Files
LangBot/docs/agent-runner-pluginization/RUN_STEERING_AND_CHECKPOINT.md
2026-06-10 22:46:13 +08:00

153 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Run Steering 与 Compaction CheckpointFuture Design Note
本文档描述两项尚未落地的 Host 能力缺口:**运行中消息注入steering / follow-up**和
**压缩摘要持久化compaction checkpoint**。两者来自官方 local-agent 对照
Pi agent harness`pi-mono/packages/agent`,下称 pi-agent-core的差距分析
local-agent 已移植 Pi 的事件生命周期、并行工具语义、hook 扩展点和压缩预算模型,
但这两项无法由 runner 单方面闭环,需要 Host 协议或授权配合。
> 本文是设计备忘,不是 schema 事实源。涉及的数据结构最终落到
> [PROTOCOL_V1.md](./PROTOCOL_V1.md);上下文边界语义以
> [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md) 为准;
> run 持久化与控制原语以 [RUNTIME_CONTROL_PLANE_V2.md](./RUNTIME_CONTROL_PLANE_V2.md) 为准。
## 1. Run Steering / Follow-up运行中消息注入
### 1.1 问题
IM 场景下用户在 agent 运行中追加消息非常常见(补充信息、纠正方向、"算了别查了")。
当前主线是 `one event -> one AgentBinding -> one run_id -> one runner`
PROTOCOL_V1 §13同会话的新消息要么等待当前 run 结束后触发新 run
要么并发触发独立 run。两种行为都无法把新消息送进**正在执行的 tool loop**
用户体验是"agent 自顾自跑完过期任务,然后才看到新消息"。
cancelPROTOCOL_V1 §10不解决这个问题cancel 丢弃已完成的工作;
steering 是在保留当前进度的前提下改变后续方向。
### 1.2 Pi 的参考语义
pi-agent-core 区分两个队列,注入时机都在 turn 边界,不打断进行中的模型流或工具执行:
- **steering**:运行中插入。当前 assistant 消息的全部 tool call 完成后、
下一次模型调用前,注入排队的用户消息;模型在下一 turn 看到它们。
- **follow-up**:排队后续工作。仅当没有 pending tool call 且没有 steering 消息、
run 即将自然结束时检查;若有排队消息则注入并继续下一 turn而不是结束 run。
两个队列各自支持 `one-at-a-time`(每次注入一条)和 `all`(一次注入全部)模式。
### 1.3 设计方向
职责划分遵循既有原则Host 拥有事件路由和会话事实源runner 拥有 turn 边界。
- **Host 侧**BindingResolver / dispatch 层识别"同 conversation 存在 active run
且 runner 声明支持 steering"的新消息事件,将其写入 run-scoped steering queue
并标记该事件已被在途 run 认领(不再触发新 run避免破坏 §13 的基数约束)。
事件仍照常进 EventLog / Transcript事实源不变改变的只是触发行为
- **Runner 侧**:在 turn 边界tool batch 完成后、下一次模型调用前,以及 run
即将自然结束前)通过 run-scoped pull API 拉取 pending steering 输入,
注入 working context。local-agent 的 `AgentLoopHooks.prepare_next_turn` /
`should_stop_after_turn` 已预留了对应的注入点。
- **能力协商**runner manifest 声明 `steering` capability参照 PROTOCOL_V1 §4.3
未声明的 runner 保持现状(新消息按现有规则另起 run
- **回执**:被 steering 消费的事件需要可审计的归属记录event 被哪个 run_id 认领、
是否最终注入成功),形式可以是新的 result type 或 EventLog 记录,落协议时定。
需要新增的协议面(最终定义归 PROTOCOL_V1
1. `ContextAccess.available_apis` 增加 steering pull 能力位。
2. `AgentRunAPIProxy` 增加 steering 拉取 action含 one-at-a-time / all 语义参数)。
3. dispatch 层的"认领"规则:什么事件类型可被 steering 吸收、超时未拉取如何回退
建议run 结束或 deadline 到期时,未消费的排队事件按普通事件重新触发 run
### 1.4 边界
- 不引入 Host 替 runner 做 prompt 拼接Host 只递队列,注入位置和格式由 runner 决定。
- 不与 observer / fan-out 混淆steering 仍是单 run 内的输入补充,不产生第二个 runner。
- 远程 / 外部 harness runnerclaude-code、codex 等)若其底层 session 自带
steering 能力adapter 可以直接转发;协议面保持一致。
## 2. Compaction Checkpoint 持久化
### 2.1 问题
local-agent 当前是无状态 runner每次 run 重新拉取 transcript 尾部
(默认 50 条)、重新估算 token、重新生成压缩摘要。后果
- 长会话中每 run 重复压缩计算,摘要每次重新生成,不同 run 之间措辞漂移,
对 provider KV cache 不友好AGENT_CONTEXT_PROTOCOL §"Summary checkpoint 稳定"
已写明期望:只有压缩发生时才产生新 checkpoint
- 历史一旦超过 fetch limit更早的内容永久不可见——没有 checkpoint 记录
"已压缩到哪里、压缩出了什么"。
pi-agent-core 把 compaction 条目持久化进 session tree摘要带
`tokensBefore` 和覆盖范围,后续 turn 直接复用,只在再次越过阈值时增量压缩。
### 2.2 现状盘点
协议面基本已备齐,缺的是消费约定和授权:
- State / Storage API 已定义PROTOCOL_V1 §8 "State / Storage"
且 AGENT_CONTEXT_PROTOCOL 已点名 `summary.checkpoint` 是 state 的预期用法。
- `ContextAccess.available_apis.state` 默认 `false`PROTOCOL_V1 §5.8
Host 尚未对 local-agent binding 默认开启。
- local-agent 侧完全未消费:不读不写 checkpoint其 README "Current Boundary"
已声明这是预期的未来工作)。
- LLM 生成摘要**不依赖**本项 Host 能力——runner 用已授权的 `invoke_llm`
即可生成,可以先行实现;本项只解决"存下来、下次复用"。
### 2.3 设计方向
- **存放位置**statescope=`conversation`(小 JSON符合 PROTOCOL_V1 §8
对 state/storage 的边界建议)。若未来摘要膨胀,超出部分放 storage 并在
state 中留引用。
- **key 约定**`runner.compaction.checkpoint`runner 命名空间内)。
- **内容约定**schema 落 PROTOCOL_V1 或 runner 文档,此处只列语义):
- `schema_version`
- `summary`压缩摘要文本LLM 生成或确定性生成)
- `covers_until`:已被摘要覆盖的 transcript 游标seq / message id
是增量压缩和"从哪继续拉历史"的锚点
- `tokens_before` / `created_at`:诊断与失效判断
- **消费流程**run 开始时读 checkpoint → 只拉取 `covers_until` 之后的
transcript → 压缩触发时基于旧摘要增量生成新摘要、写回新 checkpoint。
checkpoint 缺失或解析失败时回退到现行为(全量拉尾部),保证向后兼容。
- **失效规则**`covers_until` 在 Host transcript 中不存在(会话被清理 / 重置)
即作废runner 不得信任跨 conversation 的 checkpoint。
- **授权**Host 对声明需要 state 的 runner binding 开启
`available_apis.state`;校验沿用现有 run-scoped state 校验
scope、key、value 大小、JSON 可序列化,见 PROTOCOL_V1 §7.2 对
`state.updated` 的要求)。
### 2.4 相关但独立的工作
- **tokenizer / usage metadata 透传**runner 目前用 chars/4 启发式估 token
对 CJK 偏低 3-4 倍压缩触发系统性偏晚。Host 应在模型响应或
`ctx.runtime.metadata` 透传 provider usageprompt/completion tokens
model context windowLiteLLM model-info 工作)。该项不阻塞 checkpoint
落地,但决定压缩触发的准确性。
## 3. 实施拆分
| 项 | 归属 | 依赖 |
| --- | --- | --- |
| steering queue、事件认领、超时回退 | LangBot Hostdispatch / binding 层) | 无 |
| steering pull API + capability 位 | PROTOCOL_V1 + SDK proxy | 上一项 |
| turn 边界拉取与注入 | langbot-local-agenthooks 已预留) | 上两项 |
| local-agent 对 state API 的 checkpoint 读写 | langbot-local-agent | Host 开启 `available_apis.state` |
| checkpoint key / 内容 / 失效约定 | 本文档 → PROTOCOL_V1 | 无 |
| LLM 压缩摘要生成 | langbot-local-agent | 无(`invoke_llm` 已可用) |
| usage / context-window metadata 透传 | LangBot Hostmodel 层) | LiteLLM model-info |
建议顺序checkpoint 先行(协议面现成,改动集中在授权和 runner 消费),
steering 后行(需要新协议面和 dispatch 行为变更)。
## 4. 开放问题
- steering 注入的消息在 Transcript 中如何与普通消息区分(审计需要区分
"作为新 run 触发"与"被在途 run 吸收")。
- 多条排队消息的合并语义由谁定Host 全量递给 runner还是支持
one-at-a-time 协商;建议 Host 全量递、runner 自行决定消费节奏。
- streaming delivery 下 steering 注入后,前序 turn 已流出的内容与新 turn
输出在 IM 消息编辑面的衔接(涉及 `ctx.delivery` 能力,待 delivery 演进定)。
- checkpoint 是否需要 Host 侧主动失效通知(如会话清空时删除对应 state key
还是仅靠 runner 读取时校验 `covers_until`