CHAPTER 16 · STAGE 03 · 理解可靠性与扩展

分阶段练习与验收题

这组练习按难度递增。每一阶段都要求“解释、运行、修改”三种证据;只阅读文章不算完成。

这组练习按难度递增。每一阶段都要求“解释、运行、修改”三种证据;只阅读文章不算完成。

阶段 0:环境与地图

任务 0.1:确认运行环境

运行:

node --version
pnpm --version

Node 需要满足仓库 package.json 的 engines,pnpm 版本由 packageManager 字段固定。

任务 0.2:建立六层地图

不看文章,写出入口层、控制层、模型层、能力层、记录层和策略层各一个例子。

过关题

  1. AgentLoop 属于哪一层?
  2. SessionPersistence 属于哪一层?
  3. Permission 与 Sandbox 为什么不能合并成一个布尔开关?

通过标准:环境命令成功,并能画出用户输入到 Session 的最小主线。

阶段 1:Cordis 基础

任务 1.1:完成最小插件

按 docs/cordis-tutorial/01-first-plugin.zh.md 创建并运行插件。

任务 1.2:添加 Service 与 inject

完成教程 Service 章节,画出 Provider 与 Consumer。说明 import type 与 inject 分别发生在编译期还是运行时。

任务 1.3:写一个 Waterfall

当输入 name 为 blocked 时短路并返回拒绝;其他输入调用 next。再添加第二个监听器,验证 blocked 情况下第二个监听器没有执行。

任务 1.4:验证卸载

卸载拥有监听器的插件,重新触发事件,证明旧监听器没有残留。

过关题

  1. Context declaration merging 是否会自动创建 Service?
  2. 监听 Waterfall 但忘记调用 next 会发生什么?
  3. 插件树与 Service 依赖图分别表达什么?

通过标准:能独立写出 plugin、service、inject、event、effect 五个元素的小示例。

阶段 2:配置与完整主干

任务 2.1:阅读主干组合

打开 packages/examples/agent-spine-demo/README.zh.md,把插件分成核心 Service、Tool Consumer、策略/不变式三类。

任务 2.2:查看最终配置

运行:

pnpm dsh --profile web --dump-config

在输出中找到 AgentLoop、LLM、Tools 和至少一个具体 Provider。

任务 2.3:追踪一个叶节点

从 Headless 启动命令开始,找到 Profile、Bundle、组合包和最终 LLM Adapter。

过关题

  1. 源码包存在是否表示运行时一定加载?
  2. Bundle 与 Profile 的区别是什么?
  3. 为什么具体 LLM Adapter 留在共享主干外?

通过标准:能通过配置解释一个 Tool 为什么对当前 Agent 可见或不可见。

阶段 3:Turn、Step 与 Session

任务 3.1:手写事件序列

为“读取 package.json 后回答包管理器”写出完整事件序列。至少包含两个 Step、一对 Tool Call/Result 和 Turn 边界。

任务 3.2:对比三种输入

分别为 followup、steer、inject 设计一个真实场景,并说明它们是否唤醒 Agent。

任务 3.3:运行 Loop 测试

pnpm run test -- \
  packages/core/agent-loop/tests/loop.spec.ts \
  packages/core/agent-loop/tests/request-reconstruction.spec.ts

选择一个测试,用三句话说明输入、行为和断言。

任务 3.4:观察 deriveMessages

打开 packages/core/session/src/index.ts,搜索 deriveMessages。列出哪些事件成为 Message,哪些事件只留在日志。

过关题

  1. 为什么 Turn 可以有零个 Step?
  2. 为什么 assistant/chunk 与 assistant/message 都需要?
  3. 什么叫“模型可见就必须可重建”?

通过标准:不看时序图,能从 followup 讲到 turn/end,并指出每个持久事件的作用。

阶段 4:模型请求与工具管线

任务 4.1:拆分模型请求

为一次 GenerateOptions 标注 config、system、tools、messages 四部分的 owner 和 Session 来源。

任务 4.2:追踪一个 Tool

选择 todo_write 或一个简单 FS Tool,找到 ToolDefinition、参数 schema、canonical output、execute 和测试。

任务 4.3:扩展点配对

