Headless Claude Code가 질문하고 종료한 후 답변을 받아 재개하는 과정
요약
본 글은 코딩 에이전트가 사람의 결정을 기다릴 때, Claude Code를 사용하여 어떻게 프로세스를 일시 중지하고 재개하는지에 대한 기술적 가이드입니다. 오케스트레이터는 세션 ID와 상태 파일을 유지하며, 에이전트는 `ask_question`을 통해 질문을 올리고 `status: "waiting"`으로 종료합니다. 답변은 동일한 세션을 재개하여 다음 프롬프트로 전달됩니다.
핵심 포인트
- 에이전트가 결정을 요청할 때 프로세스를 멈추고, 나중에 재개하는 워크플로우를 설명합니다.
- 오케스트레이터는 세션 ID와 상태 파일을 관리하며, 에이전트는 `status: "waiting"`으로 종료됩니다.
- Claude Code의 `ask_question`은 티켓에 양식 카드를 생성하고 구조화된 출력을 강제합니다.
- 세션 재개 시 동일한 세션을 사용하므로, 비용 및 슬롯 관리가 중요합니다.
제 코딩 에이전트가 저에게 결정을 요청해야 할 때, 티켓에 질문을 올린 다음 멈춥니다. 프로세스가 종료됩니다. 제가 답을 하면 같은 세션 ID로 claude -p --resume 명령어를 사용하여 다시 시작하고, 제가 선택한 값들이 다음 프롬프트로 도착합니다.
제약 사항은 이렇습니다. 기본적으로 오케스트레이터는 한 번에 하나의 에이전트만 실행하며, 한 번의 실행은 240분으로 제한됩니다. 제가 알림을 읽기 위해 유휴 상태로 있는 세션은 이 시간을 모두 소모할 수 있습니다.
저는 Oizom의 소프트웨어 팀을 이끌고 있는 Panth입니다. 이것은 ticket-tracker에서 가져온 내용으로, 제가 만든 오픈 소스 보드이며 여기서 헤드리스 Claude Code 세션이 사람 옆에서 티켓별로 작동합니다. 오케스트레이터는 workspaces/ 폴더입니다: 보드당 하나의 프로세스, 티켓당 하나의 Claude Code 세션입니다.
루프(The loop)
README에는 다음과 같이 그려져 있습니다:
To do ──orch claims──▶ In progress ──agent reports "review"──▶ Review ──owner──▶ Done
│ ▲
agent asks a │ │ the answer (form or a comment) resumes
...
'waiting'은 보드의 단계가 아닙니다. 티켓은 계속 In progress 상태를 유지합니다. 이는 오케스트레이터가 세션 ID와 함께 자체 상태 파일에 유지하는 단계입니다.
에이전트에게 지시된 내용
모든 에이전트는 동일한 간결한 안내를 받습니다. 그 중 3단계는 다음과 같습니다:
만약 실제 결정이 오너가 내려야 하는 것이라면 (범위, 디자인, 파괴적이거나 외부로 향하는 모든 것),
blocking: true와 실제 옵션을 사용하여ask_question을 호출합니다. 그런 다음 작업을 멈추고status: "waiting"과question_id를 사용하여 자신의 차례를 마칩니다. 답변을 폴링하지 마십시오. 답변이 도착하면 오케스트레이터가 이 동일한 세션을 재개합니다.
ask_question은 티켓 스레드에 양식 카드를 만듭니다: 제목, 선택적 컨텍스트, 그리고 1개에서 10개의 필드(단일 선택, 다중 선택, 텍스트, 숫자, 예/아니오, 날짜). 블로킹 질문은 티켓 카드에
에이전트는 스스로 종료되었다고 결정하지 않습니다. 마지막 메시지는 --json-schema로 강제되는 구조화된 출력입니다:
const OUTCOME_SCHEMA = {
type: 'object',
properties: {
...
턴을 종료하는 세 가지 방식이 있으며, 각각은 오케스트레이터에게 다른 의미를 가집니다.
| status | 다음 오케스트레이터 동작 |
|---|---|
review | 티켓을 검토(Review)로 이동시키고 중지합니다. |
| ... |
마지막 두 가지의 차이가 중요합니다. waiting은 사람이 필요하다는 의미입니다. 장기화된 티켓에서 blocked는 거의 항상 턴이 공간 부족으로 종료되었다는 것을 의미하므로, 오케스트레이터는 아무도 요청하지 않고 제한된 횟수만큼 이를 재개합니다.
세션이 기다리지 않는 이유
각 실행은 하나의 claude -p 프로세스입니다. 이것이 종료되면, 오케스트레이터는 해당 턴에 소요된 비용을 담은 영수증(receipt)을 게시하고 슬롯을 비워 다음 '할 일(To do)' 티켓이 시작할 수 있게 합니다. maxAgents: 1의 경우, 저를 기다리는 세션이 유일한 슬롯을 점유하게 됩니다. 보드의 다른 모든 티켓은 제 받은 편지함 뒤에서 기다리게 됩니다.
브리프에는 두 번째 이유가 있습니다. 이 세션은 헤드리스(headless)하기 때문에, 턴이 끝나면 프로세스가 종료되고 백그라운드 작업까지 함께 가져갑니다. 턴은 작업을 완료하거나 깔끔하게 끝납니다. 나중에 제가 찾을 수 있는 반쯤 살아있는 프로세스는 없습니다.
그리고 아무것도 폴링(polling)하지 않습니다. 에이전트에게 그렇게 하지 않도록 지시받았기 때문입니다. 오케스트레이터도 타이머에 앉아있지 않습니다. 보드의 수정 사항(revision)에 따라 깨어나고, 답변은 이벤트로 도달합니다.
답변이 돌아오는 방식
제가 양식(form)을 제출하면, 트래커는 에이전트의 받은 편지함에 question_answered 이벤트를 넣습니다. 오케스트레이터는 받은 편지함을 비우고, waiting 상태의 티켓에 대한 이벤트를 확인한 후, 제 답변으로 구성된 프롬프트와 함께 세션을 재개합니다:
answered: (t, q) => `The owner answered your question "${q.title}" on ${t.key}.
Values: ${JSON.stringify(q.answer?.values ?? q.values ?? {})}`
...
재개 자체는 하나의 플래그입니다. 해당 ID의 세션 파일이 여전히 존재하면, 오케스트레이터는 --resume <id>를 전달합니다. 그렇지 않으면, 새로운 세션을 시작하고 비용을 0으로 재설정합니다.
...(resume ? ['--resume', sessionId] : ['--session-id', sessionId]),
에이전트는 읽었던 파일, 계획, 질문 등 전체 컨텍스트를 가지고 재개됩니다. 어디까지 진행했는지 알아내기 위해 저장소(repo)를 다시 읽을 필요가 없습니다.
몇 가지 예외 상황들이 처리됩니다:
- 양식 대신 댓글. 카드를 작성하는 대신 스레드에 답장하면, 이 역시 세션을 재개하며 내 메시지가 프롬프트에 포함됩니다.
- 슬롯 없음(No slot free). 다른 티켓이 진행 중인 경우, 해당 티켓의 상태 라인은 "답변을 받았습니다 · 에이전트가 사용 가능할 때까지 대기 중"이라고 표시되므로, 답변된 질문은 결코 무시된 것처럼 보이지 않습니다.
- 취소 또는 만료(Cancelled or expired). 프롬프트에 그 내용이 명시되며, 에이전트에게 스레드를 읽고 가장 보수적인 옵션을 진행하거나 다시 질문하도록 지시합니다.
- 누락된 이벤트(A missed event). 네 번째 루프마다 대기 중인 티켓의 질문이 이벤트를 잃어버린 경우에 대비하여 직접 가져와집니다.
- 답변 자체가 진척입니다(The answer is progress). 이는 티켓의 자동 계속 카운트(automatic-continuation count)를 0으로 재설정하며, 내가 남긴 모든 댓글도 마찬가지입니다.
재개 시 비용 투명성 유지
Claude Code는 --resume을 통해 total_cost_usd를 누적하여 보고합니다. 질문 두 개를 한 세션에서 진행했을 경우, 보고된 총합을 단순히 합산하면 첫 번째 턴의 비용이 세 번 계산됩니다. 따라서 특정 턴에 대한 영수증은 해당 세션의 마지막 보고된 총액과의 차액이며, 세션의 누적 총액은 오케스트레이터(orchestrator)의 상태에 저장됩니다.
저에게 드는 비용
질문 하나를 하는 것은 제가 자리를 비운 동안 컴퓨팅 비용이 들지 않습니다. 프로세스를 활성 상태로 유지하는 것이 아니기 때문입니다. 가격은 지연 시간(latency)입니다. 티켓은 제가 답변할 때까지 대기하며, 다른 어떤 것도 이를 가져가지 않습니다. 저는 그것에 만족합니다. 에이전트가 질문하는 내용은 제가 결정한 것들입니다.
당신의 에이전트 설정은 스스로 답할 수 없는 질문을 어떻게 처리하나요: 세션을 기다리게 하나요, 아니면 턴을 종료하고 돌아오게 하나요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기