Files
deepseek-harness/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.zh.md

9.2 KiB
Raw Blame History

Agent Note: 基于 AsyncLocalStorage 的发起 Agent 作用域

Status: implemented

English | 中文

问题

harness 中存在两种有用但不同的上下文概念。Cordis Context 负责选择服务、注册归属和生命周期;agent.ctx 是一个存活 Agent 所拥有的扁平注册作用域。Agent 与会话身份描述的则是异步操作主体。若把根 ctx.agent 改成「当前正在运行的 Agent」就会混淆这两种含义并在单进程并发驱动多个 Agent 时失效。

进程内深层基础设施有时需要在显式传递的循环、工具及请求参数之下获取可信的发起 Agent例如宿主感知传输层、追踪辅助函数、日志器或网关客户端。要求每个私有辅助函数都转发 agent 会造成重复,而进程级可变槽会在跨 await 时发生并发错误。模型可见参数也不适用,因为模型不得选择可信的会话或路由请求头。该载体归 Agent 服务所有,而非模型可见的可选上下文。

决策

必需的 ctx.agents 服务使用 Node AsyncLocalStorage 携带发起 Agent。它直接存储同一个 Agent,不引入只有一个字段的帧;另一个私有运行标记只记录嵌套边界的谱系,供 teardown 记账使用,不携带身份。核心数据目录标明了所携带的类型。

currentInitiator() 用于可选读取,requireInitiator() 抛出 no initiating agent is activewithInitiator(agent, operation) 保留操作返回的同步值或 Promise 本身。withoutInitiator(operation) 会建立清空边界,供不得继承 Agent 的工作使用。会话仍通过 agent.session 推导;轮次、步骤、工具调用、signal、模型、cwd、沙箱和授权继续由现有归属方管理。

AgentLoop 已经注入 ctx.agents,并用 agents.withInitiator(agent, ...) 包裹每个具体驱动的完整 runLoop 生命周期。循环、轮次、步骤和工具调用的包内私有入口从 ctx.agents 恢复同一个 Agent一次推导 agent.session,再由操作内辅助函数捕获该值,避免在浅层接口中转发具体驱动或 Session。若 Session 本身就是底层辅助函数的实际接口,该函数会保留狭窄的 Session 参数,而不会只为隐式查找而接收更宽泛的 Context

因此,并发驱动使用彼此独立的存储。子驱动的异步延续携带子 AgentwithInitiator() 返回后,调用方立即恢复之前的存储,而活动运行计数仍持续跟踪返回的 Promise直到其结束。创建、持久化加载和尚未发布的 setup(agentCtx) 位于子驱动边界之外:由父 Agent 发起的创建使用父身份,而 agentCtx.agent 显式标识子 Agent。

隐式身份不会取代显式约定。ToolExecution.agentAssembleContext.agentGenerateOptions.sessionId、任务归属、父子请求、ctx.agentagentCtx.agent、审批与 hook 主体、cwd 选择、取消、worker 和进程消息、持久化记录及协议身份都保持显式传递。远程边界会把所需身份写入类型化请求,因为 ALS 只在进程内有效。

AgentRegistry 管理一个有序的发起方生命周期。teardown 会先拒绝新边界;移除 ctx.agentsAgentLoop 等注入方开始排空,注册表随后等待活动的返回 Promise 边界,最后调用 AsyncLocalStorage.disable()。如果某个边界继承的异步调用链启动所属 Cordis fiber 的卸载,私有运行标记谱系会从排空范围中释放该嵌套边界链,从而避免 teardown 等待自身完成,同时继续排空无关边界。在普通排空期间,进行中代码可通过保留的服务引用继续调用 currentInitiator()requireInitiator()dispose资源释放发起方方法会抛出 agent initiator scope is disposed。根 Context dispose 可能并发启动同级 fiber 的 teardown因此除 Cordis 依赖顺序外仍必须统计活动边界。

发起方作用域不负责管理脱离返回链的工作:注册表排空只跟踪 withInitiator()withoutInitiator() 返回的 Promise。边界内创建的异步资源会继承其存储直到自身结束或 ALS 被禁用;所属 seam 必须显式停止未纳入返回 Promise 的工作。Agent 所属的前台工作会把完整生命周期纳入返回值,并保留显式取消约定。无关的定时器、队列和部署基础设施在 withoutInitiator(operation) 下启动队列、worker、进程和协议边界必须序列化身份不能期待 ALS 传播。