为下列需求选择扩展点:

  • 权限拒绝危险命令。
  • 给实际执行增加超时。
  • 修改某类工具错误内容。
  • 记录最终工具成功率。

任务 4.4:运行 Tool 测试

找到所选工具的 spec,运行单文件测试。修改一个期望值使其失败,确认测试有捕获能力,然后还原修改。

过关题

  1. Tool Schema 中哪些字段会发给模型?
  2. post-execute 与 tools/result 的区别是什么?
  3. 为什么超时不能硬杀任意 JavaScript Promise?

通过标准:能设计一个 Tool 的输入、输出、执行、取消、策略和展示,而不是只写一个函数。

阶段 5:Capability Seam

任务 5.1:画 Bash 链

画出:

tool-bash
  → ctx.shell
  → bash-local
  → ctx.subprocess
  → subprocess-local

在每一条边上写出调用的抽象能力。

任务 5.2:替换 Provider 思考题

假设命令需要在 E2B 运行,列出应该替换的 Provider,以及不需要修改的 Consumer。

任务 5.3:自己设计一条 Seam

为天气查询设计:Definition、mock Provider、HTTP Provider、Tool Consumer、错误类型、取消与真实组合测试。

过关题

  1. 为什么只有 Definition 不算完整 capability seam?
  2. 单 Provider Service 与多 Provider Registry 怎样选择?
  3. 默认值为什么应在 Provider resolve 阶段显式物化?

通过标准:看到一个新能力需求时,能先设计角色与生命周期,再决定包结构。

阶段 6:持久化与恢复

任务 6.1:区分三个时刻

为 Session.append、Persistence flush、Agent idle 各写一条可以保证的事实和一条不能保证的事实。

任务 6.2:模拟崩溃

写出一份以 turn/start 开始、包含一个完整 Step、但没有 turn/end 的事件序列。说明冷加载如何修复,为什么不删除已有 Step。

任务 6.3:Resume/Fork/Replay 对比

画表格比较 SessionId、seed、lineage、是否调用真实模型和典型用途。

任务 6.4:设计安全压缩区间

给一段含两个 Tool Call/Result 的 surface,选择一个不会切断配对的压缩范围。

过关题

  1. 为什么 live Session 不能做 interrupted repair?
  2. Header metadata 为什么不写成普通 SessionEvent?
  3. Compaction 如何做到不删除旧事件?

通过标准:能解释崩溃、恢复、压缩和 replay 都怎样以同一 SessionEvent 词汇工作。

阶段 7:第一个小插件

推荐任务:工具执行计时插件

目标:监听真实工具执行生命周期,记录名称、耗时和最终 outcome,但不修改模型结果。

步骤:

  1. 在 tmp/ 下建立独立 Cordis 教程插件。
  2. 使用 tools/execute 包住 dispatch,调用 next 并记录耗时。
  3. 使用 tools/result 观察最终 immutable outcome。
  4. 添加卸载测试,证明插件移除后不再记录。
  5. 添加取消或失败用例,证明计时仍完成。

设计检查

  • 是否调用了 waterfall next?
  • 是否把 effect 绑定到插件生命周期?
  • 是否没有把内部计时数据错误写入模型内容?
  • 是否处理失败与取消?
  • 是否使用聚焦测试验证卸载?

通过标准:插件可以加载、工作、卸载,测试能在行为回归时失败。

阶段 8:第一个真实仓库改动

选题范围

从局部错误信息、配置校验、Registry 卸载测试、简单 Tool 边界或 README 限制中选一个。

提交前清单

  1. 找到行为 owner,没有修改错误层级。
  2. 失败测试先红,再做实现。
  3. 更新 owning README/JSDoc。
  4. 模型或用户可见变化有 keyless snapshot。
  5. 非平凡改动有 Agent Note。
  6. 只运行与改动匹配的检查,并记录实际命令。

最终口试

  1. 你的改动为什么属于这个包?
  2. 它使用哪个扩展点,为什么不是另一个?
  3. 哪个测试证明单包行为?
  4. 哪个测试证明真实组合或用户输出?
  5. 插件卸载、取消和恢复时会发生什么?

通过标准:可以向另一位维护者解释设计、失败模式和验证证据,而不是只展示代码 diff。