에이전트 루프
Copilot CLI가 사용자 메시지를 어떻게 end-to-end로 처리하는지(프롬프트 전송에서session.idle까지) 설명합니다.
아키텍처
SDK는 트랜스포트 계층입니다. JSON-RPC로 Copilot CLI에 프롬프트를 보내고, 이벤트를 앱에 중계합니다. 에이전트적인 도구 사용 루프를 실행하고, 태스크가 완료될 때까지 1회 이상의 LLM API 호출을 오케스트레이션하는 것은 CLI 쪽입니다.도구 사용 루프
session.send({ prompt })를 호출하면 CLI는 다음 루프에 진입합니다.
모델은 매 호출마다 전체 대화 이력(system prompt / user message / 지금까지의 모든 도구 호출과 결과)을 참조합니다.
중요: 이 루프의 1회 반복은 LLM API 호출 1회와 완전히 일치하며, 이벤트 로그상에서는 assistant.turn_start / assistant.turn_end의 1쌍으로 보입니다. 숨겨진 호출은 없습니다.
Turn(턴)이란 무엇인가
Turn은 LLM API 호출 1회와 그 결과를 의미합니다.- 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()로 최초 프롬프트를 전송 |
| 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 호출 총수와 일치합니다. 계획, 평가, 완료 확인의 숨겨진 호출은 없습니다.
세션의 턴 수 확인 예: