会话引导(Steering)
Steering 允许你在运行中的 Agent 循环中、工具调用之间注入消息。当你需要重新引导 Agent、提供新上下文或取消正在执行的计划时非常有用 —— 无需等待当前轮次完成。
工作原理
每个工具执行完毕后,PicoClaw 会检查当前会话的 Steering 队列。如果发现一条或多条消息:
- 剩余排队的工具将被跳过 —— 其结果被替换为占位符。
- Steering 消息被注入到对话中,作为用户消息。
- 模型被再次调用,使用更新后的上下文。
流程图
Agent 循环运行中
│
▼
工具 N 执行完毕
│
▼
检查 Steering 队列 ──── 为空 ──► 继续执行工具 N+1
│
│ (发现消息)
▼
跳过剩余工具
│
▼
将 Steering 消息注入对话
│
▼
使用更新后的上下文调用 LLM
│
▼
Agent 循环以新方向继续
作用域隔离的队列
Steering 队列按已解析的会话作用域隔离 —— 不是全局的。每个活跃会话维护自己的队列,因此发送到一个会话的 Steering 消息永远不会泄漏到另一个会话。
配置
Steering 模式通过 agents.defaults.steering_mode 进行配置:
| 值 | 行为 |
|---|---|
"one-at-a-time"(默认) | 每个轮询周期出队一条消息。 |
"all" | 一次性消费整个队列。 |
也可以通过环境变量设置:
export PICOCLAW_AGENTS_DEFAULTS_STEERING_MODE=all
轮询点
PicoClaw 在 Agent 循环的四个位置检查 Steering 队列:
- 循环开始时 —— 在执行任何工具之前。
- 每个工具执行后 —— 主要的拦截点。
- 直接 LLM 响应后 —— 当模型不调用工具直接响应时。
- 轮次结束前 —— 在返回轮次结果之前的最后机会。
为什么要跳过剩余工具
当 Steering 消息在轮次中途到达时,所有尚未开始的工具都会被跳过。这是有意为之,原因有三:
| 原因 | 示例 |
|---|---|
| 防止不必要的副作用 | 用户说"停下,别删那个" —— 但 file_delete 工具还在队列中。跳过可以防止不可逆的损害。 |
| 避免浪费时间 | Agent 计划了 5 个 API 调用,但用户的 Steering 消息使它们变得无关。跳过可以节省延迟和费用。 |
| LLM 获得完整上下文 | 模型看到 Steering 消息以及之前的结果,可以做出更明智的决定。 |
被跳过的工具结果格式
因 Steering 而被跳过的工具,其结果被设置为:
Skipped due to queued user message.
这会出现在对话历史中,让 LLM 理解这些工具未被执行。
完整流程示例
Agent 接收到:"搜索配置文件,然后删除所有临时文件。"
│
▼
工具 1: search_files("*.conf") ──► 完成,返回结果
│
▼
检查 Steering 队列 ──► 用户发送了:"算了,别删任何东西。"
│
▼
工具 2: delete_files("*.tmp") ──► 已跳过("Skipped due to queued user message.")
工具 3: delete_files("*.bak") ──► 已跳过("Skipped due to queued user message.")
│
▼
注入 Steering 消息:"算了,别删任何东西。"
│
▼
LLM 看到:搜索结果 + 被跳过的工具 + 用户更正
│
▼
LLM 回复:"明白了,我找到了 3 个配置文件,但不会删除任何内容。"
自动总线消费
当 Agent 正在处理一个轮次时,后台 goroutine 会自动从总线消费入站消息并放入 Steering 队列。这确保了 Agent 繁忙时发送的消息不会丢失。
关键细节:
- 音频优先转录 —— 语音消息在入队前会被转换为文本。
- 作用域感知 —— 消息根据其作用域被路由到正确会话的 Steering 队列。
携带媒体的 Steering
Steering 消息可以包含 media:// 引用。这些引用在队列中被保留,并在消息注入对话时通过正常的媒体管道进行解析。
注意事项
- 队列最大容量为 10。 超过此限制的消息将被丢弃,并输出警告日志。
- Steering 不会中断正在执行的工具。 工具必须完成(或超时)后,才会检查 Steering 队列。