실행 lane이 늘어날수록 중요한 질문은 “누가 했는가”가 아니다. “무엇으로 증명되는가”이다.
Thesis 에이전트 조직에서 실행자와 검증자를 분리하지 않으면 “완료 보고”가 가짜 증거가 된다.
금요일 저녁에 Joe가 “테스트 통과, 배포 준비 끝”이라고 슬랙에 한 줄 남긴다. 깔끔하다. 믿고 싶다. 그런데 그 문장 안에는 실제 테스트 출력도, 실제 diff도, 실제 서비스 동작도 들어 있지 않다. 그냥 문장이다. 운영 사고는 대개 이 한 줄을 의심하지 않는 데서 시작한다.
이 글은 그 한 줄을 어떻게 다루는지에 대한 글이다. pjy.pizza를 굴리면서 가장 많이 고친 부분이 바로 여기였다.
1. Hermes 본 대화는 실행 장소가 아니라 조율 장소다
Hermes의 Joe가 모든 것을 한 턴 안에서 직접 끝내려고 하면 운영은 금방 흐려진다. 코드 수정, 긴 빌드, 원격 코딩 세션, 작업자 profile별 실행, PR 검토, CI 확인은 시간 감각도 다르고 실패하는 방식도 다르다. 코드 수정은 초 단위로 끝나고, 빌드는 몇 분을 잡아먹고, 원격 세션은 다른 표면에서 비동기로 돈다. 이걸 한 대화창에서 전부 손에 쥐고 있으면 “지금 무엇을 기다리는가”, “어떤 변경점이 실제인가”, “누가 검증했는가”가 한 덩어리로 엉킨다. 그리고 엉킨 상태에서는 “됐겠지”가 가장 편한 답이 된다.
그래서 실행을 lane으로 나눈다. 역할을 먼저 못 박아두면 나중에 의심할 지점이 명확해진다.
- Codex는 코드 변경과 명령 실행을 맡는 lane이다.
- Happy는 원격 코딩 표면과 완료 웹훅을 제공하는 lane이다. 모바일이든 웹이든, 본 대화창 바깥에서 손이 움직이는 곳이다.
- Kanban은 작업의 지속 상태, 담당 profile, 이벤트 로그를 남기는 제어판이다. 대화가 끝나도 남는 기억이다.
- Hermes의 Joe는 이 세 lane 위에 있는 판단 계층이다.
Joe가 판단 계층이라는 말은 손을 안 댄다는 뜻이 아니다. 실행 결과를 곧바로 “완료”로 받아들이지 않는다는 뜻이다.
여기서 핵심은 사람과 AI를 가르는 분리가 아니다. 실행자와 검증자의 분리다. Codex가 변경했다고 끝난 게 아니고, Happy가 success를 보냈다고 끝난 게 아니며, Kanban 카드가 done으로 움직였다고 끝난 게 아니다. 셋 다 “이제 검토를 시작하라”는 신호일 뿐이다. 완료가 아니라 입구다.
2. 실행 lane의 최소 계약
실행 lane은 “고쳐줘”라는 문장을 받는 곳이 아니다. 입력, 경계, 산출물, 검증 방식이 붙은 계약을 받아야 한다. Joe가 lane에 넘겨야 하는 것은 요구사항 원문이 아니라 완료 기준이다. 무엇을 바꾸는지, 어떤 파일 범위까지 허용되는지, 어떤 테스트가 최소 증거인지, 어떤 상황에서 멈춰야 하는지를 먼저 정리해서 넘긴다. 이 정리를 건너뛰면 lane은 자유도가 너무 높아지고, 자유도가 높은 실행은 검증이 불가능한 산출물을 만든다.
각 lane이 어떤 증거를 남겨야 하고 무엇을 믿지 말아야 하는지를 한 장에 고정해두면, 다른 조직에서도 그대로 베껴 쓸 수 있다.
| Lane | 맡는 일 | 반드시 남길 증거 | 신뢰하지 않는 것 | 멈춤 조건 |
|---|---|---|---|---|
| Codex | 로컬 코드 수정, 테스트 실행, diff 생산 | git diff, 명령 출력, 테스트 로그, 작업 디렉터리 | “수정 완료”라는 자연어 보고 | 작업 범위 밖 파일 변경, 인증/권한 문제, 파괴적 명령 |
| Happy | 원격 코딩 세션, 모바일/웹 작업 표면 | 완료 웹훅, 세션 기록, 결과 JSON, 변경점 | success 이벤트 단독 | 웹훅은 왔지만 산출물 경로가 불명확한 상태 |
| Kanban | 작업 지속 상태, 담당 profile, 이벤트 로그 | 카드 상태, heartbeat, 실행 로그, blocker 기록 | done 컬럼 자체 | heartbeat 정체, DB 이상, profile 준비 실패 |
| Joe | 조율과 검증 | 원래 완료 기준과 원천 증거의 대조 | 작업자 자기 보고 | 근거가 없거나 승인 경계를 넘는 상태 |
이 표의 목적은 도구를 비교하는 게 아니다. 각 도구가 어떤 증거를 남겨야 하는지를 못 박는 것이다. lane이 많아질수록 사람은 “어딘가에서는 제대로 됐겠지”라고 믿고 싶어진다. 그 믿음이 운영 사고의 시작이다. 그래서 “신뢰하지 않는 것” 칸을 일부러 표에 넣어뒀다. 무엇을 믿지 않을지를 먼저 정해두는 편이, 무엇을 믿을지 정하는 것보다 안전하다.
3. 완료 보고가 가짜 증거가 되는 순간
가장 흔한 실패는 작업자의 문장이 증거처럼 보이는 순간이다. “테스트를 통과했다”, “수정했다”, “배포 준비가 끝났다”. 편리한 문장이다. 그러나 이 문장들은 실제 테스트 출력이 아니고, 실제 diff가 아니며, 실제 서비스 동작이 아니다. 자연어 보고는 상태 설명이지 원천 증거가 아니다. 둘을 구분하지 못하면 검증은 그냥 “보고를 다시 읽는 일”이 된다.
Codex lane에서도 같은 일이 생긴다. 작업자가 파일을 고쳤다고 말하지만, 실제로는 dirty repo 위에서 stale base diff를 만들었을 수 있다. 이미 누가 바꿔둔 파일과 섞여 변경 범위가 의도보다 훨씬 커졌을 수 있다. 테스트를 돌렸다고 말하지만 잘못된 디렉터리에서 돌렸거나, 실패 출력을 요약하는 과정에서 빨간 줄을 흘려버렸을 수 있다. Happy도 다르지 않다. 완료 웹훅이 도착했다는 사실은 “이제 검토할 결과가 생겼다”는 뜻이지, “사용자 의도대로 끝났다”는 뜻이 아니다. 웹훅은 도착했는데 산출물 경로가 어딘지 모르겠다면, 그건 완료가 아니라 미해결이다.
Kanban은 지속 상태를 주지만, Kanban 자체도 관찰 대상이다. 카드가 done으로 옮겨졌는지보다 더 중요한 건 왜 done이 되었는가이다. 실행 로그가 남아 있는가. 담당 profile이 실제로 그 일에 필요한 skill을 갖고 있었는가. heartbeat가 중간에 멈추지는 않았는가. blocker가 조용히 묻히지는 않았는가. done 컬럼은 누구나 끌어다 놓을 수 있다. 그래서 컬럼 위치는 증거가 아니다.
4. Joe의 두 단계 검토
실행자와 검증자를 분리하면 Joe의 검토도 자연히 두 단계로 갈라진다.
첫 번째는 산출물 검토다. 어떤 파일이 바뀌었고, 어떤 명령이 실행됐고, 어떤 테스트가 통과하거나 실패했는지를 본다. 손에 잡히는 것부터 확인한다.
두 번째는 의도 검토다. 산출물이 아무리 많아도 Joon이 요구한 완료 기준을 만족하지 못하면 실패다. 변경이 풍성하다는 것과 원래 부탁한 일이 끝났다는 것은 전혀 다른 사실이다.
flowchart TB Joon[Joon의 지시] --> Joe[Joe: 완료 기준으로 구조화] Joe --> Kanban[Kanban: 지속 상태] Joe --> Codex[Codex lane: 코드 변경] Joe --> Happy[Happy lane: 원격 실행] Codex --> Diff[diff / commit 후보] Happy --> Result[웹훅 / 세션 결과] Kanban --> Events[카드 상태 / 이벤트 로그] Diff --> Review1[1차: 산출물 검토] Result --> Review1 Events --> Review1 Review1 --> Review2[2차: 원래 완료 기준 대조] Review2 --> Evidence[테스트 / CI / 로그 / 스크린샷 / 실제 동작] Evidence --> Slack[슬랙 근거 보고]
이 구조에서 Joe의 역할은 “수고했다”고 답하는 게 아니다. lane별 산출물을 원래 요청과 맞대본다. 변경점이 너무 넓으면 좁힌다. 테스트가 없으면 최소 검증을 직접 다시 돌린다. 빌드가 성공했더라도 요구의 핵심이 실제 화면이나 API 동작이었다면 그 표면을 다시 연다. 작업자가 넘어가 버린 승인 경계가 보이면 멈춘다.
마지막에 슬랙으로 나가는 보고가 “끝났다”가 아니라 “이걸로 끝났음이 증명된다”가 되도록, 다이어그램의 흐름은 항상 Evidence를 거쳐 Slack으로 닫힌다. 증거를 거치지 않은 보고는 슬랙에 올리지 않는다.
5. dirty repo 현실을 피하지 않는다
에이전트 운영을 다루는 글들은 종종 깨끗한 저장소, 독립된 브랜치, 완벽한 CI를 가정한다. 실제 운영은 그렇지 않다. pjy.pizza처럼 사람이 계속 손으로 고치고, Hermes가 들여다보고, Codex가 들어오고, Happy가 다른 표면에서 동시에 실행하면 작업 디렉터리는 언제든 dirty해진다. 이 현실을 숨기면 글은 깔끔해지지만 검증은 약해진다.
dirty repo에서 필요한 태도는 “무조건 깨끗하게 만들어라”가 아니다. 깨끗하게 만들 수 없는 순간이 더 많기 때문이다. 먼저 할 일은 현재 상태를 증거로 남기는 것이다. 변경 전 상태, 의도한 파일 범위, 기존 미커밋 변경과 이번 새 변경의 구분, 테스트를 어느 위치에서 돌렸는지를 기록한다. shared checkout에서 바로 실행하면 다른 lane의 흔적과 섞이기 쉬우므로, 가능하면 분리된 작업 디렉터리나 명시된 worktree를 쓴다. 불가피하게 같은 checkout을 공유해야 한다면, 검증 보고 안에 그 위험을 솔직하게 적어둔다. “이 diff는 깨끗한 base에서 나온 게 아니다”라는 한 줄이, 나중의 혼란을 크게 줄인다.
stale-base diff illusion도 같은 자리에서 나온다. 작업자는 자기가 본 기준선 위에서 올바른 변경을 만들었다고 느낀다. 그런데 그 기준선 자체가 이미 낡았다면, 실제로 merge한 뒤의 결과는 달라진다. 그래서 diff 검토는 “내가 만든 변경이 맞나”만 보는 일이 아니다. “지금의 기준에 얹어도 여전히 맞나”를 보는 일이다. 전자는 작업자의 시야이고, 후자는 검증자의 시야다. 둘을 같은 사람이 한 번에 하려고 하면, 거의 항상 전자만 본다.
6. 운영 규칙
여기까지의 이야기를 다른 조직에 그대로 옮길 수 있게 일곱 줄로 압축한다.
- 실행 lane의 success는 완료가 아니라 검토 시작 신호다.
- 작업자 자기 보고는 근거가 아니다. diff, 테스트 출력, CI, 로그, 실제 동작이 근거다.
- Codex는 가능하면 분리된 작업 디렉터리에서 실행하고, HOME과 인증 경로를 명시한다.
- Happy 완료 웹훅은 세션 기록과 산출물 경로가 함께 있을 때만 의미가 있다.
- Kanban done은 이벤트 로그, heartbeat, 담당 profile 상태와 함께 읽는다.
- dirty repo는 부끄러운 상태가 아니라 명세해야 하는 운영 현실이다.
- Joe는 실행자가 아니라 최종 검증자다. 검증자가 사라지면 완료 보고는 곧 소설이 된다.
7. 다음 글
실행 lane은 증거를 남긴다. 다음 문제는 그 증거를 운영자의 눈으로 어떻게 읽느냐다. 테스트 로그, 카드 이벤트, 웹훅 결과가 쌓여도, 그것을 해석하는 흐름이 없으면 증거는 그냥 더미가 된다.
그래서 다음 글은 /writing/observable-surface/observability-loki-grafana-sentry로 이어진다. 실행 lane이 남긴 증거를 Loki, Grafana, Sentry가 어떤 기준 흐름으로 받아내야 하는지를 다룬다.