宿主感知的传输层可以从 ctx.agents.requireInitiator().session.id 推导由部署方拥有的 X-Harness-Session-Id 等请求头;模型可见 schema 和参数中不包含该请求头。本决策不让现有生产 MCP 或 Web 传输层采用此请求头。测试替身传输层用于证明可信边界,而不会把宿主路由策略分配给现有的提供方无关 seam。

本决策扩展 Agent 注册作用域约定及其运行时设计,不会改变其中 agent.ctx 的静态含义。

验证

Agent 服务测试锁定可选与必需读取、同步值及跨 realm Promise 的精确身份、内建 Promise 结束状态观察、并发、嵌套及清空边界、同步抛错或 Promise 拒绝后的恢复、普通与重入排空顺序及保留引用的错误。AgentLoop 集成测试锁定并发与嵌套驱动、无 Agent 调用、AgentRegistry 重启、根 Context 销毁以及包内私有的循环和工具调度通过隐式查找完成。组合、模块图、构建及运行时闭包检查确保默认组合包、SDK 主干、Python 运行时闭包及直接 AgentLoop harness 通过 ctx.agents 完成接线,无需其他提供方。

测试替身形式的宿主感知传输层在内部推导 X-Harness-Session-Id,并验证工具 schema 与日志中记录的参数都不包含身份字段。服务有意不排空边界操作所返回 Promise 之外的异步工作;这类工作仍由所属方的显式停止约定管理。

考虑过的替代方案

在每个函数中传递 Agent。 公开、worker、进程、持久化和协议边界继续显式传递但要求每个进程内私有辅助函数都携带 Agent 只会造成重复转发不会提高可信度。ALS 仅限于这些显式边界内部的异步调用链。

ctx.agent 变成动态值。 ctx.agent 已经表示与 Agent 作用域 Cordis 上下文静态关联的 Agent。改变根上下文的含义会混合注册作用域与执行作用域并让并发行为变得意外。

新增独立的 ctx.agentExecution 服务。 该载体没有独立后端、配置或身份类型:它存储的是 ctx.agents 已经管理的同一个 Agent,而 AgentLoop 本就依赖该服务。第二个必需提供方会增加包、组合、生命周期、生成目录及测试 harness 接线,却没有拆出真实能力。

保存命名帧或完整运行时帧。 只有一个字段的 { agent } 帧只是包装该值,而 Agent、会话、inbox、取消、轮次、步骤、工具执行和持久化已经有各自的真源。增加更多字段会产生陈旧快照和另一套生命周期直接携带 Agent,由方法名标识边界,无需重复保存状态。

包含步骤级 AbortSignalcwd、沙箱或授权。 它们的生命周期及权限范围与驱动边界不一致,而且现有 seam 已经显式传递这些值。新增控制能力需要独立决策和嵌套生命周期约定。

使用进程级 currentAgent 并发 Agent 和 subagent 会在异步延续执行之间相互覆盖,因此可变全局值只在 harness 不具备的串行保证下才正确。

从模型可见参数推导身份。 不能信任模型或用户输入来选择会话、租户或沙箱路由。

给每个能力 seam 增加路由身份。 这会把宿主关注点扩散到提供方无关 API。宿主感知实现拥有其传输请求头而公开边界继续显式传递身份。

后果

深层基础设施可以获得一个可信的进程内发起 Agent而无需加宽现有工具和能力请求。并发及嵌套驱动会自动隔离AgentLoop 不增加新的必需服务HMR热模块替换或根 Context dispose 会在禁用 ALS 前达到完全停稳。

该依赖不会出现在函数签名中,并且携带一个具有控制能力的 Agent 对象。消费方必须将其限制在横切基础设施中把隐式存在视为既不证明存活、也不授予权限并保留显式取消和归属检查。ALS 还有常驻传播成本,也无法跨越 worker、进程、HTTP 或持久化队列边界。

该销毁设计有意依赖 Node 的 Stability 1实验性 API AsyncLocalStorage.disable()。Node 要求在 ALS 实例可被垃圾回收前调用 disable(),这对 HMR 替换 AgentRegistry 所拥有的实例尤为重要;服务状态守卫会阻止 dispose 后通过后续边界重新进入该实例。

该作用域有意只携带 Agent省略轮次、步骤、signalcwd、沙箱和授权。若真实消费方无法使用现有显式字段,必须另行论证扩展;陈旧字段最多只能误标遥测数据,绝不能授予控制权。