Appearance
Session 层(AgentSession)
packages/coding-agent/src/core/agent-session.ts 是整个应用最重要的连接层,约 4400 行。它回答几个问题:
text
底层 AgentMessage[] 怎么变成持久化的 session?
compaction、retry、branch、model switching 在哪发生?
扩展系统、工具注册、队列显示怎么接进来?三个对象的职责:
| 对象 | 职责 |
|---|---|
Agent | 运行时状态:消息、模型、思考级别、队列、abort、事件 |
SessionManager | 持久化树:JSONL entry、leafId、投影 |
AgentSession | 把前两者和扩展、工具、retry、compaction、模型路由连接起来 |
构造时安装了什么
AgentSession 构造时会做一批一次性接线:
text
agent.subscribe(_handleAgentEvent)
_installAgentToolHooks() beforeToolCall / afterToolCall
_installAgentNextTurnRefresh() prepareNextTurnWithContext:compaction + 提示词/工具集合刷新
_installAgentRequestProjection() prepareRequest:从 session 投影重建请求上下文、虚拟模型路由
_installAgentBoundaryHooks() finishTurn:turn_end 边界事件和 continue 决策
_installHiddenDeclarationsProjection() transformContext:过滤被隐藏的工具声明
_installAgentForcedPromptProjection() transformContext:投影强制系统提示词
_buildRuntime() 扩展 runner + 工具注册表这些钩子读取的是 this._extensionRunner 等运行时字段,所以 reload() 换掉 runner 后不需要重新安装钩子。
配置项大多来自 AgentSessionConfig:agent、sessionManager、settingsManager、cwd、resourceLoader、modelRuntime、scopedModels、customTools、cacheWarmer、初始工具集、工具 allow/deny 列表、baseToolsOverride、extensionRunnerRef、sessionStartEvent。
事件处理与持久化
_handleAgentEvent 是所有 agent 事件的入口,顺序大致是:
- 如果 run 启动时已经请求 abort,在这里再调用一次
agent.abort() - 把
ctx.executeTool()的嵌套调用记录写回 toolResult 消息(nestedCalls、usage) - user 消息开始时,先把它从队列显示数组里移除并发出
queue_update - 先发给扩展 runner,再发给公开监听器
message_end时持久化turn_end时记录 toolResults 并 flush 挂起的自定义消息
持久化分支:
text
role === "custom" -> appendCustomMessageEntry
role 是 system/user/assistant/toolResult -> appendMessage
其它类型(bashExecution 等) -> 由别处持久化每条持久化消息都会记进 _entryIdsByMessage(WeakMap),扩展的 turn_end、context edit 等需要 entry id 时都靠它反查。
message_end 里还会更新 _lastAssistantMessage。成功的 assistant 响应会重置 _retryAttempt,这样同一 turn 内多次 LLM 调用的重试计数不会累加。
AgentSessionEvent 在 AgentEvent 基础上扩展了:
| 事件 | 含义 |
|---|---|
agent_end.willRetry | 这次结束之后是否会自动重试 |
agent_settled | run 完全结算,不会再有 retry/compaction/队列续跑 |
queue_update | steering / followUp 显示列表变化 |
entry_appended | session 树新增 entry |
compaction_start / compaction_end | 手动或自动 compaction |
auto_retry_start / auto_retry_end | 自动重试 |
summarization_retry_* | 摘要请求自己的重试 |
bash_execution_update | bash 流式输出 |
session_info_changed / thinking_level_changed | 元信息变化 |
prompt 的完整路径
prompt() 会依次处理:
- 如果当前正在发送
agent_settled,把这次调用推迟到结算完成之后 - 以
/开头且允许展开时,先尝试执行扩展命令 - 手动 compaction 正在进行时直接报错
- 发
input事件给扩展拦截或改写 - 展开 skill 命令和 prompt template
- 如果正在 streaming,按
streamingBehavior走steer()或followUp() - 校验模型和认证
- 进入
_startRun()
_startRun() 是真正的运行循环:
text
设置 _isAgentRunActive = true
_prepareRun() 得到起始消息
flush 挂起的 bash / custom 消息
await agent.prompt(messages)
while (!abortRequested) {
if (await _handlePostAgentRun()) {
await agent.continue();
continue;
}
if (!(await _runBeforeSettleBoundary())) break;
await agent.continue();
}
finally:
清理状态
_flushPendingBashMessages()
_flushPendingCustomMessages()
await _emitAgentSettled()关键点是:retry、compaction、队列续跑都发生在底层循环之外。底层循环结束一次 run 后,AgentSession 再决定要不要 agent.continue() 继续。
_prepareRun() 做的是:
- 如果存在上一条 assistant 消息,先检查一次 compaction
- 发
before_agent_start事件,收集自定义消息和系统提示词覆盖 - 组装起始消息:user 消息或 custom 消息、
nextTurn消息、handler 消息 - 计算提示词和工具集合的差异,必要时在最前面插一条 system patch
run 结束后的决策
_handlePostAgentRun() 的优先级:
text
已请求 abort -> 结束
可重试错误 -> 准备 retry,返回 true
重试次数耗尽 -> 发 auto_retry_end 失败,清零
需要 compaction -> 返回是否需要继续
队列里还有消息 -> 返回 true队列
AgentSession 维护两份字符串数组 _steeringMessages / _followUpMessages,只用于 UI 显示。真正的投递由 Agent 的队列负责。
clearQueue() 会把文本返回给调用方,比如 TUI 用 Esc 中止后把排队消息恢复进编辑器。队列模式(all / one-at-a-time)从设置同步到 agent.steeringMode / agent.followUpMode。
compaction
两种入口:
| 入口 | 触发 |
|---|---|
compact() | 手动,/compact、RPC、扩展调用 |
_checkCompaction() / _runAutoCompaction() | 自动,阈值或 overflow |
两者都会:
text
prepareCompaction() 得到候选
-> session_before_compact 扩展事件(可取消,或直接提供结果)
-> 默认摘要生成(可带自己的重试)
-> sessionManager.appendCompaction()
-> session_compact 扩展事件
-> compaction_end 事件自动 compaction 分三类:
- overflow 且要重试:上下文溢出错误或可恢复的 length 截断。先把失败的 assistant 消息用
context_edit(replacement: null)从投影里省略,compaction 后重试一次;_overflowRecoveryAttempted保证只试一次。 - overflow 但响应成功:compaction 后不重试,因为
agent.continue()无法从一条完成的 assistant 消息继续。 - threshold 但无需重试:上下文用量超过阈值,只压缩不重试;如果压缩期间有排队消息,继续一次把它们投递出去。
_installAgentNextTurnRefresh() 还在每轮之间检查一次阈值 compaction,这样即使上一轮被中止,也能在下一轮开始前压缩。
retry
_isRetryableError() 判断是否可重试;上下文溢出不算可重试错误,由 compaction 处理。
_prepareRetry():
text
attempt++
超过 maxRetries -> 回退计数并返回 false
按指数退避 sleep(可被 abort)
_omitRecoveryAttempt(message) 用 context_edit 把失败消息移出投影
返回 true失败消息仍然留在原始历史里,只是不发给模型。_failedResponse 会把它带给虚拟模型路由,reason 为 retry。
模型切换
setModel() / cycleModel() 都会:
- 校验认证
- 写
agent.state.model sessionManager.appendModelChange(provider, modelId)- 把思考级别限制到模型支持的范围内
- 发
model_select扩展事件
思考级别单独处理:只有真正变化时才 append thinking_level_change entry。
虚拟模型(api === "pi-virtual")不会到达 provider。_installAgentRequestProjection() 在每次请求前调用 ModelRuntime.resolveModel(),按 user / continuation / retry / direct 原因路由到物理模型,并把 router 状态作为 pi.virtual-model-state custom entry 存在分支上。
abort 与 idle
text
abort()
-> 标记 _agentRunAbortRequested
-> abort retry / compaction / branch summary
-> 如果在 before_settle 阶段,标记 _abortDuringBeforeSettle
-> agent.abort()
-> await waitForIdle()isIdle 的条件是没有活跃 run 且没有 compaction。_emitAgentSettled() 先置 _isAgentRunActive = false,再依次发扩展和公开的 agent_settled,最后执行结算期间推迟的动作并唤醒等待者。
建议阅读顺序
不要从头逐行读。按调用链读:
text
constructor
-> prompt()
-> _startRun()
-> _prepareRun()
-> _handlePostAgentRun()
-> _prepareRetry()
-> _checkCompaction()
-> _runAutoCompaction()
-> _runBeforeSettleBoundary()
-> _handleAgentEvent()
-> _installAgentRequestProjection()
-> _installAgentNextTurnRefresh()工具、扩展、session 树分别有独立文档,可以交叉看。