惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
博客园_首页
博客园 - 三生石上(FineUI控件)
V
Visual Studio Blog
Hugging Face - Blog
Hugging Face - Blog
美团技术团队
小众软件
小众软件
T
Tailwind CSS Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
月光博客
月光博客
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
博客园 - 【当耐特】
Apple Machine Learning Research
Apple Machine Learning Research
罗磊的独立博客
V
V2EX
酷 壳 – CoolShell
酷 壳 – CoolShell
IT之家
IT之家
量子位
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Recent Announcements
Recent Announcements
M
MIT News - Artificial intelligence
阮一峰的网络日志
阮一峰的网络日志
The GitHub Blog
The GitHub Blog

祝融说。

第10章 工作图——从计划到验收的闭环 第11章 客观验收门与结构化审查 第12章 Headless 自主运行 第13章 上下文压缩与持久化管理 第14章 Daemon + Client 架构 第15章 可观测性与调试 第16章 测试策略与行为验证 第17章 文件系统即自我 第18章 三分架构——Tool、Skill、Capability 第1章 自主 AI agent 的困境 第20章 大门开在哪里——自修改系统的信任边界 第21章 事前之图,事后之树 第22章 有用户 vs. 无用户——两种 agent 模式 第23章 第三级验收门——什么时候信任你的 agent 第24章 Agent 应该忘记什么 第25章 第2章 文件系统即自我 第3章 事件驱动与消息模型 AI 辅助编程实战:从入门到团队治理 第八章:高级技能深度解析 第二册:进阶篇 第二章:方法论概览——管理者需要知道的 14 个技能 第二章:需求分析 第二章:准备工作 第九章:实战案例——完整项目拆解 第六章:常见陷阱与应对 第六章:风险控制——常见问题与预案 第六章:项目编排——多功能管理 第七章:全自动构建——从零到部署 第七章:团队培训方案
第19章 从 REPL 到操作系统
祝融 · 2026-07-29 · via 祝融说。

Agent 内核的演进路径,和操作系统的发展惊人地相似。


引子

你的 agent 正在执行一个需要 30 秒的构建命令。你想查看进度,但终端冻结了。你想取消它重来,但连"取消"这个指令都送不进去。因为你只能等——等命令返回,才能继续。

这不是小问题。当你的 agent 从一个单轮问答工具,变成一个执行多步工程任务的系统时,"一个时刻只做一件事"的模型就不够用了。你需要任务取消、需要并发、需要在后台常驻服务。

有意思的是,agent 内核为了解决这些问题所经历的演进路径,和操作系统的发展惊人地相似:从批处理到事件驱动,再到多任务系统。 不是我们在模仿操作系统——是相同的根本需求催生了相似的架构。

REPL 循环——批处理机

REPL 阶段本质上是一个批处理机:你提交一个任务,agent 处理它,返回结果。处理过程中你不能打断、不能插入新任务、不能询问中间状态。你必须等它完成。

这种架构的优点十分突出。因为执行路径完全确定——一轮输入、一轮处理、一轮输出——调试起来极其简单。你把输入打印出来,把输出打印出来,中间发生了什么一目了然。没有并发、没有竞争条件、没有消息乱序。可预测性是 REPL 最大的资产。

但它的缺点也是与生俱来的。一旦 agent 开始执行一个阻塞操作——比如 run_command 执行一个需要 10 秒的构建——在它返回之前,整个系统都是冻结的。 你不能在这 10 秒中间做任何事:不能问"进度如何",不能发"算了我取消"。你只能等。

另一个问题是跨回合的上下文管理。REPL 每个回合都是独立的——这一回合的输出不会自然流入下一回合的结构中,除非显式拼接 system prompt。这就是为什么很多 REPL 架构的 agent"记不住"上一回合的工具调用结果——每次循环都是新的开始。

这跟 MS-DOS 很像:一个时刻只能跑一个程序,没有硬件级的内存保护,没有抢占式调度。程序一跑,系统就冻结。你只能等它结束,或者按电源键。

事件驱动——单任务内核 + 中断

当 REPL 的阻塞问题变得不可忍受时,自然演进方向是事件驱动。内核不再是"你先说,我再做"的串行循环——而是变成一个事件循环,响应不同类型的消息。

CodeCoder 的双通道拓扑是这种思路的体现:

  • 命令通道(cmd_tx) — 只传递用户主动意图:ProcessMessage(新消息)、Shutdown(退出)、Cancel(取消)
  • 事件通道(event_rx) — 返回流式增量和结构化状态:NewToken、ToolStarted、ToolFinished、StatusUpdate

两个通道的分离是关键设计。命令通道的流量极低——只有用户的明确指令。事件通道的流量可以很高——每个 token、每次工具调用状态变更都经过它。把它们混在一起会导致高优先级指令(取消)被低优先级事件(token 流)阻塞。

最显著的事件驱动特性是协作式取消

在 REPL 模式下,取消是不可能的——agent 在执行 run_command,没有轮询任何取消信号的机会。在事件驱动模式下,当用户按下 Ctrl+C,系统不杀线程,而是翻转一个共享的 CancelToken。Agent 的工具执行循环(比如 run_command 的子进程循环)在每次迭代时检查这个 token。如果被翻转,它优雅地终止子进程,返回一个"已取消"状态,然后等待下一轮输入。

