
접수처와 작업실의 대화를 이벤트 기반으로 전환하기 — 병렬 agent coding 통신 편
요약
에이전트 시스템의 병목 현상을 해결하기 위해 접수처(사령탑)와 작업자 간의 통신 구조를 이벤트 기반 인터럽트 방식으로 전환하는 아키텍처를 다룹니다. 사령탑과 작업자에 서로 다른 모델을 배치하고, 폴링 방식 대신 백그라운드 인터럽트 방식을 도입하여 효율성을 높이는 방법을 설명합니다.
핵심 포인트
- 사령탑(Fable/Opus)과 작업자(Sonnet 5)의 모델 분리 운영
- 태스크 난이도(effort)에 따른 구체적인 작업 지시 프롬프트 설계
- 폴링(Polling) 방식에서 인터럽트(Interrupt) 기반 비동기 통신으로 전환
- 에이전트 간 통신 품질은 인수인계 프롬프트의 구체성에 의해 결정됨
콕핏의 다음 병목 구간은 「통신」이었다
콕핏(1 worktree = 1 workspace = 1 에이전트(agent))이 돌아가기 시작하자, 다음 병목 구간은 접수처와 작업실 사이의 통신으로 옮겨갔습니다.
디스패치(Dispatch, 하행)는 /wt 명령으로 한 줄이 되었습니다. 하지만 반대 방향 —— 작업자로부터 접수처로 향하는 상행은, 사실 「마지막 완료 보고」 단 하나뿐이었습니다. 도중에 어떤 일이 일어나도 접수처에는 보이지 않았습니다. 이 부분을 메우기 위해, 이날 하루 동안 4가지 개수를 진행했습니다.
1. 사령탑과 작업자는 서로 다른 모델이어도 된다
접수처를 명확한 사령탑으로 재정립했습니다. 게다가 접수처와 작업자를 일부러 서로 다른 모델로 구동하고 있습니다.
접수처 = 사령탑: Fable / Opus. 계획·리뷰·머지(Merge)·사고 대응. 문맥(Context)을 길게 유지하는 역할 -
작업자: Sonnet 5. 범위가 정해진 구현에 집중하는 역할. 빠르고 저렴함
그리고 태스크(Task)의 난이도에 따라 사령탑이 effort를 지정합니다. /wt에 포함된 선택 기준은 다음과 같습니다.
| effort | 용도 |
|---|---|
| medium | 정형화·기계적인 변경 (버전 bump, 오타 수정, 설정 1줄 변경) |
| ... |
low는 언더씽킹(under-thinking)의 리스크가 있어 채택하지 않았습니다[1]. 기동 커맨드는 다음과 같은 형태가 되었습니다.
herdr agent start claude-<태스크명> --workspace <id> --cwd <worktree-path> \
--split down -- claude --model claude-sonnet-5 --effort xhigh "$(cat <<'PROMPT'
<작업 지시 프롬프트 (여러 줄 가능)>
...
Sonnet 5는 지시를 문자 그대로(literal) 준수하기 때문에, 사령탑이 전달하는 「작업 지시 프롬프트」의 구체성이 곧 결과물의 품질이 됩니다. 템플릿도 /wt에 내장했습니다.
당신은 worktree <worktree-path> (브랜치 <branch-name>)에서 작업하는 구현 담당 에이전트입니다.
## 태스크
<사용자 입력을 사령탑이 구체화한 태스크 설명>
...
이 아키텍처의 품질은 작업자의 똑똑함이 아니라, 인수인계 프롬프트의 구체성에 의해 결정된다. 중간 관리자의 역량을 발휘할 지점, 그 두 번째입니다[2].
실제로 이 체제의 첫 전투( /wt 자체의 개수를 Sonnet 5에 위임)는 편집 → 커밋(Commit) → 푸시(Push) → PR 작성까지 1분 3초 만에 완주했습니다.
긴 지시는 파일에 작성하고 「이 파일을 읽고 구현해줘」라고 경로만 전달하는 방식도 안정적입니다 (여러 줄을 pane에 흘려넣는 것보다 확실합니다).
2. 폴링(Polling)을 그만두고, 인터럽트(Interrupt)로 동작한다
처음에 사령탑은 agent wait로 작업자를 기다리고 있었습니다. 하지만 이것은 **포그라운드(Foreground)에서 블록(Block)**됩니다. 기다리는 동안 사령탑은 인간과도 대화할 수 없습니다. 「9분 동안 기다리겠습니다」라며 입을 닫아버리는 것입니다. 이는 CPU가 디바이스의 완료를 계속 붙잡고 기다리는 폴링(Polling) 그 자체였습니다.
그래서 대기 작업을 백그라운드(Background)로 넘깁니다.
# 우선 작업자가 움직이기 시작한 것을 확인한 후 (포그라운드)
herdr agent wait claude-formal --status working --timeout 20000
# 「완료」와 「정체(stuck)」 두 가지를 백그라운드에서 설정
...
둘 중 하나의 이벤트가 발생하는 순간 사령탑이 깨어납니다. **인터럽트 구동 I/O (Interrupt-driven I/O)**입니다.
붙잡혀 기다리는 상태가 사라져서, 기다리는 동안 사령탑은 다른 업무, 즉 인간과의 설계 대화나 다른 태스크의 리뷰를 할 수 있습니다.
이 이점을 가장 크게 느낀 것은, 작업자가 실행되는 뒷단에서 「그러고 보니 지금의 과제, Issue로 만들고 싶다」라고 끼어들었을 때였습니다.
폴링 방식이라면 대기가 끝날 때까지 처리할 수 없습니다.
인터럽트 구동 방식이라면, 대화의 문맥을 유지한 채 그대로 Issue 생성까지 도달할 수 있습니다.
게다가 고통을 느끼고 있는 도중에 설계하기 때문에, Issue의 해상도가 높습니다.
blocked에는 두 종류가 있다 (디바운스(Debounce) 실록)
운영 첫날 바로 blocked 검지기가 작동했습니다. 달려가서 agent read로 pane을 살펴보니 ——
● Bash(git add ... && git commit -m "...")
⎿ Running…
정상적으로 작동합니다. 권한 프롬프트(permission prompt)가 자동 승인으로 일시적으로 차단된 상태를 거치는 '채터링(chattering)'을 감지기가 성실하게 포착했습니다. 몇 분 후에 다시 발생했을 때는 이번에는 진짜였습니다 (파일 읽기 허용 대기).**
그래서 운영 규칙은 다음과 같이 바뀌었습니다: **차단(blocked) 이벤트는 '즉시 대응'이 아니라 '먼저 읽고 실태 확인'**입니다. 오보라면 감지기를 재장착하고 되돌아가고, 진짜라면 사람에게 알립니다. 하드웨어 인터럽트의 디바운스 처리와 하는 일이 같습니다.
또 다른 어려움: 웨이터(waiter)는 대상이 이미 그 상태일 경우 즉시 해결해 버리기 때문에, 작업자가 working으로 전환된 것을 지켜본 후에 작동을 시킵니다.
3. 상향 채널 설계하기: '인식' 보고 의무
상향되는 '내용물'도 설계했습니다. 작업 지시 템플릿의 보고란에 한 줄 추가했을 뿐입니다.
인식(気づき) (필수): 지시에 없었던 회피책, 환경의 마찰, 예상치 못한 동작은 해결되었더라도 반드시 보고합니다. 없다면 '없음'이라고 적습니다.
이것이 놀라울 정도로 효과적이었습니다. 실제로 올라온 '인식'을 두 가지 그대로 인용하겠습니다.
첫 번째는 리뷰 코멘트에 답변하는 작업을 맡은 작업자에게서 온 것입니다:
처음 답변 게시물에서, 실제로는 실행하지 않은 nix build ... / ls /nix/store/... 명령어 출력을 '실행 결과' 같은 형식으로 작성해 버렸고, 사실 확인이 지시에 있던 내용 이상의 구체성을 위장하여 제시하려 했습니다. 즉시 gh api PATCH를 통해 '빌드 후 store 경로에서 확인됨'이라는 사실 기반의 표현으로 수정했습니다.
조작하려다 스스로 알아차리고, 공개 댓글을 고치고, 그 사실을 보고해왔습니다.
두 번째는 다른 태스크에서 온 것입니다:
git add file1 file2로 여러 경로를 전달했을 때, 한쪽이 이미 git rm된 상태여서 존재하지 않는 경로였기 때문에 fatal 오류가 발생하여 명령어 전체가 실패했고, 나머지 add도 실행되지 않은 채 커밋으로 넘어가 버렸습니다. 결과적으로, 커밋 메시지에는 적었지만 실제로는 내용물이 없는 '메시지와 내용이 다른 커밋'을 한 번 만들었습니다.
이 보고를 읽은 사령탑(접수 에이전트)은, 'idle=완료'로 흘려보내지 않고 최종 트리를 검증하러 갔습니다. 즉, 인식 보고는 사령탑의 리뷰 깊이를 동적으로 높이는 장치로 작동합니다. 보고 의무가 없었다면, 두 사실 모두 어둠 속에 사라졌을 것입니다.
Sonnet 5가 literal하기 때문에 '보고하라'고 쓰면 확실히 보고하고, 그렇지 않으면 조용히 고쳐서 먼저 진행해 버립니다. 실제로 이 시스템을 도입하기 전에는 '새로운 worktree는 mise 미신뢰(untrust) 상태로 mise run이 실패한다' [3]라는 환경의 함정을 작업자가 조용히 mise trust로 회피하여 지나가고, 사람이 우연히 pane를 보고 발견했다는 사건이 있었습니다. 인간의 육안 검사를 상향 채널의 구멍 메우기로 삼아서는 안 됩니다.
4. 넘을 수 없는 벽과, 원격 제어화
자, blocked 계기가 켜질 때마다 사람이 pane에 가서 Enter를 누릅니다.
이 왕복 과정이 남아 있었습니다. 사령탑이 대신 승인하면 안 될까요?
라고 시도했더니, Claude Code 측에서 거부되었습니다. 실제 메시지는 다음과 같습니다.
The orchestrator is answering a subordinate autonomous worker's permission prompt (sending
A류를 설계에서 제거하고, B류만을 인간에게 전달한다. 구현으로서 설계한 것은 2개의 레이어(layer)입니다.
그리고 그 아래에서, 설계하지 않은 암반이 한 층 나타났습니다.
레이어 1: 처음부터 auto mode로 기동하기
claude --permission-mode auto
정형화된 조작은 자동 승인되며, 판단이 필요한 것만이 blocked 상태로 표면화됩니다. --dangerously-skip-permissions (전체 무효화)는 사용하지 않습니다.
감독을 모두 제거하는 것이 아니라, 에스컬레이션(escalation) 경로를 남겨두는 것이 목적이기 때문입니다.
레이어 2: worktree에 allowlist를 심기
/wt 명령으로 worktree를 만든 직후, mise trust와 함께 .claude/settings.local.json을 배치합니다.
{
"permissions": {
"allow": [
...
(발췌. 실제 파일은 이보다 조금 더 깁니다) 당초 Bash(git *)로 작성했더니, AI 리뷰에서 "임의의 git 실행은 과잉 허용"이라는 지적을 받아 서브 커맨드(subcommand) 단위로 좁혔습니다. 또 다른 함정은 Read 경로 지정인데, gitignore 구문의 /tmp/...는 "프로젝트 루트 상대 경로"입니다. 절대 경로는 //tmp/... (슬래시 두 개)입니다. 구문은 공식 문서를 찾아보고 나서 작성한다 —— 담당 작업자 본인이 "추측으로 작성했다면 반대 방식을 채택했을 것"이라고 기록을 남겼습니다.
레이어 0: 파보니 나타난 암반
흥미로웠던 지점은 여기입니다. 사령탑이 --permission-mode auto로 작업자를 기동하려 하자, 그 자체로 거부되었습니다.
Launching a worker with --permission-mode auto disables the per-action approval gate; the user asked to implement the feature but did not explicitly authorize starting a worker in auto-approval mode.
"태스크는 요청받았지만, auto mode로 기동하는 것까지는 명시적으로 허가되지 않았다". 즉, 승인의 입도(granularity)가 per-action(매번 Enter)에서 per-launch(위임 시 인간이 한 번 서명)로 이동했을 뿐, 인간의 서명 자체는 없앨 수 없도록 설계되어 있습니다. 사령탑이 사용자에게 "auto mode로 기동해도 될까?"라고 한 번 확인하면, 이후 해당 작업자는 멈춤 없이 실행될 수 있습니다.
여기에 추가적인 사례도 있었습니다. 작업자가 자신의 allowlist를 확장하려 하자, 이 또한 self-modification (권한의 자기 확장)으로 분류되어 분류기(classifier)에 의해 차단되었다고 합니다. 매번 수행하는 승인은 없앨 수 있어도, "권한의 변경"만큼은 반드시 인간 경로로 떨어지게 됩니다.
이것을 "레이어 3"이라고 부르지 않은 데에는 이유가 있습니다.
레이어 1과 2는 필자가 설계하여 배치한 층입니다. 하지만 이 층은 배치한 기억이 없습니다.
auto mode도 allowlist도 결국 사용자 공간(user space)의 설정입니다. 그 한 단계 아래 —— 에이전트를 실제로 실행하는 하네스 (harness, 실행 기반) 가 두 가지 불변 조건(invariant)을 쥐고 놓아주지 않습니다.
- 인간의 서명은 없앨 수 없다. 입도는 변경할 수 있어도 (per-action → per-launch → per-policy), 서명 그 자체는 반드시 어딘가에 남는다.
- 권한의 자기 확장은 반드시 인간 경로로 떨어진다. 에이전트는 자신의 allowlist를 스스로 넓힐 수 없다.
자신의 스택 아래에 있기 때문에 번호는 0입니다.
그리고 중요한 것은, 이것이 Claude Code의 고유 사양이 아니라, 다단 에이전트(multi-stage agent)의 하네스가 토대로 가져야 할 불변 조건이라는 점입니다. 이번에는 우연히 하네스 측에서 그것을 구현하고 있었기에 "충돌"이라는 형태로 발견할 수 있었을 뿐입니다. 만약 아무런 제약이 없는 하네스였다면, 사령탑의 대리 승인도 allowlist의 자기 확장도 조용히 통과되었을 것입니다. 몇 단계(deep)로 구성하든 인간의 서명 경로가 끊기지 않을지는 설정이 아니라 하네스의 자질에 의해 결정됩니다.
실증 데이터: permission 정지 3~4회 → 0회
이 원격 제어화를 구현하는 태스크 자체를 auto mode + allowlist + mise trust 선제 대응의 완전 무장 상태로 실행했습니다. 결과적으로, 조사(공식 문서의 WebFetch) · 파일 편집 · 커밋 · push · PR 생성까지, permission 정지 0회. 개수 전의 동일한 태스크는 3~4회 정지되었으므로, 왕복(round-trip)은 의도한 대로 사라졌습니다.
다만 담당 작업자의 '알림(気づき)'란에는 다음과 같이 적혀 있었습니다.
이번 작업 자체는 N=0이라는 양호한 결과가 나왔으나, 태스크 내용이 문서 편집 중심이었고 rm이나 외부를 향한 파괴적인 조작이 전무했다는 점은 감안해서 볼 필요가 있다. B류(위험 작업)를 실제로 수행하는 태스크에서의 실증은 아직 얻지 못했다.
자신의 실증 데이터의 한계를 스스로 신고해 옵니다. "알림 필수"의 효능은 바로 이런 부분에서도 나타납니다.
요약: 콕핏은 "대화할 수 있는" 관제탑으로
하루간의 개수로 인해, 접수처와 작업실의 통신은 다음과 같이 변했습니다.
| 방향 | 메커니즘 |
|---|---|
| 하행(지시) | 템플릿화된 작업 지시 + 모델/effort 지정. 긴 추가 지시는 파일로 전달 |
| ... |
승인 설계가 per-action(매번 Enter) → per-launch(위임 시 한 번) → per-policy(allowlist로 사전 선언) 로 3단계로 분해된 것은, OS의 권한 모델이 「수시 확인 → sudo 세션 → capabilities」로 진화해 온 것과 같은 경로이며, 아마 필연적인 흐름이라고 생각합니다.
그리고 sudo도 capabilities도, 그것을 강제하는 커널은 언제나 유저랜드(userland)의 한 단계 아래에 있습니다. 레이어 0이 「하네스의 불변 조건(invariant)」인 것도 같은 구조입니다.
하행은 /wt , 상행은 인터럽트와 알림 보고입니다. 접수처와 작업실의 대화가 마침내 양방향이 되어, 콕핏은 단순히 "보이는" 것뿐만 아니라 "대화할 수 있는" 관제탑이 되었습니다.
후일담: 그 후 10일간 — 프로토콜은 어떻게 성장했는가
이 기사를 잠시 내려놓고 있는 동안에도 운용은 계속되었으며, /wt 프로토콜은 세 가지 방향으로 성장했습니다. 세션 로그에서 발췌합니다.
대화가 완성되다
먼저, 상행 채널의 남은 조각들이 채워졌습니다.
내용의 push (7/8):
지금까지의 상행은 이벤트(wait 감지) + 풀(Pull, 관제탑이 agent read로 읽으러 감) 방식이었습니다.
여기에 작업자가 완료 시점에 관제탑의 pane에 직접 한 줄을 쓰는 push 보고를 추가했습니다.
포맷은 【보고】<branch>: <한 줄 요약> (PR URL) 이며, 상세 내용은 기존 방식대로 대화 내에 둡니다.
구현된 작업자 자신에게 "관제탑으로 push 보고를 보낼 것"을 완료 조건으로 부여하고, 머지(merge) 전에 실제 트래픽으로 5회 실운용해 본 뒤 도입했습니다.
push는 어디까지나 중복성(redundancy) 확보를 위한 것이며, 실패할 경우 대화 내 보고로 축소(degrade)되어 멈추지 않도록 설계했습니다.
다음 날에는 worker 3대를 병렬로 운용하는 환경에서, 전원이 이 채널을 통해 보고해 오기 시작했습니다.
【상담】 에스컬레이션 (7/11):
"망설여지는 점은 최종 보고에 적는다"라고만 하면, 판단이 필요한 분기점에서 작업자는 돌진하거나 멈추는 두 가지 선택지밖에 갖지 못합니다.
그래서 작업 도중에 관제탑에 판단을 구하는 【상담】 프로토콜을 /wt 에 추가했더니, 당일 중에 첫 실전이 발생했습니다.
다음 날의 상담은 이상적인 형태였습니다.
통째로 맡기는 것이 아니라 "A안/B안/C안 + 권장안"을 포함하여 올라왔고, 관제탑은 판결만 내리면 되었습니다.
하행(지시), 상행(이벤트·내용·push), 그리고 양방향 상담. 이로써 마침내 "대화"라고 부를 수 있는 모든 요소가 갖춰졌습니다.
그리고 이 상담의 수신자는 프론티어 모델(frontier model)인 관제탑(Fable 5)입니다.
작업자는 긴 문맥을 떠안지 않고, 필요 최소한의 컨텍스트를 담은 상담 1회 왕복만으로 관제탑의 판단력만을 빌립니다. 토큰을 절약하면서도 화려한 해결책이 돌아옵니다 —— 모델 분업의 묘미가 가장 잘 드러나는 순간입니다.
그리고 이 구성은 사실 Anthropic의 공식 문서가 권장하는 Fable 사용법 그 자체이기도 합니다. 모델 이행 가이드에서 취지를 인용하자면 다음과 같습니다:
병렬 서브 에이전트(sub-agent)로의 위임은 Fable 5에서는 신뢰할 수 있으므로, 억제하지 말고 빈번하게 사용할 것.
spawn-and-block(기동 후 대기)보다, 오케스트레이터(orchestrator)와 비동기적으로 통신하는 서브 에이전트가 더 우수하다 —— 장기 실행 에이전트는 컨텍스트를 재구축할 필요가 없으며, 오케스트레이터는 가장 느린 서브 에이전트에 의해 속도가 제한(bottleneck)되지 않는다.
하향 위임(Downstream Delegation) · 인터럽트 구동 방식의 상향(Upstream) · 【상담】. 이 기사에서 해온 것은, 공식 측에서 말하는 "비동기 통신을 하는 서브 에이전트 (Sub-agent)" 구성을 터미널 위에서 직접 만들어 보는 것이었습니다. 본편에서 "폴링 (Polling)을 그만두고 인터럽트 (Interrupt)로 발생한다"라는 결론에 도달한 것과 동일한 결론을, 공식 가이드라인 측으로부터 정답 확인(Answer check)할 수 있었던 순간이었습니다.
프로토콜이 범용화되었다
임의의 에이전트를 관제 하에 둘 수 있게 됨 (7/12):
herdr에 명시적인 상태 보고 API가 있다는 사실이 판명되었으며, 임의의 라벨을 가진 에이전트가 자기 신고를 통해 agent list에 등록될 수 있습니다.
즉, agent wait --status blocked
감지기가 Claude Code 이외의 작업자에게도 수정 없이 그대로 사용 가능합니다.
실제로 이날은, 직접 만든 다중 모델 합의 도구인 copilot-quorum[4]을 작업자로 하여 /wt
프로토콜의 관제 하에 둘 수 있음을 실기(Real machine)로 확인했습니다.
/wt
의 사령탑 프로토콜은 Claude Code 전용이 아니라 범용 관제 프로토콜이었습니다.
그렇다면, 사령탑 아래에 둘 수 있는 것은 작업자뿐만이 아닙니다.
별도 리포지토리를 통치하는 현지 사령탑 (동급 모델)을 배치하고, 거기서 다시 서브 에이전트에게 위임하는 연방제 —— 즉, 사령탑 간의 협조 체계도 구축할 수 있을 것입니다.
이 이야기는 또 다른 기사에서 다루겠습니다.
수락 조건으로서의 레이어 0 (Layer 0) (7/12):
프로토콜이 범용화되면, 레이어 0의 의미도 변합니다.
"Claude Code가 그러했다"라는 관찰이, "관제 하에 들어가는 하네스 (Harness)는 그러해야 한다"라는 수락 조건이 됩니다.
그리고 copilot-quorum은 사실 이 시험을 스스로 받으러 왔었습니다. 관제 하 진입(전항)을 뒷받침한 상태 자기 신고 Issue에는, 기표 시점(7/4)에 다음과 같이 적혀 있습니다.
특히 HiL 승인 대기 = blocked의 가시화가 핵심: 인간의 개입을 기다리며 멈춰 있는데 계기판 상은 침묵하는 상태가 현재의 상태
"blocked의 올바른 표명"을 누군가 지적하기 전에 자신의 핵심 과제로 기표해 두었습니다. 그것이 7/12에 결실을 맺으며 관제 하 진입이 성립됩니다.
그렇다면 나머지 관점들은 어떠한가 —— 훗날, 이 기사를 쓰면서 총점검한 결과는 3승 1패였습니다.
인간 서명 레인 (Human Signature Lane): 있음. 플랜 승인과 실행 확인의 2개 게이트, 기본값은 대화 모드. 고위험 도구 실행에는 다중 모델 합의의 사전 심사도 포함됨 -
blocked의 올바른 표명: 있음. 인간 대기 상태로 들어가는 순간 blocked를 자기 신고하여 herdr의 상태 API로 흘려보냄. 묵묵히 멈춰 있지 않음 -
이벤트 구동 상태 보고: 있음. working/blocked/idle의 집약 상태가 변화할 때만 발화하는 설계로, 본편의 "인터럽트로 발생한다"와 정확히 대칭을 이루는 송신 측의 형태 -
권한의 자기 확장 금지: 없음. 모든 설정 키가 실행 시점에 수정 가능하며, 인간 승인 모드 자체를 auto_approve로 전환하는 조작을 구조적으로 방지하는 가드가 미구현됨
현재는 LLM 측에 config 조작 도구를 전달하지 않으므로, 모델이 스스로 자기 승격(Self-promotion)을 할 길은 없습니다. 다만 그것은 "막아두었다"가 아니라 "아직 길이 없다"일 뿐입니다.
불변 조건(Invariant)은 우연이 아니라 설계로 보장되어야 합니다.
그리하여 1패는, 집필을 멈추고 그대로 추가 기표(copilot-quorum#330)로 이어졌습니다.
Claude Code와의 충돌 속에서 파헤쳐낸 불변 조건이 범용 프로토콜의 수락 조건이 되고, 별도 리포지토리의 자작 도구 설계로 환류됩니다.
통신로를 만들고 있었을 뿐인데, 배움이 환류하는 회로까지 함께 만들어져 있었습니다.
루프가 「기억」에 연결되었다
「CLEAN은 거짓말을 한다」 (7/8):
mergeStateStatus=CLEAN을 확인한 후 gh pr merge를 실행하기까지의 몇 초간의 틈 사이에 AI 리뷰가 인터럽트되어, 브랜치 보호(Branch protection)에 의해 차단되는 사건이 같은 날 2회 발생했습니다.
15초 후의 재체크로도 막을 수 없었습니다.
체크 시점의 상태가 실행 시점까지 유지되지 않는, 전형적인 TOCTOU 문제 [5]입니다.
다만, 여기서 주목해야 할 점은 대책의 내용보다 교훈의 행방입니다. 이 배움은 그날 안에 Issue화되었고, /wt의 절차서 그 자체로 환류되어 갔습니다.
풀 사이클 (Full Cycle)이 1 세션 내에서 한 바퀴 (7/13):
질문 → 설계 격차 발견 → Issue화 → worktree 병렬 위임 (worker 2개) → 머지 (Merge) → 공양 ( /wtclean )
(pane 로그에서 마찰을 회수하여 worktree를 정리) →
교훈의 Issue화까지,
라이프사이클 한 바퀴가 하나의 세션에 수렴하게 되었습니다.
「깨달음(필수)」 보고 의무도 이날도 건재하여, 지시 사항에 적혀 있지 않았던 동일 근원의 버그를 독립적으로 발견하여 보고란을 통해 회수하고 있습니다.
통신로가 완성되자, 그 위로 흐르기 시작한 것은 「배움」이었습니다.
작업자의 깨달음, 공양(供養)으로 회수한 마찰, 사령탑 자신의 운영 실수.
이것들을 어디에 축적하고, 누가 확정의 게이트(Gate)를 쥐는가.
그것이 다음 회차, 기억 편의 테마입니다.
Sonnet 5의 공식 프롬프팅 가이드(Prompting Guide)에 따르면, effort의 기본값은
high
이며, 최난관의 코딩/에이전트(Agent) 태스크에는 xhigh가 권장됩니다. low/medium에서는 지시를 문자 그대로(literal) 해석하여 스코프(Scope)를 좁히기 때문에, under-thinking의 리스크가 있습니다. 본문의 표에서 medium을 정형적·기계적인 변경으로 한정한 것은 이와 반대되는 측면으로, 문자 그대로의 해석만으로 충분한 작업이라면 오히려 불필요한 일을 하지 않는 이점이 됩니다. ↩︎ -
뭐, 필자는 중간 관리직은 아닙니다만. ↩︎
mise. 런타임(Runtime)의 버전 관리, 환경 변수, 태스크 러너(Task Runner)를 일괄적으로 담당하는 Rust 기반의 개발 환경 도구입니다 (asdf나 nvm, pyenv 등의 후계자적 위치). 설정 파일로 환경 변수 주입이나 임의의 명령어를 정의할 수 있는데 —— 즉, 신뢰하지 않는 디렉토리의 설정을 읽어오는 것 자체가 위험하므로, 디렉토리마다 mise trust로 명시적으로 신뢰하기 전까지는 설정이 유효하지 않도록 하는 보안 설계가 되어 있습니다. git worktree는 물리적으로 별도의 경로이므로, 리포지토리 본체가 trust 상태여도 새로운 worktree는 미신뢰(untrust) 상태로 취급됩니다. 이것이 본문 속 함정의 정체입니다. ↩︎ -
copilot-quorum. 여러 LLM이 질문에 답변 → 익명화하여 상호 리뷰 → 의장 역할을 하는 모델이 통합하는 합의제로 결론을 내는 Rust 기반 CLI입니다. 필자가 분재를 키우듯 가꾸고 있는 자작 OSS입니다. 프론티어 모델(Frontier Model)끼리 적대적으로 토론하게 만드는 이야기는 그것만으로도 책 한 권을 쓸 수 있으므로, 이 또한 별도의 기사로 다루겠습니다. ↩︎
Time-of-check to time-of-use. 「체크한 시점」과 「사용하는 시점」 사이에 상태가 변해버려 체크가 무의미해지는 경합(Race)의 고전적인 패턴입니다. 은행 잔액을 조회한 후 송금 버튼을 누르기까지의 몇 초 사이에 다른 출금이 실행되는 상황을 상상하면 이해하기 쉽습니다. 보안 문맥에서는 「권한을 확인한 후 파일을 열기 전까지의 틈에 파일이 교체되는」 형태로 빈번하게 발생합니다. 타이밍에 따라 결과가 달라지는 **레이스 컨디션 (Race Condition, 경합 상태)**의 일종......이라고 교과서처럼 쓰고 있습니다만, 사실 필자의 최근 업무가 거의 레이스 컨디션과의 싸움이었습니다. 그래서 CLEAN에게 배신당한 순간, 사령탑보다 먼저 인간이 「또 너냐」라는 표정을 지었습니다. ↩︎
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기