8. 核心模块深度分析
这里重点拆 dsh-agent-loop 与 dsh-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 关键数据结构
Phase:idle | maintenance | running,见packages/core/agent-loop/src/agent.ts:38-46。PreparedStep:reject | 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。PromptAssembly:sections/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)statussessionctx
依据:packages/core/agent/src/runtime-types.ts:64-144。
8.5 内部协作关系
AgentLoop 依赖 agents、sessions、llm、tools、systemPrompt。ReactLoopAgent 不直接依赖具体 LLM adapter 或具体工具 provider;它依赖 ctx.llm 和 ctx.tools,所以两者可替换。
8.6 算法或处理流程
turn() 使用一个循环:
- 打开 turn。
preStep()认领输入并组装 prompt。- 若 reject 或空输入,关闭 turn。
- 写入 step/start 和 user/message。
step()循环请求模型。- 流式 chunk 写入日志并组装 assistant message。
- 有 tool-call 则执行工具,继续 step。
- 无 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/end 的 error 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-75、packages/core/session/src/index.ts:701-747。
8.10 并发或异步行为
- 工具调度支持并发,但由
isConcurrencySafe()决定;否则 exclusive。 tools/executewrapper 可以替换 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。