代理循环
说明 Copilot CLI 如何端到端地处理用户消息(从发送 prompt 到session.idle)。
架构
SDK 是传输层。它通过 JSON-RPC 将 prompt 发送到 Copilot CLI,并把事件转发给应用。执行代理式工具使用循环、编排一次或多次 LLM API 调用直到任务完成的,是 CLI 侧。工具使用循环
当你调用session.send({ prompt }) 时,CLI 会进入以下循环。
模型在每次调用时会参考 整个对话历史(system prompt / user message / 到那时为止的所有工具调用与结果)。
重要: 该循环的一次迭代与一次 LLM API 调用完全对应,在事件日志中体现为一对 assistant.turn_start / assistant.turn_end。没有隐藏的调用。
什么是 Turn(轮次)
Turn 是指一次 LLM API 调用及其结果。- CLI 将对话历史发送给 LLM
- LLM 响应(可能包含工具请求)
- 如果有工具请求,CLI 会执行它们
- 发出
assistant.turn_end
| Turn | 模型的动作 | toolRequests? |
|---|---|---|
| 1 | 使用 grep 和 glob 搜索代码库 | ✅ Yes |
| 2 | 根据搜索结果读取特定文件 | ✅ Yes |
| 3 | 为了更深入的上下文继续读取 | ✅ Yes |
| 4 | 生成最终的文本回答 | ❌ No → loop ends |
多轮次时的事件流
谁来启动每一轮
| Actor | Responsibility |
|---|---|
| Your app | 通过 session.send() 发送最初的 prompt |
| Copilot CLI | 执行工具使用循环,将工具执行结果作为下一轮输入返回给 LLM |
| LLM | 决定是请求工具继续,还是返回最终响应并结束 |
| SDK | 仅中继事件。不做循环控制 |
session.idle 与 session.task_complete 的区别
两者都是完成信号,但保证内容差别很大。
session.idle
- 在工具使用循环结束时 一定会发出
- 短暂性(ephemeral)。不会持久化到磁盘,也不会在会话恢复时重放
- 含义:「代理已停止处理,可以接受下一条消息」
- 作为可靠的「完成」信号请使用这个
sendAndWait() 会等待该事件。
session.task_complete
- 可选发出(只有模型显式发出信号时才会发出)
- 会 持久化(保存在会话事件日志中)
- 含义:「代理自身判断整个任务已完成」
- 可以包含可选的
summary
Autopilot 模式:CLI 会催促 task_complete
在 autopilot mode(headless / autonomous)下,CLI 会跟踪模型是否调用了 task_complete。如果工具使用循环在没有 task_complete 的情况下结束,CLI 会插入以下合成用户消息以催促模型。
“You have not yet marked the task as complete using the task_complete tool. If you were planning, stop planning and start implementing. You aren’t done until you have fully completed the task.”这实际上会重新启动工具使用循环。模型会把该催促当作新的用户消息接收并继续工作。催促中还包括「不要过早调用
task_complete」的内容。
- 如果有未解决的疑问不要调用。做出判断并继续工作
- 如果只是遇到了错误不要调用。尝试解决
- 如果还有剩余任务不要调用。先完成它们
- 模型带着 summary 调用
task_complete→ CLI 发出session.task_complete→ 完成 - 模型没有调用就停止 → CLI 催促 → 模型继续或调用
task_complete
task_complete 不出现的原因
在 interactive mode(普通聊天)下,CLI 不会催促 task_complete。模型有时会省略它。主要原因如下。
- 对话式 Q&A:回答问题后结束,没有离散的「已完成任务」
- 模型判断:不调用
task_complete就返回最终文本 - 中断的会话:模型未到达完成点,会话就结束了
session.idle。因为它不是语义化的信号(模型判断完成),而是机械式的信号(循环结束)。
该用哪一个
| Use case | Signal |
|---|---|
| 「等待代理处理结束」 | session.idle ✅ |
| 「知道编码任务已完成」 | session.task_complete(best-effort) |
| 「超时 / 错误处理」 | session.idle + session.error ✅ |
计算 LLM 调用次数
事件日志中的assistant.turn_start / assistant.turn_end 对数与 LLM API 调用总数一致。不存在计划、评估、完成确认等隐藏调用。
确认某个会话的轮次数示例: