저는 여러 코딩 에이전트를 동시에 운영합니다. 저에게 시간을 소모하게 만드는 부분은 그들이 작성하는 코드 자체가 아니라, 제가 한 에이전트에서 다음 에이전트로 전달하는 메모입니다.
제가 손으로 이 메모를 쓸 때마다 아픈 부분을 적게 됩니다: "CSV 내보내기 시 필터가 손실됩니다." 이 문장은 정확하지만 실행자에게는 완전히 무용지물입니다. 어떤 수정 버전부터 시작해야 하는지, 어떤 명령어가 이를 재현하는지, 어떤 출력이 올바한 것으로 간주되는지, 또는 누가 이것이 완료되었다고 말할 권한을 가졌는지에 대해서는 아무것도 언급하지 않습니다.
이것이 바로 이 글의 핵심 논제이며, 같은 문제에 부딪힌 사람들의 2026년 10월 작성 글에서 비롯된 것입니다. 아래의 모든 사실적 내용은 그들(원문 저자)의 것이며, 저는 이를 재현한 것이 아니므로 제시된 숫자는 출처가 있는 주장으로 간주해 주십시오. 출처 및 고지 사항은 마지막에 있습니다.
티켓은 증상을 설명한다. 계약서는 업무를 설명한다.
첫 번째 글을 작성한 저자는 한 문장으로 요약합니다: 티켓은 버그를 정확하게 설명할 수 있지만, 실행자가 시작할 수 없게 만들 수도 있다는 것입니다.
그의 관점은 도구 비교보다 더 유용합니다. 왜냐하면 어떤 도구가 등장하기 전에 적용되기 때문입니다: 라벨이 아닌 책임을 비교해야 합니다. 작업 추적기(task tracker)는 잘 관리된 실행 시스템의 일부가 될 수 있습니다. 하지만 에이전트 러너(agent runner)는 잘못 관리될 수 있습니다. 그 차이는 인계 과정에서 네 가지 사항을 명시하는지 여부입니다.
| 차원 (Dimension) | 답변해야 할 질문 (The question it must answer) | 일반적으로 위장되는 방식 (How it usually gets faked) |
|---|
| 맥락 (Context) | 정확히 어떤 시작 수정 버전인가? 어떤 동작, 제약 조건, 허용 파일은 무엇인가? | "메인 브랜치에서 그냥 변경해"; 짧고 현재의 명세서 대신 긴 대화 내용 |
이 비대칭성에 주목하십시오. 이 네 가지 중 세 가지는 에이전트가 스스로 만들어낼 수 없는 것입니다. 특히 승인(Acceptance)은 구현자가 아닌 사람이나 역할이어야 하므로, "에이전트가 완료되었다고 말했다"는 것은 검증 과정이 될 수 없습니다.
템플릿
원래의 템플릿은 CSV 복구용입니다. 여기서는 일반화되었으며, 저자 자신의 경고가 그대로 남아 있습니다: 대괄호 안의 값들은 실제 프로젝트에서 가져와야 하며, 명령어 이름(command names)을 임의로 만들어내서는 안 됩니다.
task: 선택된 필터를 CSV 내보내기에서 보존하기
context:
repository: <승인된 저장소>
...
이것을 따라가는 세 가지 규칙 중, 두 번째 규칙은 사람들이 서두르다가 어기는 경우가 많습니다:
- 계약서에 자격 증명(credentials)을 붙여넣지 마십시오. 실행자는 자체 승인된 도구와 액세스를 사용합니다.
- 사용할 수 없는 환경은 계정을 공유하라는 초대장이 아니라 실행 차단기입니다.
- 이 템플릿은 모든 플랫폼이 모든 필드를 자동으로 채운다는 증거가 아니라, 제안된 운영 관행입니다.
untested_cases: required, even when empty는 별도의 문장이 필요합니다. 비어 있음(Empty)과 "없음(None)"은 다른 주장입니다. 전자는 정보가 누락된 것이고, 후자는 검증 가능한 진술입니다.
실제 인계(handoff)는 어떻게 이루어지는가
소스에 있는 워크스루는 인간이 어디에 속하는지를 보여주기 때문에 통째로 보존할 가치가 있습니다:
- 제품 책임자(product owner)가 테스트 환경을 구축합니다: 기록 3개, 열린 상태 2개, 닫힌 상태 1개. 예상되는 필터링된 내보내기는 정확히 두 개의 행입니다.
- 엔지니어가 동작을 확인하고 내보내기 경로를 지적합니다.
- 실행자는 먼저 기록된 리비전에서 실패를 재현해야 합니다. 만약 재현되지 않는다면, 다음 유용한 전달물은 패치가 아니라 시도한 명령어, 환경 세부 정보 및 관찰된 출력입니다. 불일치를 해결하기 전에 편집하는 것은 다른 문제를 수정할 위험이 있습니다.
- 그 후에만: 필터링된 내보내기에 대한 커버리지를 포함하여 범위가 지정된 변경을 수행합니다. 필터링되지 않은 내보내기도 확인해야 합니다. 왜냐하면 한 경로를 수리하는 것이 다른 경로를 조용히 좁힐 수 있기 때문입니다.
- 전달물은 패치 리비전과 검증된 테스트 명령어의 실제 출력을 첨부합니다.
- 두 명의 리뷰어(각기 다름): 엔지니어는 diff와 회귀 증거를 읽고; 제품 책임자는 정제된 샘플을 보고 "필터링되었다"가 사용자가 생각하는 의미인지를 확인합니다.
- 만약 두 검토 중 어느 하나라도 실패하면, 동일한 작업은 그 이유와 다음 시도를 기록합니다.
원본의 솔직한 참고 사항: 그것들은 제안된 검사들이다. 통과 결과를 주장한 것은 없었다. 그 절제는 기사의 약점이 아니라 표준의 일부이다.
휴대 가능한 버전: 5개 필드, 5개 증거 섹션
후속 게시물은 더 복잡한 현실을 다룬다: 한 팀원은 터미널 에이전트를 사용하고, 다른 팀원은 에디터 어시스턴트를 사용한다. 그들 사이에서 전송되는 것은 **작업 계약(task contract)**이다 — 입력값, 허용되는 변경사항, 필수 증거, 그리고 리뷰어. 대화 기록은 선택적 컨텍스트일 뿐이다.
outcome: reject invalid worker-count configuration before startup
scope:
allowed: config parser and focused parser tests
...
stop_when 필드는 내가 즉시 가져온 것이다. 이는 실행기(executor)에게 중지할 권한과 기록해야 할 이유를 모두 제공하며, 이는 추측하려는 자율 작업자(autonomous worker)에게서 원하는 정확한 것이다.
증거 프로토콜은 5줄로 구성된다:
Before: command, exit code, observed output, base revision
Change: files and the reason each file is necessary
After: same reproduction, focused tests, actual exit codes
...
그 게시물의 두 문장은 책상 위에 붙여두기 좋다:
"모델이 검증이 고정되었다고 말한다"는 가설일 뿐이다. 명령어와 그 출력은 검사가 실행되었음을 보여주는 증거이다.
…그리고 리뷰어는 여전히 해당 검사가 의도된 실패를 다루는지 판단해야 한다.
건너뛰게 되는 두 가지 게이트:
- 역량보다 자격이 우선한다(Eligibility comes before capability). 접근 권한은 하나의 게이트이다: 실행자는 이미 승인되어야 한다. 강력한 모델을 가졌지만 저장소 권한이 없는 팀원은 누군가가 토큰을 붙여줄 수 있다는 이유만으로 자격을 갖춘 것이 아니다.
- 솔직한 비결과(Honest non-results). 기사의 예시에서 실행기는 작업자 수가 0인 경우를 발견하는데, 이는 의도적으로 자동 크기 조정(automatic sizing)을 의미한다. 올바른 조치는 중지하고, 그 사실을 반환하며, 요구사항을 수정하는 것이지, 만들어낸 테스트를 만족시키기 위해 작동 방식을 변경하는 것이 아니다.
사전 수락 검사(Pre-acceptance checks): 기록된 개정판을 확인하고, 명시된 케이스를 재현하며, 오류 메시지에서 우발적인 설정 노출 여부를 살펴보고, 배포 또는 관련 없는 정리 작업이 diff에 포함되지 않았는지 확인한다.
인계를 객체화하기 (그리고 쓰기 작업을 보류하기)
세 번째 작성물은 반대 방향으로 진행된다. 템플릿 없이 문서 모델을 사용한다.
그 관찰 내용은 명확하다. 에이전트 간 메시지를 전달하는 것은 간단하지만, 요구사항, 결정, 결과, 검증(validation), 미완료된 작업 등을 추적하고 유지하는 것은 그렇지 않다는 것이다. 연구 에이전트가 코딩 에이전트에게 인계할 때 단순히 요약본을 보낼 수 없다. 수신자는 원래의 요구사항, 제약 조건, 이미 내려진 결정들, 결과물, 미해결 질문, 그리고 다음 단계를 모두 필요로 한다.
따라서 작업 지시서(Work Order)를 입력하고 작업 보고서(Work Report)를 반환하며, 하나의 모델과 한 세트의 검증 규칙을 세 개의 진입점—TypeScript 패키지, MCP 서버(이를 통해 Claude Code와 친구들이 도구를 사용하여 생성/수락/보고/검토할 수 있음), 그리고 동일한 공유 테스트 케이스를 실행하고 동일한 문서를 대상으로 동일한 결과를 보고하는 Python 구현체—뒤에 배치한다.
흥미로운 부분은 중간의 MCP 게이트웨이이다. 작업자는 실제 도구를 직접 건드리지 않는다. 모든 호출은 이 게이트웨이를 통과하며, 이는 각 호출마다 해당 주문(order)이 허용하는 도구와 예산을 강제하고 그 호출을 증거로 기록한다.
버전 0.7에서는 부작용 게이트(side-effect gate)—스테이징된 도구—가 추가되었다:
Staged tools: the worker's reads run straight through the gateway, but its gmail/send call is held, logged, and answered with a placeholder; the worker lists it in the report; on approve the gateway runs it once, in order
읽기는 통과하고, 쓰기는 보류되며, 승인만이 한 번 실행되도록 한다. (이미지: dev.to의 원본 게시물을 재호스팅함.)
따라 할 만한 메커니즘들:
- 읽기(Reads)는 정상적으로 실행됩니다. 스테이징된 호출만 대기하므로, 워커(worker)가 여전히 리서치를 수행할 수 있습니다.
- 쓰기(Writes)는 보류됩니다. 게이트웨이(gateway)는 해당 호출과 인수를 계산하고 기록하지만 전송하지 않습니다. 대신 워커는
pending:gmail/send#3와 같은 플레이스홀더를 받으며, 이를 보고서의 effects 아래에 나열합니다.
- 매니저가 결정합니다. 승인(Approve) → 게이트웨이는 보류된 호출들을 순차적으로 한 번 실행합니다. 다시 전송(Send it back) → 이들은 폐기되며, 아무것도 실행되지 않았으므로 되돌릴 필요가 없습니다.
작가가 실제 워크플로우에서만 나타났다고 말하는 네 가지 세부 사항:
- 부분 승인(Partial approval). 작업을 승인하되 선택된
effects만 실행합니다: effects: ["gmail/send#3"].
- 종속적 효과(Dependent effects). 나중에 보류된 호출이 이전에 발생한 호출을 참조할 수 있습니다 — 예:
{"$ref": "github/create_issue#3", "pointer": "/structuredContent/number""} — 게이트웨이는 실행 시점에 이를 채워 넣습니다.
- 최대 한 번, 조정(reconciliation)을 통해. 무언가를 실행하기 전에, 게이트웨이는 보고서의 모든
effect를 실제로 기록된 내용과 대조합니다. 불일치 → 아무것도 실행되지 않습니다. --commit을 재실행해도 충돌 후에도 절대 중복 전송되지 않습니다.
- 하위 에이전트가 우회할 수 없습니다. 워커는 스테이징된 도구를 하위 에이전트(sub-agent)에게 넘길 수 없습니다. 하위 에이전트의 쓰기는 매니저가 아무것도 확인하기 전에 _워커_가 승인할 때 실행될 것입니다.
또한 인플라이트 노트(in-flight notes)를 위한 progress()와 워커의 최초 승인, 재작업, 실패, 만료 및 사용된 예산 기록을 담은 trackRecord()도 있습니다. 워커는 그 기록으로 판단해야 하며, 자체 보고서로 판단해서는 안 됩니다.
공개 고지: 작가는 이 패키지들을 구축하고 있으므로, 중립적인 리뷰가 아닌 "무언가를 하는 사람"의 관점에서 읽어주세요.
원장(The ledger): 상태를 파생시키고, 절대 저장하지 마라
계약서 작성은 무엇을 넘겨줄지(what)를 해결합니다. 에이전트 보드(Agent Board)에 대한 기술 문서는 나머지 절반, 즉 나중에 전체 보드를 여전히 볼 수 있는지 여부를 해결해 줍니다.
문제는 에이전트가 병렬로 실행될 때 답할 수 없는 세 가지 질문입니다: 아직 작동하고 있는가, 멈춘 것인가, 그리고 구독의 얼마나 많은 부분을 소모하는가? 원래 요구사항은 더 단순했습니다—항상 정확한 단일 페이지였습니다. 저자는 왜 그것이 어려운지 설명합니다: 이것은 분산 시스템(distributed systems) 문제입니다. 에이전트는 어떤 단일 프로세스 외부에서 시작하고 죽으며, 비동기적으로 보고하고, 충돌할 때 누락으로 거짓말을 합니다.
그의 답변은 다음과 같습니다: 대시보드(board)는 투영일 뿐이며, 원장(ledger)만이 유일한 진실의 근원입니다. 네 가지 불변성(invariants):
- 생성 전에 등록한다(Register before spawn). 작업 행은 프로세스보다 먼저 존재하므로, 지원되는 경로를 통해 시작된 것은 기록되지 않고 실행될 수 없습니다.
- 알려지지 않은 것은 알려지지 않은 상태로 유지한다(Unknown stays unknown). 추정치 백분율도, 추론된 종료 코드도, 예상 사용량도 없습니다. 신뢰할 수 있는 PID가 없는 Claude 세션은
unknown을 표시하며 중지 버튼이 비활성화됩니다.
- 건강 상태는 저장하지 않고 도출한다(Derive, do not store, health). 상태(Status)는 프로듀서가 작성하는 사실이며, 건강 상태(health)는 읽기 시점에 상태와 유휴 시간(idle time)을 더하여 계산됩니다.
- 에이전트를 절대 차단하지 않는다(Never block the agent). 계측 시스템은 개방 실패(fails open) 방식을 따릅니다: 바쁜 원장은 지연 시간을 추가하는 대신 이벤트를 드롭합니다.
세 개의 프로듀서, 하나의 추가 전용 원장(append-only ledger)
- Launch wrapper. 해당 행(row)을 작성한 후, 에이전트를 JSON 이벤트 모드(JSON-event mode)로 생성합니다. 자식 프로세스가 살아있는 동안 15초마다 하트비트(heartbeats)를 보내고, 종료 코드를 기록합니다. 스폰 실패는 사라지는 대신 종료 코드 127과 함께 실패로 기록됩니다. 프롬프트가 전송되기 전에 시그널 전달(signal forwarding)이 설치되어 'stop' 신호가 자식 프로세스에 도달하게 합니다. 테스트에서는 순서를 검증하는데, 가짜 에이전트가 첫 이벤트 발생 전에 블록되고, 테스트는 레저를 폴링하여 해당 행이 이미 존재하는지 확인합니다.
- Lifecycle hooks. 세션 시작/종료, 프롬프트 제출(prompt submit), 헬퍼 시작/종료, 각 호출 전후의 도구 이벤트(tool events)가 포함됩니다. 라이프사이클 후크는 동기적(synchronously)으로 실행되며, 도구 후크는 비동기적(asynchronously)으로 실행됩니다. 각 후크는 25ms 잠금 대기 시간과 150ms 작업 예산을 할당받아 200ms 미만의 종료를 목표로 합니다. 타임아웃 발생 시 이벤트는 로깅되고 폐기되며, 항상 0으로 종료하고 stdout에 절대 쓰지 않습니다. (출력되는 모든 내용은 에이전트의 컨텍스트(context)에 주입될 것입니다.)
- Reconciler, 5초마다 실행. 프로세스가 사라졌지만 종료 코드가 없는 실행 중인 작업은 'lost' 상태가 됩니다. 실행 중인 작업이 600초 이상 유휴 상태이면 'stalled' 상태가 되어 다음 이벤트에서 복구됩니다. 또한 이 재조정기(Reconciler)는 래퍼를 거치지 않은 Codex 프로세스를 찾기 위해 프로세스 테이블을 스캔하며, 이중 계산을 피하기 위해 최대 세 단계의 부모 레벨까지 올라가서 해당 프로세스들을 PID별로 'untracked' 상태로 기록합니다.