Skip to content
文章目录
GitHub Star

金融多业务 Agent 的可信交接:为什么不只是一个 Router? ​

以公募、私募与保险三个业务领域为例,解释身份、权限、上下文、交付与恢复

本文基于 ETF Agent 的多业务合成原型(核对至 2026 年 11 月 1 日)。账户、业务主体、基金数据和资格来源均为虚构;实际执行链经过应用 HTTP、原有 Agent Runtime、MCP Host、PostgreSQL 和独立进程恢复,并使用确定性脚本网关代替真实语言模型。文章讨论已验证的工程机制,不把它当作真实金融机构的合规系统或投资研究质量证明。

假设一位用户正在查看两只公募基金的基本信息与历史净值。研究进行到一半,他提出一个新的要求:“我还想把私募产品纳入比较,并继续参考刚才的公开基金资料。”

一个常规的 Agent Router 很容易识别:接下来应该由私募研究 Agent 处理。但产品真正需要完成的远不止这一步。进入私募业务是否需要独立资格?原会话里哪些信息可以继承?私募可以读取公募基金的公开数据,是否意味着公募基金 Agent 能反过来访问私募账户?如果已经确认交接,页面断开或进程重启,能否继续原任务,而不是重复创建一次?

路由决定“谁来处理”,可信交接还要回答“凭什么处理、带走哪些事实、产生了什么效果,以及之后如何继续”。 我们最终没有为每个业务再建一套模型循环,而是在已有的 Conversation、Run、Host、Memory 与 Recovery 上,增加一层有明确边界的业务身份和 Handoff 合同。

这篇文章围绕同一条合成请求,解释这项设计怎样真正走到结果交付,以及什么场景才有必要承担这层复杂度。

一、先分清业务领域和运行身份 ​

我们将演示中的业务划分为公募基金、私募基金和保险三个领域。这三类业务分别有不同的入口约束和可用资料,但不能简单把“一个业务领域”理解成“全系统只有一个 Agent 实例”。运营主体与业务类型是两条独立维度,同一类公募基金研究也可能由不同主体提供,拥有互不相通的会话、知识与权限。

对普通读者,理解这条业务主线只需要三个代表性角色:

业务领域代表性 Agent这次要验证什么
公募基金fund-cn(主体 A)默认研究入口,可以读取受审的公开基金资料与净值
私募基金private-cn(主体 A)新业务 Run 独立校验模拟资格,限定私募数据和公开基金只读能力
保险insurance-cn(主体 C)跨主体切换需要确认,原业务历史默认隔离,有限通用沟通偏好可共享

保险是在独立的合成 PostgreSQL/HTTP 场景中验证,并非已经整合进轻量 Demo 的常规产品入口。轻量 Demo 的主线是主体 A 的公募基金研究与私募基金交接;需要验证不同运营主体间的权限隔离时,使用独立的合成测试配置。测试中存在额外身份,并不意味着业务上需要增加一个新的公募基金入口。

在实现上,运行身份需要分别记录 scopeId(用户范围)、operatorId(运营主体)、domain(业务领域)和 agentId(确切的处理实例)。前两个用于确定访问归属,domain 表达业务规则,agentId 绑定具体配置和会话。同领域不自动共享授权,同用户也不自动共享业务资料。 这些值应由受信配置和原身份接口获得,不能靠 Agent 名称推测,更不能由模型给自己赋权。

还要区分顶层 Business Agent 与研究任务内部的 Child expert。Child 可以服务原 Main Run,执行受控子任务,却不会因此拥有另一个顶层业务会话。真正的业务切换意味着后续请求要使用另一套配置、准入规则与历史,因此需要独立的 Conversation。

这项需求并不要求为每个领域复制模型循环。如果只让一个大 Agent 靠 Prompt 切换角色,提示词本身无法建立独立的业务准入和可审查的资源授权;把新业务当 Child,则仍依附原 Root 身份。直接复制 Runtime 和数据库可以成为部署级隔离方案,但对于这次合成原型,会增加重复的执行和恢复设施。我们最终复用原有 Runtime,让受信业务身份和交接事实沿现有权威接口生效。生产环境是否需要进一步的物理隔离,取决于真实机构及其监管要求。

二、一次交接从哪里开始,又在哪一步才算完成 ​

下面继续刚才的请求:用户原本处于 fund-cn/entity-a/public-fund 会话,希望由 private-cn/entity-a/private 继续研究。正常业务路径可以概括为六个阶段:

