CHAPTER 16 · STAGE 03 · 理解可靠性与扩展
分阶段练习与验收题
这组练习按难度递增。每一阶段都要求“解释、运行、修改”三种证据;只阅读文章不算完成。这组练习按难度递增。每一阶段都要求“解释、运行、修改”三种证据;只阅读文章不算完成。
阶段 0:环境与地图
任务 0.1:确认运行环境
运行:
node --version
pnpm --version
Node 需要满足仓库 package.json 的 engines,pnpm 版本由 packageManager 字段固定。
任务 0.2:建立六层地图
不看文章,写出入口层、控制层、模型层、能力层、记录层和策略层各一个例子。
过关题
- AgentLoop 属于哪一层?
- SessionPersistence 属于哪一层?
- 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:验证卸载
卸载拥有监听器的插件,重新触发事件,证明旧监听器没有残留。
过关题
- Context declaration merging 是否会自动创建 Service?
- 监听 Waterfall 但忘记调用 next 会发生什么?
- 插件树与 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。
过关题
- 源码包存在是否表示运行时一定加载?
- Bundle 与 Profile 的区别是什么?
- 为什么具体 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,哪些事件只留在日志。
过关题
- 为什么 Turn 可以有零个 Step?
- 为什么 assistant/chunk 与 assistant/message 都需要?
- 什么叫“模型可见就必须可重建”?
通过标准:不看时序图,能从 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,运行单文件测试。修改一个期望值使其失败,确认测试有捕获能力,然后还原修改。
过关题
- Tool Schema 中哪些字段会发给模型?
- post-execute 与 tools/result 的区别是什么?
- 为什么超时不能硬杀任意 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、错误类型、取消与真实组合测试。
过关题
- 为什么只有 Definition 不算完整 capability seam?
- 单 Provider Service 与多 Provider Registry 怎样选择?
- 默认值为什么应在 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,选择一个不会切断配对的压缩范围。
过关题
- 为什么 live Session 不能做 interrupted repair?
- Header metadata 为什么不写成普通 SessionEvent?
- Compaction 如何做到不删除旧事件?
通过标准:能解释崩溃、恢复、压缩和 replay 都怎样以同一 SessionEvent 词汇工作。
阶段 7:第一个小插件
推荐任务:工具执行计时插件
目标:监听真实工具执行生命周期,记录名称、耗时和最终 outcome,但不修改模型结果。
步骤:
- 在
tmp/下建立独立 Cordis 教程插件。 - 使用
tools/execute包住 dispatch,调用 next 并记录耗时。 - 使用
tools/result观察最终 immutable outcome。 - 添加卸载测试,证明插件移除后不再记录。
- 添加取消或失败用例,证明计时仍完成。
设计检查
- 是否调用了 waterfall next?
- 是否把 effect 绑定到插件生命周期?
- 是否没有把内部计时数据错误写入模型内容?
- 是否处理失败与取消?
- 是否使用聚焦测试验证卸载?
通过标准:插件可以加载、工作、卸载,测试能在行为回归时失败。
阶段 8:第一个真实仓库改动
选题范围
从局部错误信息、配置校验、Registry 卸载测试、简单 Tool 边界或 README 限制中选一个。
提交前清单
- 找到行为 owner,没有修改错误层级。
- 失败测试先红,再做实现。
- 更新 owning README/JSDoc。
- 模型或用户可见变化有 keyless snapshot。
- 非平凡改动有 Agent Note。
- 只运行与改动匹配的检查,并记录实际命令。
最终口试
- 你的改动为什么属于这个包?
- 它使用哪个扩展点,为什么不是另一个?
- 哪个测试证明单包行为?
- 哪个测试证明真实组合或用户输出?
- 插件卸载、取消和恢复时会发生什么?
通过标准:可以向另一位维护者解释设计、失败模式和验证证据,而不是只展示代码 diff。