Files
deepseek-harness/packages/core/agent/README.zh.md
T
Yichen Jiang 21aeff3029 Merge remote-tracking branch 'origin/master' into worktree/llm-reasoning-effort
# Conflicts:
#	.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml
#	docs/config-catalog.md
#	docs/core-data-structures/session.i18n.yaml
#	scripts/type-equiv.manifest.json
2026-07-27 13:35:10 +08:00

117 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# dsh-agent
[English](README.md) | 中文
Agent 接口、注册表、进程本地发起方作用域,以及 `agent/*` 事件词汇。每个插件(UI、钩子、编排器)都面向此处定义的 `Agent` handle 编程;它不依赖循环,因此循环可以替换。
可选配套包 `@deepseek-ai/dsh-agent/invariant` 会向 `ctx.invariants` 注册此包的 agent 状态转换检查。根 agent 服务不会隐式加载诊断。
## 服务:`AgentRegistry`ctx 键:`agents`
跟踪实时 agent,并在异步驱动器工作中携带发起调用的 Agent,而无需导入具体循环包。
### 公开 API
带作用域的注册表层:`Agent.ctx` 是 agent 的作用域上下文(`dsh-scope`,键 = 该 agent)。通过它注册工具/段/变量/监听器,只对该 agent 生效,并在释放时全部撤销。`agentEvents(ctx, agent)` 是普通 agent 主体操作的融合分发器(一次完成载体 + 注入主体);其通知 mode 会调用每个监听器,并同时收容同步抛出和返回 Promise 的拒绝。注册表生命周期对复用一个稳定路由载体。`assembleContextFor(agent)` 构建按 agent 的组装上下文(同时包含 `agent` + `scope`)。`installAgentLlmTarget(agentCtx, target)` 在提示词组装期间快照可变的提供方/模型/推理(reasoning)强度选择,将路由应用到提示词变量,并将完整目标应用到一个步骤的请求路由;如果没有选定推理强度,则会清除继承的推理强度,使该目标使用适配器/提供方默认值。`CreateAgentOptions.setup(agentCtx)``ResumeAgentOptions.setup(agentCtx)` 在新建或恢复的 agent 尚未发布时,组合其带作用域的世界。Setup 是受信任、仅用于组合的同进程代码:只有创建完成后才能驱动 agent。
- `ctx.agents.register(agent: Agent): () => void`:记录一个 **已经构造完成** 的 agent。随调用 fiber 释放。
- 高级有序生命周期:`enter(agent, owner): () => void` 强制 `agent.id === agent.session.id`,执行权威 ID 冲突检查,并在不通知的情况下插入;`owner` 显式记录实时创建方 agent 关系(根 agent 为 `undefined`),与持久会话谱系无关。`announce(agent)` 恰好发出一次 `agent/created`。创建监听器同步请求的 detach 会延后到该次分发结束;每次 detach 都会检查捕获的条目对象,因此陈旧能力无法删除后续使用同一 ID 的替代项。异步工厂使用这一拆分;普通插件使用 `register()`
- `ctx.agents.get(id: SessionId): Agent | undefined`
- `ctx.agents.isOwnedBy(id: SessionId, owner: Agent): boolean`:该确切实时条目是否通过父 agent 的作用域上下文创建;运行时所有权与持久会话谱系无关。
- `ctx.agents.list(): Agent[]`
- `ctx.agents.roots(): Agent[]`:在没有所属 agent 上下文的情况下创建的实时 agent;带谱系的恢复会话仍可能是运行时根。
#### 发起方 Agent 作用域
`AgentLoop` 在发起方边界内运行每个具体驱动器的完整生命周期。并发驱动器彼此隔离:子驱动器的 continuation 携带子 agent,而 `withInitiator()` 返回后,父 continuation 立即重新取得父 agent;drain 跟踪持续到子驱动器的 Promise 结算。创建、持久化加载和未发布 setup 位于子边界之外,因此由父 agent 发起的 setup 会继承父 agent,而 `agentCtx.agent` 显式标识子 agent。
- `ctx.agents.currentInitiator(): Agent | undefined`:读取继承的发起方,不要求其存在。
- `ctx.agents.requireInitiator(): Agent`:读取发起方,缺席时抛出 `no initiating agent is active`
- `ctx.agents.withInitiator(agent, operation)`:使用一个确切 Agent 运行,并保留操作的确切同步值或 Promise。
- `ctx.agents.withoutInitiator(operation)`:对无关的进程本地工作隐藏继承的发起方。
该作用域携带 `Agent` 本身,并且只在进程内有效。环境中的身份既不是存活证明,也不是授权;在服务、worker、进程、持久化和 wire 边界,显式 Agent 字段仍是权威来源。Teardown 会拒绝新边界,允许注入的依赖方和返回 Promise 的边界 drain,然后禁用底层 `AsyncLocalStorage`;未返回的工作仍归将其分离的子系统所有。如果某个边界继承的异步链开始卸载一个拥有它的 Cordis fiber,该嵌套边界链会从 drain 中释放,使卸载不会等待自身;其 continuation 会在 teardown 后观察到已释放的服务。详细边界与 teardown 契约由[发起方作用域决策](../../../.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md)拥有。
#### 工厂 seam(创建)
Agent *创建* 由实现 `AgentFactory` 的插件(`dsh-agent-loop`)提供,并通过 `setFactory` 注册。这样,创建功能留在 `dsh-agent` 接口上,消费方(UI、ACP 桥接层)可以面向 `ctx.agents` 编程,而不依赖具体循环包。注册表会把已经 traced 的 Service 规范化为具体目标,并通过调用方上下文重新 trace 每次调用;这既避免嵌套 Cordis shadow,也会把显式、绑定调用方的 `ownerCtx` 传给普通工厂。
- `ctx.agents.setFactory(factory: AgentFactory): () => void`:注册创建工厂(循环在构造时调用)。第二个工厂会导致抛出;释放时清空槽位。
- `ctx.agents.create(options: CreateAgentOptions): Promise<AgentHandle>`:创建会话和 agent,在不发布的情况下等待可选 setup,然后通过最终的 `SessionStore.enter()``AgentRegistry.enter()` 检查发布。不支持并发创建同一 ID:多个操作可以进行准备,但只有一个能进入;每个失败方都会回滚其私有作用域/会话/驱动器。可选且只用于创建的 `signal` 会取消未发布的 setup,并在返回 handle 前分离;之后的取消使用 `handle.dispose()``agent.cancel()`。发布包含在回滚范围内,回滚期间每条已交付创建边都会成对处理。未注册工厂时拒绝。
- `ctx.agents.resume(options: ResumeAgentOptions): Promise<AgentHandle>`:加载持久化会话([会话持久化](../../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md)),创建新的未发布 agent 作用域,等待可选 setup,并使用相同的最终进入发布序列。其可选 `signal` 同样只用于创建。未注册工厂或未配置会话持久化时拒绝。
`AgentHandle = { agent: Agent; dispose(): Promise<void> }`。Disposer 是一项 **消费方能力**;仅持有裸注册表条目的观察方不能 teardown agent。调用方 fiber 和已注册工厂提供方是结构化共同拥有者:调用方卸载会强制结构化所有权,而工厂卸载必须停止旧实例,因为它们的作用域依赖表层属于该提供方。任意拥有者调用 `dispose()` 都会到达同一个记忆化静默边界:它停止循环,`await` 循环退出以及每次未完成的空闲注入刷新(而不只是 `disposed` 状态翻转),注销 agent,从存储中移除其会话,最后撤销其作用域世界。该顺序会在分离会话前捕获 agent 启动的每个 `session/flush`,并让作用域监听器存活到这些检查点完成。`ctx.agents.get(id)` 仍返回裸 `Agent`ACP 桥接层与进程内 subagent 后端持有消费方 handle,而配置创建的 agent 已由循环 fiber 拥有。
### 实时事件
`dsh-agent` 声明实时 `agent/*` 协调词汇,使插件不必依赖具体循环。确切签名、分发 mode、作用域筛选规则与 payload 契约位于生成的 [Cordis 事件目录](../../../docs/cordis-catalog/events.md)[架构轮次流](../../../docs/architecture.md#turn-flow) 展示它们与持久会话事件的相对顺序。
生命周期边有两个重要的本地注意事项。`agent/created` 在作用域 setup 之后、会话与 agent 注册表条目都存在之后运行。Setup 是受信任、仅用于组合的代码;紧随其后且不可 veto 的 `agent/session-start` 通知是第一个受支持的启动注入点。`agent/disposed` 始终表示确切 agent 已离开注册表。AgentLoop 在其驱动器静默后发出该事件,而有序 teardown 此时可能仍在分离会话并撤销作用域;直接注册的自定义 agent 自行拥有任何更强的驱动器顺序契约。
大多数拦截点都是返回 seam 专属决策的协作式 waterfall。轮次作用域的异步 seam 接收一个显式 `AbortSignal`,其中 `signal` 紧邻 waterfall 最终的 `next`;监听器可以配合,但不得将它保留为控制另一轮次的权限。信号在终止策略执行期间仍是权威来源,并在发布 `turn/end` 前立即退役,因此终止观察方与之后的持久性刷新无法取消已完成的轮次工作。`agent/pre-step``agent/post-step` 是步骤持久工作前后的串行检查点,而 `agent/request-error` 是失败模型请求的恢复 waterfall:失败步骤关闭后,它接收确切错误、规范化失败事实、不可变的先前重试事实和信号;重试会打开一个新的编号步骤。`agent/turn-stop` 是终止串行 fold:它在普通 continuation 与 steering fold 之后运行;返回的停止会持续到轮次关闭和刷新,因此之后的 steering 不能创建额外步骤或轮次。普通排队提示词保持原样。有效的广义取消会先发出只观测的 `agent/cancel-requested` 及其解析后的类型化原因,再清空队列并中止;通知失败会被收容,不能 veto 停止。信号生命周期由[显式取消决策](../../../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md)拥有;作用域分发与终止结算由 [agent 作用域 runtime 设计 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md#three-execution-boundaries-are-deliberately-one-way)拥有。
`PromptDecision.additionalContexts` 是数组,因此每个上下文都保留自己的来源、元数据和放置位置。`SendOptions.contexts` 将同一形状绑定到一条排队消息,并且发生在提示词拦截前:默认允许决策会继续携带它,而被阻止的提示词不记录上下文。缺席或 `separate` 放置会写入独立注入的 `user/message`plugingoal 来源);`prompt-prefix` 会把上下文、`## My request:` 分隔符和有效提示词写入一条 `user/message``steering/message`,其对模型隐藏的 envelope 会保留直接提示词与上下文描述符供人类回放。包装下游允许决策的监听器会保留其 `content``additionalContexts`,除非有意替换任一字段;返回的允许决策是权威来源。`ContinuationDecision` 原因更窄:它成为不附带上下文元数据的 `steering/message`
轮次和步骤边界以及模型 token 流是持久 `session/event` 事实,而不是镜像的 `agent/*` 通知。消费方从会话 feed 读取 `turn/*``step/*``assistant/chunk`;工具策略与结果观测属于 [`dsh-tools`](../tools/README.md) 记录的完整流水线。
### Agent 接口(`types.ts`
`Agent` 是结构化接口。`followup()``queue()``steer()``inject()` 指名常见调用方意图;调用方已经拥有确切路由事实时,`send(ResolvedAgentInput)` 公开同一接受路径([决策](../../../.agents/notes/implemented/architecture/2026-07-24-intent-named-agent-delivery.md))。每个 `ResolvedAgentInput` 字段均为必填,其可辨识联合会排除附带上下文的非唤醒下一步骤注入。FIFO 接受会返回不透明 `AgentMessageId`,由该条目的 `agent/inbox/enqueue`/`dequeue`/`discard` 事件携带。驱动器会在通知和入队前,把内容、已解析来源、附带上下文与对模型隐藏的元数据快照为一条已分离、深度冻结的无损 JSON 记录;无效数据同步抛出。辅助方法应用默认值:在省略 `options.source``followup()``queue()``steer()` 调用中,会将直接人类输入声明为 `{ kind: 'user' }`,因此每个非人类生产方都要标记自身内容。
- `agent.followup(content, options?)`:将一条独立 FIFO 消息作为自己的轮次排队,并唤醒驱动器。接纳后,独立上下文成为注入的 `user/message` 事件,而 prompt-prefix 上下文会在同一 `user/message` 中写到有效请求之前;阻止或替换默认附加上下文决策可以丢弃它们。轮次原理由 [one-send-one-turn Agent Note](../../../.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md)拥有。
- `agent.queue(content, options?)`:将相同的普通消息排队,但不唤醒空闲驱动器。单独的排队项会让 `whenIdle()` 保持已解析,并在下一条唤醒消息前一并处理。
- `agent.steer(content, options?)`:运行时为下一个检查点排队 steering,且不分发 `agent/prompt-submit`;空闲时创建会唤醒的普通轮次。附带上下文留在同一冻结记录中;独立上下文紧跟 steering 事件追加,prompt-prefix 上下文则写入该事件。二者都能在迟到 steering 转为排队输入时保留,并随消息在取消或终止丢弃时消失。策略仍可以在另一步骤前停止;轮次关闭及其检查点之后,剩余 steering 会成为稍后的排队输入,除非终止轮次策略、取消或释放将其丢弃。
- `agent.inject(content, options?)`:接受已分离的会话内上下文而不运行模型;下一次请求会看到其 `user/message`(默认 plugin 来源),其中 `content` 逐字渲染为 user role 消息。`InjectOptions` 有意不提供附带上下文。`options.meta` 持久化不透明 JSON 状态,但不渲染。轮次打开时,注入加入该轮次;当前工具批次执行时会延后 FIFO,如果执行被中断则在轮次关闭前 drain。空闲时,它会被包在一次性 `injection` 轮次和持久性检查点内([轮次包围不变式](../../../.agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.md))。注入绕过 FIFO,不发出 `agent/inbox/*` 事件。
- `agent.send(input)`:接受完整指定的路由,不应用辅助方法默认值。`next-turn` 指向普通 FIFO;带 wakeup 的 `next-step` 指向 steering,并在空闲时回退为会唤醒的普通轮次;不带 wakeup 的 `next-step` 是注入,且要求 `contexts: []`。调用方没有元数据时也要显式提供 `meta: undefined`
- `agent.cancel(cause?, options?)`:取消活动轮次,并在未设置 `options.keepInbox` 时取消全部待处理工作:省略原因表示 `{ kind: 'user' }`TypeScript 把调用方限制在 `user | parent` 联合中,活动持有者会在中止前把其判别字段复制为已分离、冻结的信号原因。有效调用会在清除排队与 steering 工作前,随原因发出 `agent/cancel-requested`;丢弃项在 `agent/inbox/discard` 上报告,观察方可以同步状态,但不能 veto 取消。`keepInbox: true` 会中止轮次,但保留排队与 steering 项(不丢弃,且不删除尚未开始的工作)。同进程类型化 seam 不会为无类型调用方添加运行时校验或兼容回退。重复取消活动轮次时,首个信号生效;空闲取消是安全空操作,不发通知。ACP 映射到 `user`,进程内父传播映射到 `parent`。原因只存在于运行时;持久 `turn/end` 保持粗粒度的 `aborted`
- `agent.whenIdle()`agent 从 `running` 结算后达到静默时解析(idle ⇒ 立即;disposed ⇒ 等待循环退出)。这是非拥有者的静默观测钩子:观察工作结算,但不 teardown agent。Teardown 独立存在;生命周期拥有者通过 `AgentHandle.dispose()` 停止并注销,并直接等待循环退出。
- `agent.session``agent.status``agent.options``agent.id`
`running` 描述驱动器范围的 drain 区间,而不是轮次仍打开的证明;它可以覆盖轮次关闭、持久性检查点和连续的排队轮次。
### 扩展点
- Agent 创建:`AgentLoop.create()` 是具体配置路径实现(位于 `dsh-agent-loop`),程序化消费方则通过 `ctx.agents.create()`/`ctx.agents.resume()` 创建或恢复有所有权的 agent。替换循环时,应实现 `Agent` 并通过 `ctx.agents.register()` 注册。
- 事件监听器:全部 `agent/*` 事件都在此处声明,不需要依赖循环包。
- Subagent 委派不是 `Agent` 方法;提供方通过工厂 seam 创建或驱动普通 handle,因此委派传输留在核心 agent 接口之外。
## 模型体验
### 用户、steering 与注入消息
#### 模型所见
四个意图辅助方法与完整解析的 `send` 路径会向所属会话提供输入。`agent/prompt-submit``agent/session-prefix` 和其他已声明事件让插件能够阻止提示词或添加请求材料;此接口本身不贡献固定文案。
#### Token 影响
已接受内容成为保留历史或重复会话前缀;被阻止内容不贡献请求 token。大小取决于调用方与插件。
#### KV Cache 影响
已接受历史与 steering 只追加;被阻止的提交不发送请求。会话前缀在循环实例内保持稳定,而新建或恢复的实例可能建立不同前缀。
### Agent 作用域的请求组合
#### 模型所见
通过 `agent.ctx` 进行的注册可以遮蔽提示词段或工具,也可以在未发布 setup 期间安装仅适用于该 agent 的拦截器。
#### Token 影响
此包自身不增加 token;带作用域贡献只影响该 agent,并在释放时消失。
#### KV Cache 影响
只要 agent 的作用域注册不变,前缀就保持稳定。改变提示词段、工具定义或请求监听器的 setup 或 reload,可能从第一个受影响的请求 token 起使复用失效。
## 已知限制与延后工作
- **发起方作用域只存在于进程内**:worker、子进程、HTTP、持久队列和重启会显式物化所需身份。
- **环境身份可能比存活状态更久**:消费方在生命周期敏感工作前,仍要检查 `agent.status`、取消状态和所属能力契约。
- **委派以外的 agent 间通道**:共享状态、流式子输出和后台/轮询语义仍在当前同步 `ctx.subagents` seam 之外。
- **`agent/session-start` 不能为启动设置门禁**:它仍是同步且不可 veto 的通知;必须在发布前完成的异步组合属于工厂的 `setup(agentCtx)` 事务。
- **`cancel()` 默认清空 inbox**:它会中止正在处理的轮次以及排队和 steering 工作;`cancel(cause, { keepInbox: true })` 只中止轮次并保留待处理项。仍不存在只中止步骤、同时让正在处理的轮次继续运行的操作([停止表层 Agent Note](../../../.agents/notes/implemented/simplification/2026-06-20-public-agent-stop-surface.md))。
- **`HookContext` 恰好携带一个 `MessageSource`**:多个插件合并到一次工具调用上的贡献会归入一个来源;无法表示混合来源。
- **`SessionStartSource` 预留 `'clear'`/`'compact'`,但还没有发出方**:在驱动子系统落地前,只会出现 `'startup'`/`'resume'``TODO(compaction)`)。