> ## 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가 사용자 메시지를 어떻게 end-to-end로 처리하는지(프롬프트 전송에서 `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로 **Copilot CLI**에 프롬프트를 보내고, 이벤트를 앱에 중계합니다. 에이전트적인 도구 사용 루프를 실행하고, 태스크가 완료될 때까지 1회 이상의 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`의 1쌍으로 보입니다. 숨겨진 호출은 없습니다.

## Turn(턴)이란 무엇인가

**Turn**은 LLM API 호출 1회와 그 결과를 의미합니다.

1. CLI가 대화 이력을 LLM에 전송합니다
2. LLM이 응답합니다(도구 요청을 포함할 수도 있음)
3. 도구 요청이 있으면 CLI가 실행합니다
4. `assistant.turn_end`가 발행됩니다

1회의 사용자 메시지에 대해 보통 **여러 턴**이 발생합니다. 예를 들어 "이 코드베이스에서 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()`로 최초 프롬프트를 전송               |
| **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에서는 다음의 **2단계 완료 메커니즘**이 됩니다.

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

## 관련 문서

* [스트리밍 이벤트](/ko/packages/laravel-copilot-sdk/streaming-events) — 각 이벤트 타입의 필드 레벨 참조
* [세션 재개](/ko/packages/laravel-copilot-sdk/resume) — 세션 저장과 재개
* [세션 훅](/ko/packages/laravel-copilot-sdk/hooks) — 루프 내 이벤트 인터셉트(권한, 도구)


## Related topics

- [커스텀 에이전트](/ko/packages/laravel-copilot-sdk/custom-agents.md)
- [Boost의 커스텀 에이전트 만들기](/ko/advanced/boost-custom-agent.md)
- [Laravel Agent Detector — AI 에이전트 감지 패키지](/ko/blog/agent-detector-introduction.md)
- [Laravel PAO — AI 에이전트용 출력 최적화 도구](/ko/blog/pao-introduction.md)
- [laravel/agent-skills — Laravel 공식 AI 에이전트 스킬 모음](/ko/blog/agent-skills-introduction.md)
