> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 代理循环

> 说明 Copilot CLI 如何处理从 prompt 接收到 session.idle 的整个流程。

## 代理循环

说明 Copilot CLI 如何端到端地处理用户消息（从发送 prompt 到 `session.idle`）。

## 架构

```mermaid theme={null}
graph LR
    App["Your App"] -->|send prompt| SDK["SDK Session"]
    SDK -->|JSON-RPC| CLI["Copilot CLI"]
    CLI -->|API calls| LLM["LLM"]
    LLM -->|response| CLI
    CLI -->|events| SDK
    SDK -->|events| App
```

**SDK** 是传输层。它通过 JSON-RPC 将 prompt 发送到 **Copilot CLI**，并把事件转发给应用。执行代理式工具使用循环、编排一次或多次 LLM API 调用直到任务完成的，是 **CLI** 侧。

## 工具使用循环

当你调用 `session.send({ prompt })` 时，CLI 会进入以下循环。

```mermaid theme={null}
flowchart TD
    A["User prompt"] --> B["LLM API call<br>(= one turn)"]
    B --> C{"toolRequests<br>in response?"}
    C -->|Yes| D["Execute tools<br>Collect results"]
    D -->|"Results fed back<br>as next turn input"| B
    C -->|No| E["Final text<br>response"]
    E --> F(["session.idle"])

    style B fill:#1a1a2e,stroke:#58a6ff,color:#c9d1d9
    style D fill:#1a1a2e,stroke:#3fb950,color:#c9d1d9
    style F fill:#0d1117,stroke:#f0883e,color:#f0883e
```

模型在每次调用时会参考 **整个对话历史**（system prompt / user message / 到那时为止的所有工具调用与结果）。

**重要：** 该循环的一次迭代与一次 LLM API 调用完全对应，在事件日志中体现为一对 `assistant.turn_start` / `assistant.turn_end`。没有隐藏的调用。

## 什么是 Turn（轮次）

**Turn** 是指一次 LLM API 调用及其结果。

1. CLI 将对话历史发送给 LLM
2. LLM 响应（可能包含工具请求）
3. 如果有工具请求，CLI 会执行它们
4. 发出 `assistant.turn_end`

一条用户消息通常会产生 **多个 turn**。例如对于「在这个代码库中 X 是如何工作的？」这样的问题：

| Turn | 模型的动作                    | toolRequests?    |
| ---- | ------------------------ | ---------------- |
| 1    | 使用 `grep` 和 `glob` 搜索代码库 | ✅ Yes            |
| 2    | 根据搜索结果读取特定文件             | ✅ Yes            |
| 3    | 为了更深入的上下文继续读取            | ✅ Yes            |
| 4    | 生成最终的文本回答                | ❌ No → loop ends |

模型在每一轮判断「是否继续使用工具」或「进入最终回答」。由于每次调用都能看到 **累积的完整上下文**（包括过去的工具调用与结果），因此可以判断信息是否足够。

## 多轮次时的事件流

```mermaid theme={null}
flowchart TD
    send["session.send({ prompt: &quot;Fix the bug in auth.ts&quot; })"]

    subgraph Turn1 ["Turn 1"]
        t1s["assistant.turn_start"]
        t1m["assistant.message (toolRequests)"]
        t1ts["tool.execution_start (read_file)"]
        t1tc["tool.execution_complete"]
        t1e["assistant.turn_end"]
        t1s --> t1m --> t1ts --> t1tc --> t1e
    end

    subgraph Turn2 ["Turn 2 — auto-triggered by CLI"]
        t2s["assistant.turn_start"]
        t2m["assistant.message (toolRequests)"]
        t2ts["tool.execution_start (edit_file)"]
        t2tc["tool.execution_complete"]
        t2e["assistant.turn_end"]
        t2s --> t2m --> t2ts --> t2tc --> t2e
    end

    subgraph Turn3 ["Turn 3"]
        t3s["assistant.turn_start"]
        t3m["assistant.message (no toolRequests)<br>&quot;Done, here's what I changed&quot;"]
        t3e["assistant.turn_end"]
        t3s --> t3m --> t3e
    end

    idle(["session.idle — ready for next message"])

    send --> Turn1 --> Turn2 --> Turn3 --> idle
```

## 谁来启动每一轮

| Actor           | Responsibility                   |
| --------------- | -------------------------------- |
| **Your app**    | 通过 `session.send()` 发送最初的 prompt |
| **Copilot CLI** | 执行工具使用循环，将工具执行结果作为下一轮输入返回给 LLM   |
| **LLM**         | 决定是请求工具继续，还是返回最终响应并结束            |
| **SDK**         | 仅中继事件。不做循环控制                     |

CLI 的行为是机械式的（「模型请求工具 → 执行 → 再次调用模型」）。做出停止判断的决策者是 **模型**。

## `session.idle` 与 `session.task_complete` 的区别

两者都是完成信号，但保证内容差别很大。

### `session.idle`

* 在工具使用循环结束时 **一定会发出**
* **短暂性（ephemeral）**。不会持久化到磁盘，也不会在会话恢复时重放
* 含义：「代理已停止处理，可以接受下一条消息」
* 作为可靠的「完成」信号请使用这个

SDK 的 `sendAndWait()` 会等待该事件。

```typescript theme={null}
// 等待直到发出 session.idle
const response = await session.sendAndWait({ prompt: "Fix the bug" });
```

### `session.task_complete`

* **可选发出**（只有模型显式发出信号时才会发出）
* 会 **持久化**（保存在会话事件日志中）
* 含义：「代理自身判断整个任务已完成」
* 可以包含可选的 `summary`

```typescript theme={null}
session.on("session.task_complete", (event) => {
    console.log("Task done:", event.data.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`」的内容。

* 如果有未解决的疑问不要调用。做出判断并继续工作
* 如果只是遇到了错误不要调用。尝试解决
* 如果还有剩余任务不要调用。先完成它们

在 autopilot 中形成了以下 **两阶段完成机制**：

1. 模型带着 summary 调用 `task_complete` → CLI 发出 `session.task_complete` → 完成
2. 模型没有调用就停止 → CLI 催促 → 模型继续或调用 `task_complete`

### `task_complete` 不出现的原因

在 **interactive mode**（普通聊天）下，CLI 不会催促 `task_complete`。模型有时会省略它。主要原因如下。

* **对话式 Q\&A**：回答问题后结束，没有离散的「已完成任务」
* **模型判断**：不调用 `task_complete` 就返回最终文本
* **中断的会话**：模型未到达完成点，会话就结束了

另一方面，CLI 总是会发出 `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 调用总数一致。不存在计划、评估、完成确认等隐藏调用。

确认某个会话的轮次数示例：

```bash theme={null}
# 计算会话事件日志中的 turn 数量
grep -c "assistant.turn_start" ~/.copilot/session-state/<sessionId>/events.jsonl
```

## 相关文档

* [流式事件](/zh/packages/laravel-copilot-sdk/streaming-events) — 各事件类型的字段级参考
* [会话恢复](/zh/packages/laravel-copilot-sdk/resume) — 会话保存与恢复
* [会话钩子](/zh/packages/laravel-copilot-sdk/hooks) — 拦截循环内事件（权限、工具）


## Related topics

- [Blade 模板](/zh/blade.md)
- [自定义代理](/zh/packages/laravel-copilot-sdk/custom-agents.md)
- [Laravel Boost](/zh/boost.md)
- [Fluent 类](/zh/advanced/fluent.md)
- [进程](/zh/processes.md)
