练习 31:终章——你手里的这个东西

这一章不加任何东西。不加工具,不加机制,不加依赖。它只做一件事: 把你从练习 1 写到现在的东西摊开,看清楚。

盘点

先数数。你手里现在有:

  • 一个 main.go,4971 行。 从练习 1 那个一百来行的文件长到这里, 三十个练习,每一步都能跑,没有一步是跳跃。
  • 19 种工具。 read_file、write_file、edit_file、bash、skill、grep、 glob、web_search、web_fetch、mcp__*(动态)、sub_agent、workflow、 schedule_wakeup、get_goal、create_goal、update_goal、terminal_output、 terminal_input、browser。
  • 1 个第三方依赖。 一个 websocket 库,练习 30 才出现,出现时交代了 理由。在那之前,标准库写到底。
  • 0 个框架。 没有 LangChain,没有 agent SDK,没有编排引擎。

再数数它会什么:常驻对话、会被打断、能插话、会话落盘、上下文压缩、 跨会话记忆、规则文件、skill 按需加载、并行分身、代码持有的编排、外部 工具按协议接入、OS 级沙箱、误删有回收站、危险命令有闸门、自己安排 唤醒、给自己记目标和预算、后台进程有人管、检索、联网、一双操作浏览器 的手。

这个清单在任何一家 agent 产品的功能页上都不会丢人。而它的每一行, 你都知道是哪一章、为什么、用什么换来的——因为是你自己写的。

取舍地图

三十个练习做过的决定,按章节排是流水账,按手艺排就是一张地图。 全书反复出现的手艺一共七种,每一种都出现了不止一次——第一次是教训, 后来是习惯:

手艺第一次出现后来又出现在
教 → 拦 → 兜底:软约束、硬闸门、坏结局兜底,三层各管一段练习 8–10(base prompt / 权限 / 误删备份)练习 18 的 skill 转正审批、练习 19 的子 agent 未完成标记
报错也是 prompt:错误消息里铺好下一步的路练习 21(计划写歪,报错带形状示例)练习 29 grep 截断报总数、练习 30 的"点不到"指路和 url 参数护栏
上限是信息设计:截断可以,不吭声不行练习 7(bash 输出 tail 截断)练习 12 预算播报、练习 28 防轮询硬停、练习 29 两道 grep 上限、练习 30 observe 的"共 76 个只列 60"
谁能拿到工具,是结构不是自觉练习 19(子 agent 的注册表天生没有 sub_agent)workflow、schedule_wakeup、goal 三件套、后台观察窗、browser——全部排在 subAgent 之后注册
洞要点破,不粉饰:管不到的地方明说练习 22(MCP 工具绕过权限系统)练习 23 进程内工具不过沙箱、练习 29 web_fetch 自觉看开关、练习 30 Chrome 出网谁也管不着
设计决定会随前提失效:推翻要认账练习 26(推翻练习 24 的"空闲时交还 Ctrl+C")练习 28 后台 ctx 与轮次解耦、练习 30 maxRounds 10→30
外包给现成的,还是自己写:传输外包,判断自己留练习 29(ripgrep 干活,上限自己管)练习 30 的 websocket 库;反例贯穿全书——循环本身、权限、预算,一行都不外包

看一遍这张表的左列。没有一行是算法,没有一行是模型技巧——全是接口 形状、报错文案、上限数字、注册顺序。agent 的手艺不在"让模型更聪明", 在给一个已经够聪明的模型修路、设闸、立牌子。 这就是前言那句话的 另一面:架构只有一种,所以功夫全在工具的设计里。

没写的东西,为什么可以以后再要

octo 里有三大块,这本书一行都没写:serve(一百多个 Go 文件的 HTTP/WebSocket 服务加一整个网页界面)、IM(飞书、Telegram、 Discord、钉钉、企业微信、微信六个平台适配器)、scheduler(cron 定时任务,到点自动开会话)。听起来像三座大山。

现在把它们逐个对到你已经写过的东西上:

没写的它的本体,你在哪一章写过
serve:网页/API 接入练习 24 的常驻循环。serve 无非是把"从 stdin 读一行"换成"从 websocket 收一条",收件箱、打断、healTurn 全部原样复用
IM:六个平台适配器练习 25 的插话收件箱。IM 消息进来之后走的就是同一个 inbox——适配器只是把各家平台的消息格式翻译成"一句话",翻译完,后面的路你全修过了
scheduler:cron 定时开会话练习 26 的 schedule_wakeup。同一件事的服务化形态:任务落成文件、到点起会话——"到点开一轮"这个机制你已经有了,差的只是把它搬到进程外

看出规律了吗?这三样没有一个引入新的 tool 机制。 它们是同一个 agent 的不同接入方式——换一张皮,骨头一根没换。这本书教骨头,皮是 工程活:值得做,但不值得占一章,因为做它们的时候你不会遇到任何一个 这本书没讲过的概念。这就是"可以以后再要"的确切含义——不是"太难了 以后再说",是你已经具备了随时把它们做出来的全部知识

同理还有两样正文交代过的:bubbletea 全屏界面(练习 24 选了轻量 REPL) 和录制回放(练习 30 加分练习 5)。皮,都是皮。

最后一次运行

好了,仪式时间。让这个 harness 干它这辈子最合适的最后一件事:用 自己的工具,读自己的源码,说清楚自己是什么。

go run . "看看你自己。你的全部源码就是当前目录的 main.go。用 grep 数一数注册表里定义了多少个工具(提示:每个工具都有一个 definition() toolSpec 方法),列出它们的名字,然后用三句话总结:你是什么。"

DeepSeek 跑了 7 轮:ls -la 和 glob 并发圈范围,四五条 grep 从不同 角度数(先数 definition() toolSpec 出现次数,再数 type xxxTool struct,再抓 Name: "..." 交叉验证),最后 read_file 精读注册段。 它的回答值得全文保留:

definition() toolSpec 方法在 main.go 里共出现 20 处,其中 第 67 行是 tool 接口的声明(不算工具实现),所以实际定义了 19 个工具类型。另外两点要注明:mcpTool 是动态模板(当前目录 没有 mcp.json,运行时为 0 个);skillTool 只有发现到 .harness-skills/ 下的 skill 才挂进注册表(当前目录也没有,所以 此刻运行时实际注册的是 17 个)。

三句话总结:

  • 我是一套手写的 agent harness:一个常驻 REPL 把用户的话发给 OpenAI 兼容的 LLM API,把模型点名要的工具调用分发到注册表里执行, 结果喂回去循环到收敛,并配上会话落盘、上下文压缩、跨会话记忆和 skill 按需加载这些配套。
  • 这套 harness 的骨子里是安全纪律:read-before-write、bash 命令 三档权限、OS 级沙箱、覆盖前备份、后台任务、定时唤醒和 goal 预算 追踪——这些横切约束装在注册表这一层,不靠模型自觉。
  • 而"我"就是恰好运行在这套 harness 里的那个 agent:通过 read_file、grep、bash 这些工具在真实文件系统上干活的那双手, 此刻正用它们来读自己的源码。

数对了:20 处减去接口声明,19 种。连"运行时其实只挂了 17 个"这种 我们自己盘点时都容易漏的账,它都从代码里读出来了。第二句里那句 "横切约束装在注册表这一层,不靠模型自觉"——那是练习 6 写在注册表 上方的注释,它读到了,而且用对了。

一个你亲手写的循环,用你亲手注册的工具,读懂了你亲手写下的每一条 纪律。没有比这更合适的毕业演示了。

从这里去哪

三个方向,按远近排:

  1. 后记。 翻过去就是——"一通百通"会告诉你,这套东西离一个客服 agent、一个运维 agent 有多近(剧透:换工具、换 system prompt, 循环一行不改)。
  2. octo。 这本书的每一章都是从它蒸馏的。你现在读它的源码不会再 迷路了——每个包你都写过它的极简版。那些书里"没搬"的部分(事件 订阅、跨源 iframe、录制回放、serve、IM),现在是你的加分练习题库。
  3. 你自己的方向。 把这个 harness 改成你的:换掉工具、重写 base prompt、接上你公司的内部 API。它是你的了——这五千行里没有 一行你没敲过。

前言的第一节引过一句定义:agent 就是模型在循环里根据环境反馈使用 工具。三十一个练习之前,这是你读到的一句话;现在,它是你写完过一遍 的东西。

现代 agent 只有一种架构。你手里的这个,就是。