阶段实际责任重要边界
提交意图原用户消息与当前 Main 提出目标 Agent目标意图不等于权限
准入与确认原受信配置、资格来源和必要的人机交互不允许模型自签准入回执
提交交接原 Conversation Store 建立新会话及绑定的 Run源会话与历史不被改写
消费目标 Run目标以原请求身份继续执行,而非重发一条普通消息target-created 不等于研究完成
读取与发布原 MCP Host、Source Owner 和 Publication 处理效果工具可见、可执行、证据可用分别判断
回读与恢复原 History、Control 与 Recovery 维护同一执行身份未知回执不能推导成未执行

在原型的 HTTP 路径中,当前用户消息可以带一个明确的目标意图:

json
{
  "clientMessageId": "synthetic-demo-handoff",
  "content": "demo:public-comparison",
  "interactionProfile": "concurrent-conversation@1",
  "taskRestrictions": { "personalData": "deny" },
  "businessHandoffTarget": { "agentId": "private-cn" }
}

这是合成 Demo 的固定输入示例,供验证协议而非展示模型对自然语言的自主理解。businessHandoffTarget 只限定了原用户本次可以交接到哪里;Main 仍需沿受信的 request_business_handoff 执行动作、核对已封存的模型请求和原 Run 授权。没有合法目标意图,单靠用户文本或模型说“切到私募”不会产生目标业务执行。

交接成功时,原 Conversation Store 创建新 Conversation 与新 Run。目标首条内容保留来源用户消息的原始字节和引用关系,但标识为来自应用交接的消息,而不是伪装成用户在目标会话又亲自发送了一遍。源 Conversation、原消息、已经提交的答案均保持原样。

这一步需要精确的幂等身份:同一来源消息、同一目标、同一原动作,即使前端重试或确认回执丢失,也不能无条件复制出第二个目标。对于用户而言,“开始一次新研究”和“重新打开已经开始的研究”必须被区分。目标创建回执之后,前端需要拿到原目标 Run 的身份进行订阅;新建会话的成功状态并不代表模型已完成分析。

这正是 Router 通常不负责的状态:目标选择是一次语义判断,交接提交是一项持久业务效果,回答交付又属于后续独立运行。

三、让 Manifest 定义身份上限,而不是创建第二个授权中心 ​

每个业务 Agent 有一份来自服务端的受信 Manifest,记录具体 agentId、所属 operatorId 和 domain、不可变的 Prompt 版本与内容摘要、允许引用的 Skill、知识来源、可申请的 MCP 方法及 Handoff/Memory 策略。

Manifest 很重要,但它不是“只要列了工具就允许调用”。真正执行时,至少要满足三组相互独立的条件:

第一层:业务声明。 目标 Agent 的身份和配置版本必须确切匹配,当前来源是否允许交给它处理,业务策略所允许的历史共享模式是什么。

第二层:Run 准入。 原账户身份、私募资格、当前 Manifest 及本轮策略形成受信接纳事实。已接纳 Run 会冻结这些版本,不能在恢复时按最新配置重新解释其授权。

第三层:具体资源访问。 MCP Host 与原数据来源负责人根据本 Run 的授权、工具精确身份、来源许可、个人数据限制、输入参数和预算等信息决定是否真正派发。

可以将其理解为一种逐项收窄的能力上限,而不是“模型看到什么就能执行什么”。假设私募的 Skill 描述了“可以比较公募基金”,它只能使受审公共读取成为候选;实际请求若缺少该方法的 Run grant 或具体来源许可,Host 仍然拒绝。用户自行填入 operatorId、模型提供的自述身份或远程 MCP 的描述,同样不能扩大权限。

这种思想与 NIST SP 800-162 属性访问控制(ABAC)对主体、对象、操作和策略条件的区分具有相通之处。但本项目复用的是已有 Host/Run/Source 责任,不是宣称实现了完整通用 ABAC 平台,更不把所有复杂度集中到一个新的 Policy Manager。

四、私募资格与人工确认,不是同一类授权 ​

私募案例的第一个硬约束是入场资格。用户即使在聊天里写“我满足要求”,或 Memory 记录了某种风险偏好,都不能代替可信资格来源。原型中有一个受信的模拟资格读取器,返回 eligible、ineligible、pending、expired、unavailable 等状态,并携带适用账户、运营主体、来源版本和有效时段。

