从零构建 Coding Agent
English

06. Agent 内核与生命周期

到目前为止,你已经有了消息协议、provider、工具和流式事件。下一步是把它们组织成 Agent 内核。内核不是产品界面,也不是某个 provider 的包装器。它的职责是维护运行状态、推进 turn、执行工具、发出事件,并把可定制点暴露给上层。

四个职责边界

一个清晰的 Agent 内核通常分成四块:

  • State:当前消息、工具、模型、配置、队列和运行状态。
  • Runner:根据 stop reason 推进 turn。
  • Tool executor:校验、调度、执行工具,形成 tool result。
  • Event bus:把生命周期事件广播给 UI、日志、扩展和测试。

不要让 UI 直接改 messages,也不要让工具直接调用 provider。每一层都通过明确契约交互。这样你才能在未来增加 JSON 模式、SDK、扩展系统或测试 harness,而不是重写核心 loop。

生命周期事件

内核应该发出稳定的生命周期事件:

agent_start
turn_start
model_request_start
model_response
tool_batch_start
tool_execution_start
tool_execution_end
tool_batch_end
turn_end
agent_idle

这些事件不是为了好看。它们支撑几类能力:

  • UI 显示当前状态。
  • 会话日志准确记录发生顺序。
  • 扩展在 tool call 前做权限检查。
  • 测试断言一次任务的执行轨迹。
  • 统计系统计算耗时和 token 成本。

如果你没有统一事件流,这些能力会各自发明一套状态,系统很快失去一致性。

钩子面,而不是继承树

Agent 很容易被需求推向复杂继承:安全 Agent、测试 Agent、带压缩 Agent、带扩展 Agent。更稳妥的方式是暴露钩子:

type AgentHooks = {
  beforeModelRequest?: (context: Message[]) => Promise<Message[]>;
  afterModelResponse?: (message: AssistantMessage) => Promise<AssistantMessage>;
  beforeToolCall?: (call: ToolCallBlock) => Promise<ToolCallBlock>;
  afterToolResult?: (result: ToolResultMessage) => Promise<ToolResultMessage>;
  shouldStop?: (state: AgentState) => Promise<boolean>;
};

钩子让产品层能注入系统提示词、权限确认、压缩、统计和扩展逻辑,而不需要改内核控制流。注意钩子必须有明确时机和错误语义。比如 beforeToolCall 抛错时,是阻止工具执行并形成错误 tool result,还是终止整个任务?这要在内核层定义清楚。

并行工具与顺序事实

很多模型会在一个 assistant turn 里请求多个工具。只读工具可以并行执行,写工具通常需要按文件串行。无论执行是否并行,会话事实都要保持可解释。建议按 assistant 消息里的 tool call 顺序写入 tool result,或者至少在日志里保存 tool call id 和完成顺序。

并行执行带来的真正问题是文件写冲突。两个工具同时读取旧文件,各自计算修改,最后一个写入覆盖前一个。这不是模型问题,而是工具运行时缺少同一文件的写队列。后面的 coding 工具章会专门处理。

内核状态

内核状态不要塞满产品信息。它只需要维护推进 loop 所需的数据:

  • messages。
  • active model。
  • active tools。
  • isRunning。
  • queued steering 和 follow-up。
  • abort controller。
  • turn counters。

会话名称、主题色、最近命令历史等属于产品层。把它们放进内核会让 SDK、CLI 和 TUI 互相污染。

运行观察

一个用户请求从开始到空闲,事件可能是:

agent_start
turn_start
model_request_start
model_response stopReason=toolUse
tool_batch_start count=1
tool_execution_start read
tool_execution_end read
tool_batch_end
turn_end
turn_start
model_request_start
model_response stopReason=stop
turn_end
agent_idle

这条序列能同时驱动终端 spinner、日志写入、扩展权限门和测试断言。稳定事件序列是 Agent 产品化的地基。

练习

把当前 loop 包成 Agent 类或工厂函数。

验收标准:

  • 外部只能通过 promptabortsubscribe 和配置方法驱动内核。
  • 事件订阅者不能直接修改内部 messages。
  • 一个 faux provider 测试能断言完整生命周期事件序列。
  • 未知工具、工具错误和用户 abort 都会发出明确事件。