Add runtime guidance input queue
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
# 运行中输入队列与 Runtime Guidance 注入
|
||||
|
||||
## 背景
|
||||
|
||||
用户在一个会话运行中继续输入,是 Agent 产品的基本能力。这个输入不能直接当成普通 user message 写入当前模型历史,否则会产生三个问题:
|
||||
|
||||
1. **串台**:前端切换 session 或 URL 状态滞后时,新输入可能被写进旧 session。
|
||||
2. **取消误伤**:新建任务或继续输入会触发新的 run,从而取消当前正在运行的 run。
|
||||
3. **上下文污染**:运行中的输入如果直接进入 `model_messages`,会破坏当前 tool_use/tool_result 顺序,甚至触发 Bedrock/Anthropic 的 tool_result 校验错误。
|
||||
|
||||
正确做法是把运行中输入先作为 UI 和 runtime 的外部事件持久化,等 Agent loop 进入安全边界时再决定如何注入。
|
||||
|
||||
## 主流方案对比
|
||||
|
||||
| 方案 | 关键机制 | 对本项目的启发 |
|
||||
|------|----------|----------------|
|
||||
| [OpenAI Codex long-horizon tasks](https://developers.openai.com/blog/run-long-horizon-tasks-with-codex) | 长任务依赖计划、验证、修复和可持续的外部状态,而不是单轮大 prompt | 会话运行态要可恢复;用户中途修正不能重置整轮任务 |
|
||||
| [Claude Code hooks](https://code.claude.com/docs/en/hooks-guide) | `UserPromptSubmit`、`PreToolUse`、`PostToolUse`、`Stop` 等生命周期点允许注入上下文或阻断动作 | runtime guidance 应只在明确生命周期边界注入,不直接改写当前消息流 |
|
||||
| [Building AI Coding Agents for the Terminal](https://arxiv.org/html/2603.05344v1) | Agent harness 把输入层、工具层、上下文层和执行层拆开;输入可通过线程安全队列进入执行循环 | 运行中输入应先入队,再由 Agent loop 主线程消费 |
|
||||
| [Event-driven agentic loops](https://boundaryml.com/podcast/2025-11-05-event-driven-agents) | 用户输入、LLM chunk、tool call、interrupt 都是事件;UI、LLM、持久化各自投影 | `display_messages`、`model_messages`、`run_events` 必须分离,避免一个状态源服务所有场景 |
|
||||
|
||||
## 目标设计
|
||||
|
||||
```text
|
||||
用户输入
|
||||
-> 如果当前 session idle: 正常发送,创建 run
|
||||
-> 如果当前 session running: 写入 agent_input_queue
|
||||
-> UI 展示 pending chip
|
||||
-> 用户可编辑、删除、引导
|
||||
-> 引导: kind=guidance,绑定当前 run_id
|
||||
-> Agent loop 下一轮开始前消费 guidance
|
||||
-> guidance 以 display=false 的 user message 注入 model_messages
|
||||
```
|
||||
|
||||
## 数据分层
|
||||
|
||||
| 数据 | 作用 | 是否允许 compact 覆盖 |
|
||||
|------|------|-----------------------|
|
||||
| `model_messages` | 给模型推理用,可压缩、可摘要、可隐藏注入 | 允许 |
|
||||
| `display_messages` / `agent_display_messages` | 给 UI 回放用,append-only,不因为 compact 丢历史 | 不允许 |
|
||||
| `run_states` | 当前 run 的状态、耗时、取消能力 | 不允许用前端内存替代 |
|
||||
| `run_events` | 右侧活动区事件流 | 不允许只存在 SSE 内存里 |
|
||||
| `agent_input_queue` | 运行中输入、guidance、待处理后续输入 | 不允许直接写进 display/model messages |
|
||||
|
||||
## 后端实现约定
|
||||
|
||||
### 状态读取
|
||||
|
||||
前端优先读取:
|
||||
|
||||
```text
|
||||
GET /api/sessions/{session_id}/state
|
||||
```
|
||||
|
||||
返回:
|
||||
|
||||
- `messages`: DB 中 `agent_display_messages` 的增量或全量。
|
||||
- `activity_events`: DB 中 `run_events` 的增量或全量。
|
||||
- `run`: `run_states` 最新状态。
|
||||
- `input_queue`: 当前 pending 输入队列。
|
||||
|
||||
旧接口 `GET /api/sessions/{session_id}` 只作为兼容兜底,不应该再作为实时 UI 的主状态源。
|
||||
|
||||
### 输入队列
|
||||
|
||||
```text
|
||||
POST /api/sessions/{session_id}/input-queue
|
||||
GET /api/sessions/{session_id}/input-queue
|
||||
PATCH /api/sessions/{session_id}/input-queue/{item_id}
|
||||
DELETE /api/sessions/{session_id}/input-queue/{item_id}
|
||||
```
|
||||
|
||||
字段约定:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `kind=next_turn` | 运行中输入的默认状态,只展示在 composer 上方,不进入模型 |
|
||||
| `kind=guidance` | 用户显式点击“引导”后进入当前 run |
|
||||
| `run_id` | guidance 应绑定当前 active run;未绑定时由后端尝试绑定 latest active run |
|
||||
| `status=pending` | UI 可见,等待处理 |
|
||||
| `status=consumed` | 已被 runtime 注入 |
|
||||
| `status=cancelled` | 用户编辑/删除/取消 run 后不再处理 |
|
||||
|
||||
### Runtime 注入
|
||||
|
||||
`LocalCodingAgent` 提供 `runtime_guidance_provider`,在每轮模型调用前消费 pending guidance:
|
||||
|
||||
```text
|
||||
agent loop boundary
|
||||
-> consume_session_guidance(session_id, run_id)
|
||||
-> append hidden user message:
|
||||
<runtime-guidance>
|
||||
用户在任务运行中补充了以下引导...
|
||||
</runtime-guidance>
|
||||
-> display=false
|
||||
-> 继续模型调用
|
||||
```
|
||||
|
||||
这个注入点必须在 tool_result 已经写回之后、下一次模型调用之前,不能插在 tool_use 和 tool_result 中间。
|
||||
|
||||
## 前端交互
|
||||
|
||||
1. 当前 session idle:输入框 Enter 仍然正常发送。
|
||||
2. 当前 session running:输入框不禁用;Enter 写入 queue,清空输入框。
|
||||
3. pending 输入显示为 composer 上方 chip:
|
||||
- **编辑**:取消 queue item,把文本恢复到输入框。
|
||||
- **引导**:改成 `kind=guidance` 并绑定 active `run_id`。
|
||||
- **删除**:取消 queue item。
|
||||
4. 停止 run 时,后端同时取消该 session 下 pending queue item,避免下一轮误消费。
|
||||
|
||||
## 不做的事
|
||||
|
||||
- 不在运行中输入时自动创建新 run。
|
||||
- 不把 pending 输入直接写入 `display_messages`。
|
||||
- 不把 guidance 展示成普通用户消息;它是运行中的控制信号,不是对话历史。
|
||||
- 不依赖前端内存判断最终状态;刷新后必须能从 DB 完整恢复。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. 同一个账号同时打开两个 session,分别运行任务,输入不会串台。
|
||||
2. A session 运行中切到 B session 输入,B 的输入只进入 B 的 queue。
|
||||
3. A session 运行中输入后刷新,pending chip 仍存在。
|
||||
4. 点击“引导”后,下一次模型调用前能消费 guidance,并在活动区记录注入事件。
|
||||
5. 点击停止后,对应 run 的进程和 pending queue 都被取消。
|
||||
6. 触发 compact 后,UI 仍能从 `display_messages` 回放完整历史;模型只使用 compact 后的 `model_messages`。
|
||||
@@ -15,6 +15,7 @@
|
||||
8. [label-master Skill 实现](08-label-master.md)
|
||||
9. [外部系统 Skill:ELK、SQL、模型迭代](09-external-skills.md)
|
||||
10. [Agent 记忆机制调研与对比](10-memory-research.md)
|
||||
11. [运行中输入队列与 Runtime Guidance 注入](12-runtime-guidance-queue.md)
|
||||
|
||||
## 一句话定位
|
||||
|
||||
|
||||
Reference in New Issue
Block a user