Skip to content

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 事件的入口,顺序大致是:

  1. 如果 run 启动时已经请求 abort,在这里再调用一次 agent.abort()
  2. 把 ctx.executeTool() 的嵌套调用记录写回 toolResult 消息(nestedCalls、usage)
  3. user 消息开始时,先把它从队列显示数组里移除并发出 queue_update
  4. 先发给扩展 runner,再发给公开监听器
  5. message_end 时持久化
  6. 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_settledrun 完全结算,不会再有 retry/compaction/队列续跑
queue_updatesteering / followUp 显示列表变化
entry_appendedsession 树新增 entry
compaction_start / compaction_end手动或自动 compaction
auto_retry_start / auto_retry_end自动重试
summarization_retry_*摘要请求自己的重试
bash_execution_updatebash 流式输出
session_info_changed / thinking_level_changed元信息变化

prompt 的完整路径 ​

prompt() 会依次处理:

  1. 如果当前正在发送 agent_settled,把这次调用推迟到结算完成之后
  2. 以 / 开头且允许展开时,先尝试执行扩展命令
  3. 手动 compaction 正在进行时直接报错
  4. 发 input 事件给扩展拦截或改写
  5. 展开 skill 命令和 prompt template
  6. 如果正在 streaming,按 streamingBehavior 走 steer() 或 followUp()
  7. 校验模型和认证
  8. 进入 _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 分三类:

  1. overflow 且要重试:上下文溢出错误或可恢复的 length 截断。先把失败的 assistant 消息用 context_edit(replacement: null) 从投影里省略,compaction 后重试一次;_overflowRecoveryAttempted 保证只试一次。
  2. overflow 但响应成功:compaction 后不重试,因为 agent.continue() 无法从一条完成的 assistant 消息继续。
  3. 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 树分别有独立文档,可以交叉看。