仅当结果为当前有效、身份匹配的 eligible 时,新的私募 Run 才可以被接纳。其他状态会阻断新的私募模型、私募知识和私募 MCP 执行,但不会抹掉来源会话原本可以交付的公募研究。这一检查发生在业务执行之前,不能靠最后一条自然语言回答“我不能提供私募建议”来代替。

然而,有资格进入目标业务,不代表用户已经确认这一次跨主体切换;同意切换,也不代表用户同意共享全部历史。这些事实必须分开。

同一运营主体、没有扩大共享范围的 Handoff 可以通过受信策略设为自动;跨主体切换在原型中默认需要类型化人工确认,并默认使用 isolated 历史。确认请求关联来源会话、原用户消息、目标 Agent、配置版本、当前修订与过期时间。用户点击确认或拒绝,产生的是这次具体请求的决定,不是永久开通另一家主体的所有资源,更不是金融交易授权。

例如“同意”这两个普通文字不能替代类型化的 confirmed 回执。一个已经过期、被新意图 Correction 取代或身份不匹配的确认,也不能重新拿来创建目标会话。网络不确定时,系统优先核对原决定;重试沿相同的决定身份进行,不能因为没有收到 ACK 就假设之前没有提交。

如果以后需要真实合格投资者认证、持续适当性判断或运行中撤权,必须由持牌机构、数据责任方和明确的业务规则独立设计。本例只验证受信资格 → 新 Run 准入,不编码法律阈值,也不声称模拟资格等价于监管认证。

五、非对称 MCP:可以共用公开资料,不能共享私募权限 ​

多业务之间不应该只有“完全共享”与“完全隔离”两种选择。当前私募研究可能需要两只公开公募基金的资料和历史净值;这件事可以通过受限的公共数据工具完成,无需让私募 Agent 继承另一个运营主体的全部业务身份。

合成公募基金供应商只提供两个精确注册的只读 MCP 方法:public_fund__get_fund_profile 和 public_fund__get_nav_history。请求只能包含受审的虚构基金 ID(SYN-FUND-ALPHA、SYN-FUND-BETA)及有界的日期区间,不接受任意 prompt、私募客户信息、持仓原文或其他来源引用。合成数据只是为了让工具的参数、来源与结果能够被检查。

这条允许关系是定向的:获准的私募 Run 可以通过 Host 查询公开基金资料;公募业务的任一未授权实例都不能因此读到私募持仓方法、私募知识库和私募会话历史。即使模型通过 Prompt Injection 提出私募工具调用,或调用方绕过模型直接伪造请求,原 Host 仍依精确方法、业务身份、Run 授权和来源条件判断,不能从 Skill 的文字中创造许可。

这里有一个很容易被误解的细节:公开数据本身不要求拥有私募账户权限,私募账户权限也不能随公开查询参数一起传给供应商。 Host 可以允许精确公开 ID 的研究读取,同时禁止把内部账户详情夹在参数、URL 或工具正文里。供应商只接受有界的结构化参数,不知道私募研究的完整上下文。

如果这项公共来源暂时不可用,正确交付是“这部分公开比较缺少资料”,而不是把它描述成资格不合格,更不应该清空此前已经合法取得的私募解释。来源失败和业务入场失败属于不同 Owner,也有不同的影响范围。

六、跨会话 Context 的三个级别:选择历史,不复制历史 ​

交接成功之后,用户希望“接着刚才的话题聊”。这并不意味着可以直接把原 Conversation 的所有消息粘到目标 Prompt 中。原会话可能同时包含用户原话、已提交的助手正文、工具结果、缓存、旧摘要、到期来源,以及某个运营主体单独持有的敏感材料。

合成原型提供三种受信历史投影方式:

模式真正含义不包含的隐含授权
isolated新业务请求默认不继承之前的业务历史不自动带走旧 Prompt、工具结果、账户业务 Memory
selective只选定明确的消息或已提交内容,并逐项核对原来源与用途不能通过重新总结绕过被拒绝的来源
full对指定来源会话,投影其完整的获准集合不等于所有运营主体、所有附件和全部工具权力共享

这里的 full 不是“无条件整个聊天历史”。它要求同一受信账户范围、适用的运营主体及业务目的,以及每一项来源的独立读取许可。原型中的完整共享正向路径是限定于原 PostgreSQL 相同主体与受控来源条件的;跨主体默认隔离。即便是同主体,也不是因为“都是自己家业务”就天然可读。