这跟操作系统的信号机制一样:内核不强制终止进程(那会导致资源泄漏),它发送一个信号,进程在安全点响应。

另一个事件驱动的好处是 sub-agent 可以存在了。在 REPL 模式下,"一个 agent 调用另一个 agent"意味着外层循环被递归地阻塞——几乎不可能实现。在事件驱动模式下,agent 工具创建一个新的只读 AgentLoop 实例,它在自己的事件通道上运行,父 agent 通过回复通道等待结果。两个 agent 共享同一套事件分发机制,不互相阻塞。

但这个阶段仍然不是多任务。工具是串行执行的——一个 turn 内,工具按顺序排队运行,没有并行。这套串行执行的设计对验收体系有直接影响:验收门只能串行串联而非并行执行,完整讨论见篇 7「第三级验收门」。

操作系统——多任务内核

事件驱动再往前走一步,就是 OS 风格的多任务架构。

CodeCoder 的最终形态是 OS 线程 + channel。这里有一个关键的选择:为什么不用 async runtime(tokio、async-std 之类)?

原因有三。首先,工具调用往往涉及阻塞式系统操作——读文件、跑命令、等子进程。在 async runtime 里,一个阻塞操作会卡住整个事件循环,所以你必须确保每个工具调用都是非阻塞的。这在实践中几乎不可行——不是每个第三方工具都提供 async API。OS 线程允许你在线程里阻塞而不影响其他线程。

其次,sub-agent 需要独立阻塞。如果父 agent 在等子 agent 返回,它应该释放 CPU 给其他线程,而不是卡住整个运行时事件循环。OS 线程天然支持这种模式——一个线程可以阻塞在 channel receive 上,其他线程继续运行。

第三,取消路径更清晰。异步框架的取消通常靠 abort()drop(),但这不能保证子进程的正确清理。OS 线程的取消算是指向共享 CancelToken 的——你明确知道取消时执行了什么清理代码。

当然,这不是说 CodeCoder 真的是一个多任务操作系统。它的 turn 内仍然是串行的——agent 在一个时刻只做一件事。多线程主要体现在:

  • 主 agent 线程处理 turn 的逻辑
  • 工作线程运行为后台任务(比如 headless runner 的里程碑推进)
  • daemon 线程监听 Unix socket、接受客户端连接
  • Compaction 线程周期性地压缩上下文

它更像早期 Unix——多进程,但进程间隔离清晰,每个进程也只做一件事。

对 agent 架构设计者的启示

这三个阶段的区分不是学术分类——它是帮助你判断"我的 agent 现在需要哪种内核"的工具。

阶段适合场景瓶颈迁移信号
REPL单轮问答、短任务无法取消、无法并发遇到需要取消的操作
事件驱动多步任务、交互式工作工具串行执行需要同时处理多个请求
OS 内核常驻服务、后台运行架构复杂度需要常驻 Capability

不需要一开始就设计 OS。 REPL 就够起步,直到你遇到"取消不了正在执行的命令"的问题时才需要事件驱动。直到你遇到"需要在后台跑一个持续服务"的问题时才需要 OS。

关键警告是:不要设计一个"想有 OS 的能力但只有 REPL 的结构"的中间态。 具体表现为:试图用全局变量实现并发、用 sleep 循环实现定时任务、用写临时文件来实现进程间通信。这些不是架构演进——是 hack。如果发现自己在做这些事,说明该迁移到下一阶段了。

最后,代码调用栈和 agent 调用栈之间有一个深层类比,可能帮助判断你在哪个阶段:

  • REPL = 单线程同步函数调用——简单、可预测、易调试
  • 事件驱动 = 回调 + 信号——灵活、可中断、调试困难一点
  • OS = 进程 + 消息传递——强隔离、可组合、最复杂

代价与权衡

从 REPL 到事件驱动到 OS 的每一步演进都伴随着可量化的代价。

第一级代价:调试复杂度。 REPL 循环的调试是最简单的——你只要打印输入和输出,就知道"它的输入是什么,输出了什么"。事件驱动模式下,你还要追踪消息何时到达、是否被阻塞、Token 是否被翻转。OS 级的多线程则引入了竞争条件、死锁、状态同步等调试维度——println 调试不再够用,你需要结构化事件追踪。

第二级代价:确定的承诺。 REPL 的 turn 是完美确定的——每次的输入产生输出,没有其他东西插进来。事件驱动的协作式取消增加了"turn 可以中途中断"的可能性。OS 级的多线程意味着 agent 可以在你不知情的情况下并行处理多个事情——这在提高利用率的同时也意味着"agent 做了什么"不再是可预设的路径。对于需要确定性回放的安全审计场景(比如金融交易 agent),OS 级架构可能比 REPL 更难满足需求。

第三级代价:操作系统不是所有问题的答案。 CodeCoder 的多线程仅限基础设施层(daemon 线程、工作线程、compaction 线程),主 agent 的 turn 内仍然是串行的。这不是偷懒——是有意保持"一个 turn 执行逻辑的确定性"。如果你的 agent 不需要 daemon 化运行、不需要并行 compaction、不需要多客户端同时连接,OS 线程 + channel 架构的复杂性是不必要的。对大多数单用户、单进程的 agent 场景来说,事件驱动就是最终形态。

不要为了技术趣味而过度设计。

下一篇,我们把这些机制的信任边界打开来审视——当一个 agent 能写代码、能跑代码时,大门开在哪里?