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

# Agent Loop

> 說明 Copilot CLI 從接收 prompt 到 session.idle 的處理過程。

## Agent Loop

說明 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 / 目前為止的所有工具呼叫與結果）。

**重點：** 此迴圈的每次迭代與 1 次 LLM API 呼叫完全對應，事件日誌上會呈現為 1 組 `assistant.turn_start` / `assistant.turn_end`。並無隱藏的呼叫。

## 何謂 Turn（回合）

**Turn** 指的是 1 次 LLM API 呼叫及其結果。

1. CLI 將對話歷史送至 LLM
2. LLM 回應（可能包含 tool 請求）
3. 若有 tool 請求則由 CLI 執行
4. 觸發 `assistant.turn_end`

1 次使用者訊息通常會產生 **多個 turn**。例如「這個 codebase 中 X 如何運作？」這類問題會如下發生：

| Turn | 模型的動作                         | toolRequests?    |
| ---- | ----------------------------- | ---------------- |
| 1    | 以 `grep` 與 `glob` 搜尋 codebase | ✅ Yes            |
| 2    | 依搜尋結果讀取特定檔案                   | ✅ Yes            |
| 3    | 為取得更深的脈絡再讀取                   | ✅ Yes            |
| 4    | 產出最終文字回覆                      | ❌ No → loop ends |

模型會在每個 turn 判斷「還要使用更多工具嗎」或「進入最終回覆」。由於每次呼叫都能看到 **累積的完整脈絡**（含過去的 tool 呼叫與結果），因此能判斷資訊是否足夠。

## 多 turn 時的事件流

```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
```

## 各 turn 由誰啟動

| Actor           | Responsibility                          |
| --------------- | --------------------------------------- |
| **Your app**    | 透過 `session.send()` 送出第一個 prompt        |
| **Copilot CLI** | 執行工具使用迴圈，將 tool 執行結果作為下一 turn 的輸入交還 LLM |
| **LLM**         | 決定要繼續請求 tool，還是回傳最終回覆並結束                |
| **SDK**         | 僅轉遞事件，不進行迴圈控制                           |

CLI 的動作是機械式的（「模型請求 tool → 執行 → 再次呼叫模型」）。決定何時停止的是 **模型**。

## `session.idle` 與 `session.task_complete` 的差異

雖然兩者皆為完成訊號，但保證內容差異很大。

### `session.idle`

* 於工具使用迴圈結束時 **必定發出**
* **短暫性（ephemeral）**。不會持久化至磁碟，session 重啟時不會重播
* 意義：「代理停止處理，可接受下一則訊息」
* 作為可信賴的「完成」訊號應使用此事件

SDK 的 `sendAndWait()` 會等待此事件。

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

### `session.task_complete`

* **選擇性發出**（僅在模型明確發出訊號時）
* **持久化**（會儲存於 session 事件日誌）
* 意義：「代理自行判斷整體任務已達成」
* 可包含任意 `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` 而直接回傳最終文字
* **中斷的 session**：模型還沒抵達完成點時 session 就已結束

另一方面，CLI 一律會發出 `session.idle`，因為它並非語義訊號（模型判斷完成），而是機械訊號（迴圈結束）。

### 該使用哪一個？

| Use case    | Signal                             |
| ----------- | ---------------------------------- |
| 「等待代理處理結束」  | `session.idle` ✅                   |
| 「得知編碼任務已完成」 | `session.task_complete`（盡力而為）      |
| 「逾時 / 錯誤處理」 | `session.idle` + `session.error` ✅ |

## 計算 LLM 呼叫次數

事件日誌中的 `assistant.turn_start` / `assistant.turn_end` 配對數與 LLM API 呼叫總數一致。並無用於規劃、評估、確認完成的隱藏呼叫。

Session 內 turn 數的確認範例：

```bash theme={null}
# 計算 session 事件日誌中的 turn 數
grep -c "assistant.turn_start" ~/.copilot/session-state/<sessionId>/events.jsonl
```

## 相關文件

* [串流事件](/zh-TW/packages/laravel-copilot-sdk/streaming-events) — 各事件類型的欄位層級參考
* [Session 恢復](/zh-TW/packages/laravel-copilot-sdk/resume) — session 儲存與恢復
* [Session Hook](/zh-TW/packages/laravel-copilot-sdk/hooks) — 攔截迴圈內事件（權限、工具）


## Related topics

- [我的包](/zh-CN/packages/index.md)
- [建立 AI SDK 的自定義 Provider](/zh-TW/advanced/ai-sdk-custom-provider.md)
- [Blade 模板](/zh-TW/blade.md)
- [自訂 Agent](/zh-TW/packages/laravel-copilot-sdk/custom-agents.md)
- [Laravel Reverb](/zh-TW/reverb.md)