用户消息应保留原文字节和消息身份,助手历史应从原已提交的 Publication、修订和来源摘要重新核对。工具缓存或未经证明的 Memory 派生结果不能冒充原发布正文。投影发生在本轮模型输入,而不是把来源消息表复制进目标数据库,更不会把原 Run 的授权一起继承。来源被删除或失去访问资格时,仅对应条目不再可用,合法的当前用户问题仍应得到处理机会。

实际模型输入有容量限制,因而“已获准的完整来源集合”和“这一轮能装进上下文多少文本”仍是不同问题。实现保留完整的来源引用,实际拟合时按完整条目处理容量缺口,避免截断一半却让读者以为原材料已全部消费。不能根据模型最终回答没有提到某个敏感词,就认定未授权信息没有进入输入;必须回到实际 Gateway 的拟合请求与原来源回执检查。

Memory 另有一条独立的共享边界 ​

在这项演示中,跨 Agent 的 Memory 只放行极少量可明确确认的通用沟通偏好,例如中文回答、简短或详细表达;业务风险偏好、保险需求或私募研究事实则保留原实体归属,还要受写入用途、读取用途、原始来源和状态约束。

旧记录没有可信业务归属时,不会因为某个 shared 标签就自动升级为全局记忆。纠正、遗忘、到期和临时模式也不因 Handoff 失效。受控输入核对中,保险 Agent 可以获得确认过的表达偏好,同时排除另一个主体的风险标记、未经确认的候选和来源不明的旧记录。这个观察证明了输入隔离规则,而不是保险建议的业务质量。

七、用户看到什么,与后端真正完成了什么 ​

从业务上看,交接后的关键结果不只是新对话出现在列表。我们需要区分至少四个事实:

  • 目标已创建:有唯一的新 Conversation 和绑定的原目标 Run。
  • 目标已执行:模型与工具真正沿原许可产生请求和回执。
  • 内容已提交:正文由原 Publication 保存,History 能按相同身份回读。
  • 依据可核对:引用与原结果绑定,能提供的来源信息由原读取记录决定。

轻量 Demo 通过普通 HTTP 产生新目标后,阅读端需要使用原目标 businessHandoffRunId、原 clientMessageId 与相同内容订阅结果,而不是重新发起一条普通用户消息。返回来源、重新打开目标或刷新页面时,应优先回读同一 Run 的已提交结果,不能借 UI 的重新挂载制造新的研究任务。

用户在两个会话之间导航时,旧目标 Run 的执行并不因为页面切换就失去身份。前端可以保留与原 Run 绑定的订阅;迟到事件只更新其所属的会话和 Run,不应覆盖当前浏览的另一条会话。若结果已经在 History 被原 Owner 确认完成,界面就不应继续以一条旧传输的不确定性掩盖真实终态;反过来,没有确认的传输失败也不能被随意清掉或当作成功。

对于金融来源,还要防止“有引用”被误读为“能打开完整原始材料”。公募基金查询的来源可以确认是精确的结构化工具,显示工具接口与数据日期;如果原读取窗口没有留存可供打开的内容,就必须标明可用信息边界,不能让模型编造引用段落。可见正文、可识别引用和可读原始材料,是三种不同的能力。

在已记录的合成浏览器和原 HTTP 场景中,能够观察到来源/目标分别保留、独立目标正文与两项公募基金来源、重复打开与 History 回读;人工确认、拒绝、未知回执和恢复则有独立的受控验证。它们支持工程交付事实,不证明三个业务领域已整合成可公开访问的完整产品。

八、真正的跨进程恢复:继续旧 Run,但重新审查新请求 ​

交接可能停在需要人回复的位置,也可能在写下控制事件后遇到服务退出。此时最危险的做法之一,是根据当前 Prompt/配置“再建一个差不多的请求”,然后把它当成旧任务继续。

合成冷恢复链路采用两次独立的操作系统进程:进程 A 在 PostgreSQL 中保存来源会话、目标 Handoff、冻结的业务 Run、授权及已选上下文,并停在一条阻塞式人工问题;A 关闭后,进程 B 通过原 HTTP 收到那条问题的答复,再由 Control、SQL 和原 Recovery 发现并继续同一个 Run。没有把 A 内存中的临时授权对象经 IPC 直接传给 B。

