Skip to content
签到签到

8. 核心模块深度分析

这里重点拆 dsh-agent-loopdsh-session,因为它们是项目最能体现“Agent 是怎么回事”的模块。

8.1 模块要解决的问题

dsh-agent-loop 要解决:给定一个 Session 和模型能力,如何可靠地把输入、模型请求、工具调用、流式输出、取消、恢复和停止组织成可观察的 turn/step 循环。

dsh-session 要解决:如何让模型看到的上下文、UI 看到的聊天记录、磁盘上的会话记录和回放重建使用同一份事实。

8.2 核心抽象

  • Agent:公开生命周期接口。
  • AgentRegistry / AgentFactory:创建与恢复。
  • ReactLoopAgent:具体 driver。
  • Inbox:待处理输入的持久投影。
  • Session / SurfaceManager:事件日志与模型可见 surface。

8.3 关键数据结构

  • Phaseidle | maintenance | running,见 packages/core/agent-loop/src/agent.ts:38-46
  • PreparedStepreject | enter(messages, assembly),见 packages/core/agent-loop/src/agent.ts:50-52
  • SessionEvent<T>type/seq/time/data,surface 类型额外有 surfaceOp/sourceEventSeqs,见 packages/core/session/src/types.ts:404-430
  • PromptAssemblysections/contexts/tools/variables,见 packages/core/system-prompt/src/index.ts:115-120

8.4 公共接口

Agent 的公共接口包括:

  • followup(message)steer(message)inject(message)
  • cancel(cause, options?)
  • whenIdle()
  • runMaintenance(task)
  • status
  • session
  • ctx

依据:packages/core/agent/src/runtime-types.ts:64-144

8.5 内部协作关系

AgentLoop 依赖 agentssessionsllmtoolssystemPromptReactLoopAgent 不直接依赖具体 LLM adapter 或具体工具 provider;它依赖 ctx.llmctx.tools,所以两者可替换。

8.6 算法或处理流程

turn() 使用一个循环:

  1. 打开 turn。
  2. preStep() 认领输入并组装 prompt。
  3. 若 reject 或空输入,关闭 turn。
  4. 写入 step/start 和 user/message。
  5. step() 循环请求模型。
  6. 流式 chunk 写入日志并组装 assistant message。
  7. 有 tool-call 则执行工具,继续 step。
  8. 无 tool-call 且无 next-step 输入,则 agent/turn-stopping,关闭 turn。

对应源码:packages/core/agent-loop/src/agent.ts:246-330

8.7 状态生命周期

Agent 从 idle 开始。waking input 进入 running。一个 driver 可能连续跑多个 turn。维护任务运行时状态仍对外表现为 idle,但内部是 maintenance。dispose 会执行 disposed cancel、等待 whenIdle()、退出 registry、unwind scope。

8.8 错误处理

循环把两类错误分开:

  • 模型请求失败:以 finish chunk 的 error/aborted 形式进入 agent/request-error,监听器可返回 retry。
  • 中间件、结果处理、工具等扩展失败:直接 throw,结束当前 turn。

turn() 的 catch 会生成 turn/enderror reason,并通过 agent/error 报告,见 packages/core/agent-loop/src/agent.ts:302-322

8.9 性能考虑

  • Session.deriveMessages() 缓存每个 surface 节点,仅新增节点时投影。
  • Session.events 缓存快照,直到下次 append。
  • ReactLoopAgent.dispatch 在构造时构建一次,热路径不再分配。
  • SystemPrompt 组装会 clone tool schema,避免 provider 可变对象进入请求。

依据:packages/core/agent-loop/src/agent.ts:73-75packages/core/session/src/index.ts:701-747

8.10 并发或异步行为

  • 工具调度支持并发,但由 isConcurrencySafe() 决定;否则 exclusive。
  • tools/execute wrapper 可以替换 signal,但不能替换 call identity。
  • cancellation 是协作式,不能硬杀同一进程代码。
  • Agent 创建/恢复用 AbortSignal 和 teardown 合并,防止卸载时泄漏。

8.11 测试策略

仓库政策要求:

  • 单测覆盖核心状态机、错误路径、事件顺序、并发竞态。
  • e2e 用真实 API 验证“世界是否改变”,不能只检查模型自述。
  • snapshot 验证真实组合产出的 transcript。
  • 每个 registry 有 HMR cleanup 测试。

依据:docs/testing.md

8.12 设计模式

  • 微内核 + 插件:所有扩展通过服务与事件。
  • 注册表:tools、agents、adapters、projections。
  • strategy / provider:LLM adapter、persistence backend、sandbox backend。
  • pipeline / middleware:tools pre/execute/post、llm/stream、agent/pre-step。
  • event sourcing:Session log。
  • scope / layered registry:全局与 per-agent 注册。

8.13 当前设计的优点和代价

优点:

  • 可替换能力强。
  • 模型上下文可重建、可回放。
  • 扩展点类型化,容易静态发现。
  • 热更新和安全卸载有明确机制。

代价:

  • 学习曲线高。
  • 简单功能也要经过多个抽象层。
  • 运行时不变量和测试门禁要求高。
  • developer preview 下兼容性不稳定。

8.14 可替代实现

  • Agent loop 可以不是 ReactLoopAgent;公开接口在 dsh-agent,实现包在 dsh-agent-loop
  • Session 持久化可以换成 SQLite、JSONL 或自定义 backend。
  • 工具呈现可以用 native、code 或 both。
  • UI 可以从 Web 换成 ACP 或自定义协议 driver。

8.15 修改该模块时最容易破坏的约束

  • 不要绕过 Session log 直接给模型塞未记录内容。
  • 不要修改 agent-loop 主循环去加普通功能;应使用事件。
  • 不要让 waterfall listener 忘记 next()
  • 不要让工具返回不可 JSON 序列化的值。
  • 不要假设 agent-loop 是唯一实现;插件应依赖 dsh-agent