Agent 管理层中文翻译版Agent Management Layer Module Overview
源文件:agent.ts(543 行)+ types.ts(389 行)→ 提炼为中文模块结构 | 已优化排版,支持 Ctrl+P 直接打印 / 保存 PDF
Source files: agent.ts (543 lines) + types.ts (389 lines) → distilled into module structure | print-optimized, Ctrl+P to print / save PDF
定位:agent.ts 是"整车"——把引擎(agent-loop.ts,Agent Loop 章节已精读)、上下文系统、工具系统等零件组装起来,对外提供"发指令、收通知"的操作面板。types.ts 是"合同书"——定义了所有零件必须长什么样。
Role: agent.ts is the "whole vehicle" -- it assembles the engine (agent-loop.ts, covered in the Agent Loop chapter), the context system, the tool system, and other parts, exposing an interface for "sending commands, receiving notifications." types.ts is the "contract" -- defining the shape every part must conform to.
和 Agent Loop 章节的关系:agent-loop.ts = 引擎(干活的循环),agent.ts = 管理层(配置 + 启动 + 协调 + 善后)。管理层把配置打包交给引擎,引擎把进度通知回报给管理层。
Relationship to Agent Loop chapter: agent-loop.ts = the engine (the working loop), agent.ts = the management layer (configuration + startup + coordination + cleanup). The management layer packages the config and hands it to the engine; the engine reports progress notifications back to the management layer.
全局定位图Global Position Map
agent.ts 在整个架构里的位置Where agent.ts sits in the architecture
知识全景地图里的 ① 核心循环,展开看其实分两层。外层是 agent.ts(管理层),内层是 agent-loop.ts(引擎,上篇已读)。
The ① Core Loop in the knowledge map actually splits into two layers when expanded. The outer layer is agent.ts (management layer), and the inner layer is agent-loop.ts (engine, covered in Part A).
外部世界(UI / 产品代码 / 持久化层)
│ 发指令 ▲ 收通知
▼ │
┌──────────────────────────────────────────────────────┐
│ agent.ts 管理层 │
│ │
│ ┌ A 配置层 ─────────────────────┐ │
│ │ 对话记录 / 工具清单 / 转换函数 │──→ ② 上下文工程 │
│ │ 区块 1, 2, 3 │──→ ③ 工具系统 │
│ └───────────────────────────────┘ │
│ │
│ ┌ B 控制层 ─────────────────────┐ │
│ │ 启动 / 停止 / 插话 / 等待 │←──→ 外部世界 │
│ │ 区块 4, 6, 7 │ │
│ └──────────────┬────────────────┘ │
│ │ 打包配置交给引擎 │
│ ┌ C 引擎对接 ──┼────────────────┐ │
│ │ ▼ │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ agent-loop.ts 引擎 │ │ │
│ │ │ 调 LLM → 工具 → 喂回 │ │ │
│ │ │ (上篇已精读) │ │ │
│ │ └────────────────────────┘ │ │
│ │ 区块 5, 8 │ │
│ └──────────────┬────────────────┘ │
│ │ 引擎发进度通知 │
│ ┌ D 事件处理 ──┼────────────────┐ │
│ │ ▼ │ │
│ │ 更新内部状态 → 转发给订阅者 │──→ 外部订阅者 │
│ │ 区块 9 │──→ ⑨ 持久化 │
│ └───────────────────────────────┘ (Session Log) │
│ │
└──────────────────────────────────────────────────────┘
│
▼
LLM API (Claude)
External World (UI / Product Code / Persistence)
│ Send commands ▲ Receive notifications
▼ │
┌──────────────────────────────────────────────────────────┐
│ agent.ts Management Layer │
│ │
│ ┌ A Config Layer ───────────────────┐ │
│ │ Conversation history / Tool list │──→ ② Context Eng │
│ │ / Transform functions │──→ ③ Tool System │
│ │ Block 1, 2, 3 │ │
│ └───────────────────────────────────┘ │
│ │
│ ┌ B Control Layer ──────────────────┐ │
│ │ Start / Stop / Steer / Wait │←──→ External World│
│ │ Block 4, 6, 7 │ │
│ └──────────────┬────────────────────┘ │
│ │ Package config for engine │
│ ┌ C Engine Interface ──┼────────────┐ │
│ │ ▼ │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ agent-loop.ts Engine │ │ │
│ │ │ Call LLM → Tools → Feed │ │ │
│ │ │ (covered in Part A) │ │ │
│ │ └────────────────────────────┘ │ │
│ │ Block 5, 8 │ │
│ └──────────────┬────────────────────┘ │
│ │ Engine sends progress notifications │
│ ┌ D Event Handling ──┼──────────────┐ │
│ │ ▼ │ │
│ │ Update internal state → Forward │──→ External │
│ │ to subscribers │ Subscribers │
│ │ Block 9 │──→ ⑨ Persistence │
│ └───────────────────────────────────┘ (Session Log) │
│ │
└──────────────────────────────────────────────────────────┘
│
▼
LLM API (Claude)
注意箭头含义:──→ ② 上下文工程、──→ ③ 工具系统 表示"连接到 / 持有引用",不是"包含"。agent.ts 像项目经理——手上有通讯录,自己不干活,把引用打包交给引擎去调用。
Note on arrows: ──→ ② Context Eng, ──→ ③ Tool System means "connects to / holds a reference," not "contains." agent.ts is like a project manager -- it has a contact list, doesn't do the work itself, but packages references and hands them to the engine.
A 配置层A Config Layer
B 控制层B Control Layer
C 引擎对接C Engine Interface
D 事件处理D Event Handling
全文结构地图(9 个区块,角色对应上图 A-D)Full Structure Map (9 blocks, roles correspond to A-D above)
┌─ 区块 1 ────────────────────────── A 配置层 ────── [快读] ──┐
│ 消息筛选函数 + 默认值 │
└──────────────────────────────────────────────────────────────┘
↓
┌─ 区块 2 ────────────────────────── A 配置层 ────── [快读] ──┐
│ Agent 内部状态的保护机制 │
└──────────────────────────────────────────────────────────────┘
↓
┌─ 区块 3 ────────────────────────── A 配置层 ────── [快读] ──┐
│ 出厂配置清单 │
└──────────────────────────────────────────────────────────────┘
↓
┌─ 区块 4 ────────────────────────── B 控制层 ────── [精读] ──┐
│ 消息排队桶 — "运行中插话"的实现 │
└──────────────────────────────────────────────────────────────┘
↓
┌─ 区块 5 ────────────────────────── C 引擎对接 ──── [精读] ──┐
│ Agent 的零件清单 + 出厂组装 │
└──────────────────────────────────────────────────────────────┘
↓
┌─ 区块 6 ────────────────────────── B 控制层 ────── [精读] ──┐
│ 对外操作面板 — 订阅/插话/取消/等待/重置 │
└──────────────────────────────────────────────────────────────┘
↓
┌─ 区块 7 ────────────────────────── B 控制层 ────── [精读] ──┐
│ 两种启动方式 — 发新消息 vs 从断点恢复 │
└──────────────────────────────────────────────────────────────┘
↓
┌─ 区块 8 ────────────────────────── C 引擎对接 ──── [精读] ──┐
│ 启动前准备 + 引擎对接 + 收工清理 │
└──────────────────────────────────────────────────────────────┘
↓
┌─ 区块 9 ────────────────────────── D 事件处理 ──── [精读] ──┐
│ 收到引擎通知后怎么处理 │
└──────────────────────────────────────────────────────────────┘
┌─ Block 1 ──────────────────────── A Config Layer ── [Skim] ──┐
│ Message filter functions + defaults │
└───────────────────────────────────────────────────────────────┘
↓
┌─ Block 2 ──────────────────────── A Config Layer ── [Skim] ──┐
│ Agent internal state protection mechanism │
└───────────────────────────────────────────────────────────────┘
↓
┌─ Block 3 ──────────────────────── A Config Layer ── [Skim] ──┐
│ Factory default configuration │
└───────────────────────────────────────────────────────────────┘
↓
┌─ Block 4 ──────────────────────── B Control Layer ─ [Deep] ──┐
│ Message queue -- implementing "mid-run steering" │
└───────────────────────────────────────────────────────────────┘
↓
┌─ Block 5 ──────────────────────── C Engine Intf ─── [Deep] ──┐
│ Agent's parts list + factory assembly │
└───────────────────────────────────────────────────────────────┘
↓
┌─ Block 6 ──────────────────────── B Control Layer ─ [Deep] ──┐
│ Public control panel -- subscribe/steer/abort/wait/reset │
└───────────────────────────────────────────────────────────────┘
↓
┌─ Block 7 ──────────────────────── B Control Layer ─ [Deep] ──┐
│ Two startup modes -- new message vs resume from checkpoint │
└───────────────────────────────────────────────────────────────┘
↓
┌─ Block 8 ──────────────────────── C Engine Intf ─── [Deep] ──┐
│ Pre-launch prep + engine interface + post-run cleanup │
└───────────────────────────────────────────────────────────────┘
↓
┌─ Block 9 ──────────────────────── D Event Handling ─ [Deep] ─┐
│ How engine notifications are processed │
└───────────────────────────────────────────────────────────────┘
一图总结:agent.ts 做了什么Visual Summary: What agent.ts Does
你(外部调用方) Agent 类(管理层) 引擎(agent-loop.ts)
────────────── ────────────── ──────────────────
agent.prompt("你好") ────→ 门卫检查 → 复印对话记录
打包配置 → 交给引擎 ─────────→ 开始跑循环
│
agent.steer("改方向") ──→ 塞进方向盘桶 引擎来取 →
│
收到通知 ←── 进度通知 ────────────┘
├─ 更新记事本
└─ 转发给订阅者 ──→ UI 等外部代码
│
收工清理 ←── 循环结束 ────────────┘
agent.abort() ──→ 发取消信号 ──→ 引擎停下来
agent.waitForIdle() ──→ 等到收工清理完成
You (external caller) Agent class (Mgmt Layer) Engine (agent-loop.ts)
───────────────────── ──────────────────── ──────────────────
agent.prompt("hello") ────→ Guard check → Copy conversation
Package config → Hand to engine ──→ Start loop
│
agent.steer("change") ───→ Push into steering queue Engine pulls →
│
Receive notification ←── Progress ─────┘
├─ Update internal state
└─ Forward to subscribers ──→ UI & external code
│
Post-run cleanup ←── Loop ends ────────┘
agent.abort() ──→ Send cancel signal ──→ Engine stops
agent.waitForIdle() ──→ Wait until cleanup completes
一句话:Agent 类是引擎和产品之间的管理层。它管四件事——包装引擎、运行中插话、进度通知、启停善后。引擎只管跑循环。
In one line: The Agent class is the management layer between the engine and the product. It handles four things -- wrapping the engine, mid-run steering, progress notifications, and start/stop lifecycle. The engine only runs the loop.
一、两个文件的模块结构(含区块 1-3 配置层速览)I. Module Structure of Both Files (incl. Block 1-3 Config Layer Overview)
文件:File: agent.ts
┌─ 导入声明(跳过)┌─ Imports (skip)
│
├─ A 配置层(区块 1-3)├─ A Config Layer (Block 1-3)
│ ├─ defaultConvertToLlm() — 默认的消息筛选函数:只保留 user/assistant/toolResult— Default message filter: keeps only user/assistant/toolResult
│ ├─ EMPTY_USAGE / DEFAULT_MODEL — 默认值常量— Default value constants
│ ├─ createMutableAgentState() — 创建受保护的内部状态对象— Creates a protected internal state object
│ └─ AgentOptions — 创建 Agent 时的配置清单(20 个字段)— Config checklist for creating an Agent (20 fields)
│
├─ B 控制层(区块 4, 6, 7)├─ B Control Layer (Block 4, 6, 7)
│ ├─ PendingMessageQueue — 消息排队桶:运行中插话/追加任务的队列— Message queue: queued messages for mid-run steering/follow-up tasks
│ ├─ subscribe() / state / steer() / followUp() / abort() / reset() — 操作面板— Control panel
│ └─ prompt() / continue() — 两种启动方式— Two startup modes
│
├─ C 引擎对接(区块 5, 8)├─ C Engine Interface (Block 5, 8)
│ ├─ Agent 构造函数Agent constructor — 把配置清单安装到零件上— Installs the config checklist onto parts
│ ├─ ⭐ createLoopConfig() — 把零件打包成引擎需要的格式— Packages parts into the format the engine expects
│ ├─ runWithLifecycle() — 启动前准备 + 调引擎 + 收工清理— Pre-launch prep + call engine + post-run cleanup
│ └─ handleRunFailure() / finishRun() — 异常处理和善后— Error handling and cleanup
│
└─ D 事件处理(区块 9)└─ D Event Handling (Block 9)
└─ ⭐ processEvents() — 接收引擎通知 → 更新内部状态 → 转发给订阅者— Receive engine notifications → update internal state → forward to subscribers
文件:File: types.ts
┌─ StreamFn — 调 LLM 的函数长什么样(输入什么、输出什么)— What the LLM-calling function looks like (inputs & outputs)
├─ AgentLoopConfig — 引擎的配置包(管理层打包、引擎拆包的那份"快递")— Engine config package (packed by management layer, unpacked by engine)
├─ AgentTool — 一个工具必须有:名字、参数格式、execute 执行函数— A tool must have: name, parameter schema, execute function
├─ AgentEvent — 引擎发给管理层的 12 种通知(开始/结束/消息/工具执行……)— 12 notification types from engine to management layer (start/end/message/tool execution...)
├─ AgentMessage — 对话消息 = LLM 标准消息 + 自定义消息(可扩展)— Conversation message = standard LLM message + custom messages (extensible)
├─ AgentState — Agent 的公开状态:提示词、模型、工具列表、对话记录、是否运行中— Agent's public state: prompt, model, tool list, conversation history, running status
├─ AgentContext — 发给引擎的上下文快照:提示词 + 消息 + 工具— Context snapshot sent to engine: prompt + messages + tools
├─ BeforeToolCallContext / AfterToolCallContext — 工具执行前/后钩子收到的信息— Info received by before/after tool call hooks
└─ BeforeToolCallResult / AfterToolCallResult — 钩子返回的指令(阻止/修改/终止)— Instructions returned by hooks (block/modify/terminate)
二、区块 4:PendingMessageQueue——运行中插话的实现II. Block 4: PendingMessageQueue -- Implementing Mid-Run Steering
概念:什么是"运行中插话"?Concept: What is "mid-run steering"?
Agent 正在工作(循环还没结束),外部想插一句话进去。比如用户发了新指令、或者产品代码想追加一个任务。这些消息不能直接塞进正在运行的循环,而是先排队等候,等引擎每一轮(turn)结束后再注入。
The agent is working (loop hasn't finished), and the outside world wants to inject a message. For example, the user sends a new instruction, or product code wants to add a task. These messages can't be injected directly into the running loop -- they queue up first, and get injected after each turn ends.
类比:餐厅厨师正在做菜(agent 在跑循环),服务员不能冲进厨房把新订单扔锅里(直接改上下文),而是贴在窗口的订单架上(队列),厨师做完一道菜才看下一张订单。
Analogy: A restaurant chef is cooking (agent running the loop). The waiter can't rush into the kitchen and toss a new order into the pan (modify context directly), but instead pins it on the order rack by the window (queue). The chef checks the next order only after finishing the current dish.
类 PendingMessageQueueClass PendingMessageQueue(消息排队桶Message Queue)[通用 Agent 模式][Universal Agent Pattern]
messages 一个列表,存放等待中的消息A list storing pending messages
mode 出队模式:"all" = 一次全部取出 / "one-at-a-time" = 每次只取一个Drain mode: "all" = take all at once / "one-at-a-time" = take one per drain
├─ enqueue(消息message) 放一条消息进队列尾部Push a message to the end of the queue
├─ hasItems() 队列里还有消息吗?Are there any messages left in the queue?
├─ drain() 取出消息(根据 mode 决定取几个),取完后从队列删除Take messages out (count depends on mode), remove from queue after taking
│ mode = "all" → 全部取出,清空队列Take all, clear queue
│ mode = "one-at-a-time" → 只取第一个,剩余留着Take only the first, keep the rest
└─ clear() 清空所有排队消息(不执行,直接丢弃)Clear all queued messages (discard without executing)
Agent 里有两个这样的队列The Agent has two such queues
- steeringQueue(插队队列):引擎每轮结束后立刻检查,有消息就注入下一轮。用于"运行中改方向"。
- steeringQueue (steering queue): Checked immediately after each turn ends. If messages exist, they're injected into the next turn. Used for "changing direction mid-run."
- followUpQueue(追加队列):只有当引擎准备停下来时才检查(没有工具调用了、也没有插队消息了)。用于"完成当前任务后接着做下一个"。
- followUpQueue (follow-up queue): Only checked when the engine is about to stop (no tool calls left, no steering messages). Used for "do the next task after finishing the current one."
这两个队列是 Agent Loop 章节中 runLoop 的 ❶ 注入插队消息和 ❼ 追加任务的数据来源。
These two queues are the data source for ❶ inject steering messages and ❼ follow-up tasks in the runLoop from the Agent Loop chapter.
三、区块 5:Agent 构造函数——出厂组装III. Block 5: Agent Constructor -- Factory Assembly
类 Agent 的构造函数Agent class constructor(把配置清单安装到零件上Install config checklist onto parts)
(外部传入一份配置,每个字段都有默认值兜底Config passed in from outside; every field has a default fallback)
├─ ❶ 创建内部状态Create internal state
│ 调用 createMutableAgentState(),初始化:提示词、模型、工具列表、对话记录Call createMutableAgentState() to initialize: prompt, model, tool list, conversation history
│ 状态有保护机制:外部赋值 tools/messages 时自动复制一份,防止被外部篡改State has protection: auto-copies tools/messages on external assignment, preventing external tampering
│
├─ ❷ 安装核心引用Install core references [关键:这些是"引用"不是"实现"][Key: these are "references" not "implementations"]
│ convertToLlm 消息格式转换函数(默认:只保留 user/assistant/toolResult 三种)Message format converter (default: keep only user/assistant/toolResult)
│ transformContext 上下文压缩函数(默认:无,即不压缩)Context compression function (default: none, no compression)
│ streamFn 调 LLM 的函数(默认:streamSimple,直接调 API)LLM calling function (default: streamSimple, direct API call)
│ beforeToolCall 工具执行前的权限钩子(默认:无,即全部放行)Pre-tool-call permission hook (default: none, allow all)
│ afterToolCall 工具执行后的修改钩子(默认:无,即原样返回)Post-tool-call modification hook (default: none, return as-is)
│
├─ ❸ 创建两个排队桶Create two message queues
│ steeringQueue(插队队列,默认 one-at-a-timesteering queue, default one-at-a-time)
│ followUpQueue(追加队列,默认 one-at-a-timefollow-up queue, default one-at-a-time)
│
└─ ❹ 安装其他配置Install other config
sessionId / thinkingBudgets / transport / maxRetryDelayMs / toolExecution
这些是传给 LLM API 的参数,agent 自己不用These are parameters passed to the LLM API; the agent itself doesn't use them
类型合同:AgentOptions(types.ts 没有定义,在 agent.ts 第 94 行)Type Contract: AgentOptions (not in types.ts, defined at agent.ts line 94)
这份配置清单有 20 个字段,全部可选(都有默认值兜底)。最重要的 5 个是 ❷ 里的核心引用。你只要改这 5 个引用,就能把同一个引擎变成完全不同的产品——不同的 LLM、不同的压缩策略、不同的权限规则。
This config checklist has 20 fields, all optional (every one has a default fallback). The 5 most important are the core references in ❷. By changing just these 5 references, you can turn the same engine into a completely different product -- different LLM, different compression strategy, different permission rules.
四、区块 6:操作面板——外部怎么控制 AgentIV. Block 6: Control Panel -- How the Outside World Controls the Agent
Agent 的对外操作面板Agent's Public Control Panel [通用 Agent 模式][Universal Agent Pattern]
├─ state 读取当前状态(提示词、模型、工具列表、对话记录、是否运行中……)Read current state (prompt, model, tool list, conversation history, running status...)
│
├─ subscribe(监听函数listener) 注册一个监听者,每次有事件就通知它Register a listener that gets notified on every event
│ 返回一个"取消订阅"函数,调用后不再收通知Returns an "unsubscribe" function; call it to stop receiving notifications
│ 类比:关注了一个公众号,取关就不推送了Analogy: Following a newsletter -- unfollow to stop getting updates
│
├─ steer(消息message) 插队:当前轮结束后注入这条消息Steer: inject this message after the current turn ends
├─ followUp(消息message) 追加:agent 准备停下来时才注入这条消息Follow-up: inject this message only when the agent is about to stop
├─ abort() 紧急停止:按下取消按钮,引擎会在安全点停下来Emergency stop: press the cancel button; engine stops at a safe point
│
├─ clearAllQueues() 清空所有排队消息Clear all queued messages
├─ reset() 完全重置:清空对话记录、队列、运行状态(相当于"重启")Full reset: clear conversation history, queues, run state (equivalent to "reboot")
│
└─ waitForIdle() 等到 agent 完全闲下来(引擎停了 + 所有通知也发完了)Wait until the agent is fully idle (engine stopped + all notifications delivered)
五、区块 7:prompt() 和 continue()——两种启动方式V. Block 7: prompt() and continue() -- Two Startup Modes
函数 prompt(输入)Function prompt(input)(发新指令Send a new command)[通用 Agent 模式][Universal Agent Pattern]
├─ ❶ 防重入检查Re-entry guard
│ 如果 agent 已经在跑 → 报错(不能同时跑两个任务)If agent is already running → throw error (can't run two tasks at once)
│ 类比:厨师已经在炒菜了,不能再开一口锅(要插话用 steer)Analogy: The chef is already cooking -- can't start another pan (use steer to interject)
│
├─ ❷ 统一输入格式Normalize input format
│ 支持三种输入方式:字符串 / 单条消息 / 消息列表Supports three input types: string / single message / message array
│ 统一转成消息列表 → 传给下一步Normalize to message array → pass to next step
│
└─ ❸ 启动引擎Start engine
调 runPromptMessages() → 内部调 runWithLifecycle() → 最终调引擎的 runAgentLoop()Call runPromptMessages() → internally calls runWithLifecycle() → ultimately calls the engine's runAgentLoop()
(runAgentLoop 就是 Agent Loop 章节中的入口函数,进入核心循环)(runAgentLoop is the entry function from the Agent Loop chapter, entering the core loop)
函数 continue()Function continue()(接着上次继续Resume from where we left off)[通用 Agent 模式][Universal Agent Pattern]
├─ ❶ 防重入检查Re-entry guard (同上same as above)
│
├─ ❷ 检查最后一条消息Check last message
│ 如果最后是 assistant 消息(AI 的回复):If the last message is an assistant message (AI's reply):
│ 先尝试取插队消息 → 有则作为新 prompt 启动 First try to drain steering messages → if any, start as new prompt
│ 再尝试取追加消息 → 有则作为新 prompt 启动 Then try follow-up messages → if any, start as new prompt
│ 都没有 → 报错(没有新内容可继续) None found → throw error (nothing to continue with)
│
└─ ❸ 继续引擎Resume engine
如果最后是 user/toolResult → 调 runAgentLoopContinue()(不发新消息,从上次断点继续循环)If last message is user/toolResult → call runAgentLoopContinue() (no new message, resume loop from checkpoint)
prompt vs continue 的区别prompt vs continue: the difference
- prompt:带着新消息启动一次全新的循环。等于给厨师一张新订单。
- prompt: Starts a fresh loop with a new message. Like giving the chef a new order.
- continue:不带新消息,从上次的对话记录接着跑。等于告诉厨师"刚才那道菜接着做"。
- continue: No new message; resumes from the existing conversation history. Like telling the chef "keep working on that dish."
- 两者最终都进入同一个引擎循环,区别只在"有没有新消息加进去"。
- Both ultimately enter the same engine loop; the only difference is whether a new message is added.
六、区块 8a:createLoopConfig()——给引擎打包配置VI. Block 8a: createLoopConfig() -- Packaging Config for the Engine
函数 createLoopConfig()Function createLoopConfig()(把管理层的零件打包成引擎需要的格式Package management layer parts into the format the engine expects)
(引擎只认这个格式,管理层必须"翻译"成它要的样子The engine only accepts this format; the management layer must "translate" into it)
├─ ❶ 直接传递的引用Direct pass-through references
│ model / convertToLlm / transformContext / beforeToolCall / afterToolCall
│ sessionId / transport / thinkingBudgets / toolExecution ……
│
├─ ❷ thinking 级别翻译Thinking level translation
│ thinkingLevel 是人类友好的名字(off / low / high……)thinkingLevel is a human-friendly name (off / low / high...)
│ 引擎的 reasoning 字段需要不同格式 → 翻译:off 变 undefined,其他原样Engine's reasoning field needs a different format → translate: off becomes undefined, others pass through
│
├─ ❸ 包装 getSteeringMessagesWrap getSteeringMessages [Pi 特有机制][Pi-Specific]
│ 引擎每轮结束后调这个函数问"有插队消息吗?"Engine calls this after each turn to ask "any steering messages?"
│ 管理层把 steeringQueue.drain() 包装成这个函数Management layer wraps steeringQueue.drain() into this function
│ 特殊处理:如果是 continue() 带着插队消息启动的,第一次不取(避免重复)Special case: if started via continue() with steering messages, skip first drain (avoid duplicates)
│
└─ ❹ 包装 getFollowUpMessagesWrap getFollowUpMessages
引擎准备停下时调这个函数问"还有追加任务吗?"Engine calls this when about to stop: "any follow-up tasks?"
管理层把 followUpQueue.drain() 包装成这个函数Management layer wraps followUpQueue.drain() into this function
类型合同:AgentLoopConfig(types.ts 第 115-248 行)Type Contract: AgentLoopConfig (types.ts lines 115-248)
这是引擎和管理层之间的"合同"。里面规定了引擎需要的所有回调函数:
This is the "contract" between the engine and the management layer. It specifies all the callback functions the engine needs:
- convertToLlm:把 AgentMessage 翻译成 LLM 能读的 Message(必须提供)
- convertToLlm: Translate AgentMessage into LLM-readable Message (required)
- transformContext:压缩/裁剪上下文(可选)
- transformContext: Compress/trim context (optional)
- getApiKey:动态获取 API 密钥(可选,用于 OAuth 场景)
- getApiKey: Dynamically fetch API key (optional, for OAuth scenarios)
- shouldStopAfterTurn:每轮结束后问"要停吗?"(可选,安全阀)
- shouldStopAfterTurn: Ask "should we stop?" after each turn (optional, safety valve)
- getSteeringMessages / getFollowUpMessages:取排队消息(可选)
- getSteeringMessages / getFollowUpMessages: Drain queued messages (optional)
- beforeToolCall / afterToolCall:工具执行的前后钩子(可选)
- beforeToolCall / afterToolCall: Pre/post tool execution hooks (optional)
所有回调都有一个规矩:不准抛异常。出错了必须返回安全的默认值。因为引擎内部没有 try-catch 来接回调的错误——回调炸了,引擎也炸了。
All callbacks share one rule: never throw exceptions. On error, they must return a safe default. The engine has no try-catch for callback errors -- if a callback crashes, the engine crashes too.
七、区块 8b:runWithLifecycle()——启动、运行、善后VII. Block 8b: runWithLifecycle() -- Start, Run, Cleanup
函数 runWithLifecycle(执行器)Function runWithLifecycle(executor)(管理整个运行的生命周期Manage the entire run lifecycle)[通用 Agent 模式][Universal Agent Pattern]
executor 一个函数,里面调引擎(runAgentLoop 或 runAgentLoopContinue)A function that calls the engine (runAgentLoop or runAgentLoopContinue)
├─ ❶ 防重入Re-entry guard 如果已有活跃运行 → 报错If an active run exists → throw error
│
├─ ❷ 创建运行上下文Create run context
│ AbortController 取消按钮(abort() 方法按的就是它)Cancel button (this is what abort() presses)
│ Promise 等待对象(waitForIdle() 等的就是它)Wait object (this is what waitForIdle() waits on)
│ 打包成 activeRun,挂在 Agent 上Packaged as activeRun, attached to the Agent
│
├─ ❸ 标记"运行中"Mark as "running"
│ isStreaming = true,清空旧的流式消息和错误信息isStreaming = true, clear old streaming messages and errors
│
├─ ❹ 执行引擎Execute engine
│ 调用 executor(signal),等引擎跑完Call executor(signal), wait for engine to finish
│ 如果出错 → 调 handleRunFailure() 处理异常If error → call handleRunFailure() to handle the exception
│
└─ ❺ 收工清理Post-run cleanup
isStreaming = false
清空 streamingMessage、pendingToolCallsClear streamingMessage, pendingToolCalls
通知 waitForIdle() 的等待者"我闲了"Notify waitForIdle() waiters: "I'm idle now"
删除 activeRun(允许下一次 prompt)Delete activeRun (allow next prompt call)
"启动前-运行-善后"是通用模式"Pre-launch, Run, Cleanup" is a universal pattern
几乎所有需要"独占运行"的系统都有这个结构。比如:洗衣机(上锁 → 洗 → 解锁)、数据库事务(开启 → 操作 → 提交/回滚)。Agent 也一样:标记运行中 → 调引擎 → 收工清理。善后步骤放在 finally 里,保证即使出错也一定执行。
Nearly every system that requires "exclusive access" follows this structure. Examples: a washing machine (lock → wash → unlock), a database transaction (begin → operate → commit/rollback). The Agent is the same: mark running → call engine → cleanup. The cleanup step lives in a finally block, guaranteeing it runs even on errors.
八、区块 9:processEvents()——事件调度中心VIII. Block 9: processEvents() -- Event Dispatch Center
函数 processEvents(事件)Function processEvents(event)(接收引擎通知 → 更新状态 → 转发订阅者Receive engine notification → update state → forward to subscribers)[通用 Agent 模式][Universal Agent Pattern]
event 引擎发来的一个事件(12 种之一,见下方合同)An event from the engine (one of 12 types, see contract below)
├─ 如果If message_start
│ → 记住"正在生成的消息"(streamingMessage = 这条消息)Remember "message being generated" (streamingMessage = this message)
│
├─ 如果If message_update
│ → 更新"正在生成的消息"(AI 还在打字,内容变长了)Update "message being generated" (AI is still typing, content growing)
│
├─ 如果If message_end
│ → 消息生成完毕:清空 streamingMessage,加入对话记录(messages.push)Message complete: clear streamingMessage, add to conversation history (messages.push)
│
├─ 如果If tool_execution_start
│ → 把这个工具 ID 加入"正在执行的工具"集合Add this tool ID to the "currently executing tools" set
│
├─ 如果If tool_execution_end
│ → 把这个工具 ID 从集合中移除Remove this tool ID from the set
│
├─ 如果If turn_end
│ 如果这一轮 AI 回复带有错误信息 → 记录 errorMessageIf this turn's AI reply contains an error → record errorMessage
│
├─ 如果If agent_end
│ → 清空 streamingMessage(运行结束了,没有正在生成的消息了)Clear streamingMessage (run is over, no message being generated)
│
└─ 逐个通知订阅者Notify subscribers one by one
遍历所有通过 subscribe() 注册的监听函数Iterate through all listener functions registered via subscribe()
按注册顺序依次调用,每个都等它完成再调下一个Call in registration order, wait for each to complete before calling the next
(Session JSONL 写入器就是其中一个订阅者——它把事件写成文件存到磁盘)(The Session JSONL writer is one such subscriber -- it writes events to disk as file records)
类型合同:AgentEvent(types.ts 第 374-389 行)Type Contract: AgentEvent (types.ts lines 374-389)
引擎能发出的所有事件(12 种):
All event types the engine can emit (12 total):
| 事件Event | 含义Meaning | 对应你的体验What you experience |
| agent_start | 整个运行开始Entire run begins | 你发了一条消息,Claude 开始工作You sent a message, Claude starts working |
| agent_end | 整个运行结束Entire run ends | Claude 回复完了,光标停了Claude finished replying, cursor stops |
| turn_start | 一轮开始A turn begins | Claude 开始思考(一次 API 调用)Claude starts thinking (one API call) |
| turn_end | 一轮结束A turn ends | Claude 回复了一段话(可能还要继续)Claude replied with a segment (may continue) |
| message_start | 一条消息开始生成A message starts generating | Claude 开始打字Claude starts typing |
| message_update | 消息内容更新Message content updates | Claude 正在打字(逐字出现)Claude is typing (text appears word by word) |
| message_end | 消息生成完毕Message generation complete | Claude 一段话说完了Claude finished a paragraph |
| tool_execution_start | 开始执行一个工具Tool execution starts | 出现 "Reading file..." 提示"Reading file..." indicator appears |
| tool_execution_update | 工具执行进度Tool execution progress | 进度条在动Progress bar is moving |
| tool_execution_end | 工具执行完毕Tool execution complete | 文件内容显示出来了File contents are displayed |
九、types.ts 关键合同速查IX. types.ts Key Contract Quick Reference
合同 AgentToolContract AgentTool(一个工具必须长什么样What a tool must look like)[通用 Agent 模式][Universal Agent Pattern]
├─ name 工具的名字(如 "Read"、"Bash")——AI 看这个名字决定要不要用Tool name (e.g. "Read", "Bash") -- the AI reads this name to decide whether to use it
├─ description 工具的说明——AI 看这个说明理解工具能做什么Tool description -- the AI reads this to understand what the tool can do
├─ parameters 参数格式定义(JSON Schema)——AI 生成参数时必须符合这个格式Parameter schema (JSON Schema) -- the AI must generate parameters conforming to this
├─ label 人类友好的标签(UI 显示用,AI 看不到)Human-friendly label (for UI display, invisible to AI)
├─ execute(参数params) 执行函数——真正干活的代码,返回结果Execute function -- the code that does the actual work, returns results
├─ prepareArguments(原始参数rawParams) 可选:修正 AI 给的参数(AI 有时格式不完全对)Optional: fix AI-provided params (AI sometimes gets the format slightly wrong)
└─ executionMode 可选:"sequential"(必须排队)/ "parallel"(可以并行)Optional: "sequential" (must queue) / "parallel" (can run concurrently)
合同 AgentMessageContract AgentMessage(对话消息的通用格式Universal format for conversation messages)[通用 Agent 模式][Universal Agent Pattern]
= LLM 标准消息(user / assistant / toolResult)= Standard LLM messages (user / assistant / toolResult)
+ 自定义消息(产品代码可以扩展,如:通知、状态、UI 指令……)+ Custom messages (product code can extend, e.g.: notifications, status, UI commands...)
为什么不直接用 LLM 的 Message?因为产品需要在对话流里塞自己的消息类型。Why not use the LLM's Message directly? Because products need to insert their own message types into the conversation flow.
比如 Claude Code 在对话里插入"工具执行中..."的状态消息——这不是 AI 说的话,也不是用户说的话。For example, Claude Code inserts "tool executing..." status messages -- these aren't said by the AI or the user.
convertToLlm 的职责就是在发给 AI 之前把这些自定义消息过滤掉。convertToLlm's job is to filter out these custom messages before sending to the AI.
合同 AgentContextContract AgentContext(发给引擎的上下文快照Context snapshot sent to the engine)[通用 Agent 模式][Universal Agent Pattern]
├─ systemPrompt 系统提示词(告诉 AI "你是谁、你的规则")System prompt (tells the AI "who you are, your rules")
├─ messages 全部对话记录(从第一条到现在)Full conversation history (from the first message to now)
└─ tools 可用工具列表(AI 只能调这些工具)Available tool list (AI can only call these tools)
这三样东西合在一起就是"三件套"——上次笔记 streamAssistantResponse 的 ❸ 组装三件套These three together form the "trio" -- the ❸ Assemble Trio from the streamAssistantResponse notes
管理层用 createContextSnapshot() 从自己的 state 里拍一张快照,交给引擎The management layer uses createContextSnapshot() to take a snapshot from its state and hand it to the engine
十、读完应该带走的认知X. Key Takeaways After Reading
5 条核心认知5 Core Insights
- Agent = 管理层 + 引擎,职责分明
管理层(agent.ts)负责配置、启动、协调、事件分发;引擎(agent-loop.ts)负责循环、调 LLM、执行工具。管理层不碰循环细节,引擎不碰外部通信。这种分层在所有框架中通用。
- Agent = Management Layer + Engine, with clear separation of duties
The management layer (agent.ts) handles config, startup, coordination, and event dispatch; the engine (agent-loop.ts) handles the loop, LLM calls, and tool execution. The management layer never touches loop internals; the engine never touches external communication. This layering is universal across frameworks.
- 管理层通过"引用"连接外部系统,不自己实现
上下文压缩函数、消息格式转换函数、工具列表、权限钩子——全是从外部传入的引用。换一套引用,同一个引擎就变成完全不同的产品。这是"可插拔"设计的具体实现。
- The management layer connects to external systems via "references," never implementing them itself
Context compression, message format conversion, tool list, permission hooks -- all are references passed in from outside. Swap the references, and the same engine becomes a completely different product. This is the concrete implementation of "pluggable" design.
- 两个队列实现了"运行中干预"
steeringQueue(插队:当前轮后注入)和 followUpQueue(追加:停下前注入),让外部可以在 agent 运行时改变它的方向或追加任务,而不需要重启。
- Two queues enable "mid-run intervention"
steeringQueue (steer: inject after current turn) and followUpQueue (follow-up: inject before stopping) let the outside world change the agent's direction or add tasks while it's running, without restarting.
- 事件系统是"内部状态"和"外部世界"的桥梁
引擎把进度通知发给 processEvents,它先更新自己的内部状态,再转发给所有订阅者。Session JSONL 写入、UI 渲染、进度条——全是订阅者,都挂在这个桥上。
- The event system bridges "internal state" and the "external world"
The engine sends progress notifications to processEvents, which first updates its own internal state, then forwards to all subscribers. Session JSONL writing, UI rendering, progress bars -- all are subscribers, all connected to this bridge.
- "合同优先"——types.ts 定义了所有零件的接口
引擎不关心谁提供了 convertToLlm 函数,只关心它符合合同(输入 AgentMessage[],输出 Message[])。这让每个零件可以独立替换,不影响其他部分。
- "Contracts first" -- types.ts defines the interface for every part
The engine doesn't care who provides the convertToLlm function, only that it conforms to the contract (input AgentMessage[], output Message[]). This lets every part be replaced independently without affecting the rest.
和你日常体验的对应Mapping to Your Daily Experience
| 你看到的现象What you see | 对应模块Corresponding module |
| 在 Claude Code 输入一条消息按回车Type a message in Claude Code and press Enter | prompt() ❶→❷→❸,启动引擎循环, starts the engine loop |
| Claude 正在工作时你又发了一条消息You send another message while Claude is working | steer():消息进入 steeringQueue 排队: message enters the steeringQueue |
| Claude 回复完了,界面变回等待状态Claude finishes replying, UI returns to waiting state | finishRun():isStreaming = false,释放 activeRun: isStreaming = false, releases activeRun |
| Claude 正在逐字输出回复Claude is outputting a reply word by word | processEvents 收到 message_update,更新 streamingMessagereceives message_update, updates streamingMessage |
| 界面上显示 "Reading file...""Reading file..." appears in the UI | processEvents 收到 tool_execution_start,某个 UI 订阅者渲染了提示receives tool_execution_start, a UI subscriber renders the indicator |
| 你按了 Ctrl+C 中断 ClaudeYou press Ctrl+C to interrupt Claude | abort():按下 AbortController 的取消按钮: presses the AbortController's cancel button |
| Session JSONL 文件里多了几行记录Session JSONL file gains a few new lines | 某个订阅者(JSONL 写入器)在 processEvents 转发时写入磁盘A subscriber (JSONL writer) writes to disk when processEvents forwards |
补充说明:为什么 Agent 只能同时跑一个任务?Supplementary: Why can the Agent only run one task at a time?
prompt() 和 continue() 都有"防重入检查"——如果已经有 activeRun,就报错。这是因为一个 Agent 实例只有一份状态(一份对话记录、一份工具列表)。同时跑两个循环会导致状态冲突(两个循环往同一份对话记录里写东西)。想并行处理多个任务?创建多个 Agent 实例。
Both prompt() and continue() have a "re-entry guard" -- if there's already an activeRun, they throw an error. This is because a single Agent instance has only one state (one conversation history, one tool list). Running two loops simultaneously would cause state conflicts (two loops writing to the same conversation history). Want parallel tasks? Create multiple Agent instances.
补充说明:createContextSnapshot 为什么要"拍快照"?Supplementary: Why does createContextSnapshot take a "snapshot"?
管理层把 messages 和 tools 复制一份(.slice())再交给引擎,而不是直接把原始列表递过去。原因:引擎运行期间,外部可能通过操作面板修改了 state.messages 或 state.tools。如果引擎拿的是原始引用,外部的修改会影响正在运行的循环。快照保证引擎用的是启动那一刻的状态,不会被中途篡改。
The management layer copies messages and tools (.slice()) before handing them to the engine, rather than passing the original arrays directly. Reason: while the engine is running, external code might modify state.messages or state.tools via the control panel. If the engine held the original reference, external changes would affect the running loop. The snapshot guarantees the engine uses the state from the moment of launch, safe from mid-run tampering.