恢复时检查的不只是 runId 还存在,还包括来源消息、业务身份、目标 Handoff、原准入事实和已选材料回执是否仍精确对应。恢复后的模型输入得到原获准的用户消息和人工答复;未选择的助手历史不会自动混入。在本次已验证的业务身份与版本范围内,此前接纳的 Run 保留冻结配置与授权,不会在恢复时根据最新 Manifest 或资格来源重新获得另一份许可。

而当进程 B 接受新的独立私募用户请求时,会重新读取当前资格。如果模拟资格已经过期,这次新的 Run 被拒;旧的合法已接纳 Run 则依照原冻结授权与停止、预算规则完成。这种“旧执行保持身份、新执行重新准入”的区分,是持久 Handoff 比一次性消息路由更重要的长期合同。

它也说明了工程边界:当前方案不等于实现了运行中实时撤权或任意跨进程副作用的精确一次保证。真实监管要求、账户失效和不可逆业务动作,必须由另行授权的业务 Owner 设计更强的控制;不能从一次合成恢复测试推导出生产安全承诺。

九、实现完成度与复现边界:把工程闭环和金融业务质量分开 ​

截至这次核对,核心多业务 Handoff 已完成原 OpenSpec 的限定正式工程验收并归档。已留存的原始回执覆盖定向离线场景、隔离 PostgreSQL/HTTP、真实进程退出与重启,以及部分浏览器交互与结果回读。这说明在合成身份、数据和确定性网关下,业务 Agent 能沿原 Conversation/Run/Host 完成可信交接并形成可观察的结果。

这些证据还不能推出真实模型对复杂投资问题的理解正确、真实私募资格或基金供应商接入合法、长期生产恢复成功率、跨机构物理隔离充分性,或某项投资比较带来了实际收益。对于工程分享,这不是否定已完成的工作,而是确定结论应准确落在哪一层。

对具备私有原型代码访问权限的工程师,仓库有一个可显式开启的本地轻量入口:

bash
cd agent-app
DEMO_BUSINESS_HANDOFF=true npm run demo:api

它固定使用本机虚构身份、合成基金数据、确定性网关,并绑定 loopback;不应公开反向代理成服务。按仓库内的原始 HTTP runbook,先创建默认会话,再提交含 businessHandoffTarget 的来源请求,取得目标会话及其原 Run 身份,使用该 Run 的精确订阅入口继续,最后对照目标 Publication/History。轻量 Demo 不等于完整 Stage 2 的 PostgreSQL、跨主体保险、Memory 与冷恢复装配;本轮写作没有重新启动服务,也没有凭空补充外部读者可直接访问的公开 Demo。

公开的 open-etf-agent 当前并不包含这套等价的完整多业务实现。因此这篇文章是基于受控合成路径的架构实践分享,并非已发布的开源多业务产品教程。

十、什么时候只用 Router,什么时候必须做可信交接 ​

如果场景只是一个用户、一个授权范围、一次研究任务,在“解释净值”和“总结报告”之间切换回答策略,Router 或同一个 Agent 配不同 Skill 往往就够了。没有必要为了角色名称整齐,给每一种意图制造独立 Conversation、人工确认、Memory 归属和崩溃恢复协议。

但如果下一步改变的是业务准入、数据归属、资源来源、允许共享的历史,或者需要持久记录的业务交接效果,Router 就只能保留为入口。需要独立业务 Agent,不是因为模型不擅长分类,而是因为不能让一次模型选择变成跨业务的通用授权。

这项设计的额外成本也很实在:更多的身份与版本关系、交接幂等记录、来源逐项核验、旧新 Run 的时间边界,以及前端对“已创建、已执行、已提交、仍未知”的准确呈现。我们选择将它们尽可能交还给既有权威组件,而不新造第二个 Runtime、第二套 Memory 或独立业务权限引擎。

回到最开始的请求:一个用户从公募基金研究进入私募研究,真正值得保证的不是模型是否知道该叫哪个 Agent,而是目标是否有权开始、旧材料能否合法带走、公开数据能否受限复用、用户是否真正确认,以及进程重启后该延续哪份已经生效的事实。当这些问题由各自可信的来源回答,Router 才可以继续保持简单。

延伸阅读:模型会理解,代码才授权、持久人机交互如何接回原研究。欢迎在 ETF Agent 工程文集 提出问题或 Star,支持后续工程实践分享。

与个人博客并行:这里按问题连续阅读,博客保留时间线。 · 联系邮箱:hpoenixf@foxmail.com · 微信:hpoenixf0510