两周前我在这里发过 v0.1.0 ( https://www.v2ex.com/t/1223216 ),当时的介绍是"OpenAI Agents SDK 的忠实 Go 移植"。先还个愿:3 楼建议、我在 4 楼答应的"把野路子裸 JSX 换成正经构建",干完了——现在是 Vite + React + TS ,产物照旧 embed 进单二进制,部署还是一个文件。
然后说这两周发生的事。一句话版本:我停止了对 Python 上游的忠实,151 个提交之后,v0.2.0 不再是移植品。
"忠实"是怎么变成陷阱的
移植做到后面,越来越多地方是在用 Go 模仿 Python 的形状:Python 用生命周期钩子,我就造一套钩子接口; Python 的 run 靠异步任务,我就开后台协程加 channel 。能跑,但每一处都别扭——你得记得 cancel context ,得担心协程泄漏,得在文档里解释"这里为什么长得不像 Go"。
压垮我的最后一根稻草是流式接口。Go 1.23 有了 range-over-func ,我试着把整个 run 循环塞进迭代器里:range 就是运行,break 就是停止,没有后台协程,没有需要记得 cancel 的东西。改完那天我意识到,这个形状 Python 永远给不了我——它是这门语言自己长出来的。
于是做了个决定:不再对标上游。写了一份 spec.md 当"宪法",现在 1600 行、61 节,每个行为决定连同理由记录在案,规则只有一条:改行为必须在同一个提交里改 spec 。上游还看,但只记录"采纳/不采纳+为什么",没有跟随义务。
顺着这个思路重想了几件事
会话不再是消息列表,是一棵树。起因很具体:想做"重新生成",发现列表模型下只能删历史。改成 append-only 的树之后,重试是弃一根分支而不是抹掉过去,"看看另一个答案"就成了自然能力,Web UI 里现在是个 2/3 ‹ › 切换器。
上下文管理也顺势变了:压缩不再改写历史,而是追加一个"检查点"条目——被折叠的内容还在,随时能展开看。配合"上下文溢出就压缩重试这一轮",长会话基本不会因为超长直接失败。
还有个私心功能:run 跑着的时候可以"半路插话"——转向当前回合、排队下一轮、或者追加下一个话题,打字的内容不会因为时机不对而丢掉。
两个丢人的故事
一,CI 里 go build ./... 会把可执行文件写在源码旁边,git add -A 一网打尽,一个 18MB 的二进制就这么混进了提交历史。重写历史清掉、加了 .gitignore ,自觉万无一失——几周后另一个 8.6MB 的从仓库根目录又溜进来一次,因为 ignore 规则只挡了子目录 /手动狗头。现在 .git 从 73MB 瘦到 3.3MB ,教训是防呆规则也要防自己。
二,包的门面文档( doc.go )一直写着"tracking Python SDK v0.17.4",还链接着两个早已删除的符号——喊了半个月独立演化,门牌上还挂着旧户口。这暴露了一个盲区:代码有编译器,文档没有。于是写了个 verifydocs 塞进 CI:文档里的代码片段、godoc 链接、markdown 锚点,全部要"过编译"。上线第一天就抓到一个真断链。
关于 AI 协作,说点实话
上个帖子提过,这项目是我和 Claude Code 结对写的。这两周的玩法更极端一些:大的重构让它先做机械化校验(逐函数比对新旧代码,确保"纯移动"承诺是真的),再派独立的审查任务对抗性地找茬——最狠的一轮真找出来一个 context 泄漏,是它自己重构时引入的,又被它自己的审查抓出来。我的角色越来越像是"定宪法的人":spec 写清楚行为和理由,人和 AI 都照着办。这套流程好不好,欢迎拍砖,我自己是觉得比"AI 一把梭然后人肉收拾"体面多了。
其他这两周落地的:后台任务(子代理干活、完成后自动通知主会话)、沙箱的 apply_patch 编辑/持久 shell/命令策略、MCP 双向(既能接别人的,也能把自己的 Agent 暴露出去)、OpenTelemetry 导出、以及 Web 端的交互式沙箱终端。
repo: https://github.com/zzir/agents-go
还是那句话:欢迎试用、欢迎拍砖。
