grill-me 와 우로보로스(Ouroboros)를 비교해 달라는 요청을 받고 둘을 나란히 열어봤다. 둘 다 “에이전트 하네스” 라는 말로 뭉뚱그려 불리는데, 열어보니 크기가 우스울 정도로 다르다.

  grill-me Ouroboros
실체 마크다운 스킬 2개 Python 패키지
크기 319단어 파일 511개 / 280,757줄
버전 mattpocock/skills main (2026-08-24 push) 0.50.8 (로컬 설치본)
상태 무상태, 파일 안 남김 저널·증거 매니페스트·게이트

세 자릿수가 아니라 세 자릿수 다. 그런데 이 둘은 사실 같은 실패를 막으려 한다. 다른 쪽 끝에서.

grill-me 는 319단어가 전부다

먼저 놀란 건 grill-me 의 실체다. skills/productivity/grill-me/SKILL.md7줄이고, 본문은 사실상 한 줄이다 — grilling 스킬을 호출하라. front matter 에 disable-model-invocation: true 가 붙어 있어서, 모델이 알아서 부를 수 없고 사용자가 명시적으로 불러야만 동작한다.

실제 메커니즘은 grilling 에 있고 그게 319단어다. 요지는 이렇다.

  • 아이디어를 설계 트리로 본다. 결정 하나가 그 아래 매달린 결정들로 가지를 친다.
  • 프런티어는 선행 결정이 이미 확정된 질문들의 집합 — 아직 못 들은 답을 추측하지 않고 지금 물을 수 있는 것들이다.
  • 프런티어 전체를 한 라운드에 한꺼번에 묻는다. 번호를 붙이고, 각 질문에 자기 추천답을 같이 적는다. 그리고 사용자의 답을 기다린다.
  • 답이 들어오면 트리가 바뀐다. 확정된 결정이 프런티어를 바깥으로 밀고, 막혀 있던 질문이 풀린다. 다시 계산해서 다음 라운드.
  • 사실 조회는 에이전트의 일이고, 결정은 사용자의 일이다. 파일시스템이나 도구로 알아낼 수 있는 건 사용자에게 묻지 말고 서브에이전트를 띄워 찾아오라고 명시한다. 다만 거기서 블로킹하지는 않는다 — 그 조회에 의존하는 질문만 기다리고 나머지 프런티어는 지금 묻는다.
  • 프런티어가 비면 끝. 그리고 사용자가 “이해가 맞춰졌다” 고 확인하기 전까지는 행동하지 않는다.

나는 이 블로그에 2026-08-09 에 grill-me 글을 쓴 적이 있는데, 오늘 원본을 다시 읽고 두 가지를 고쳐야 했다. 현재 소스는 질문마다 추천답을 동봉하라고 요구하고, 환경에서 알아낼 수 있는 사실은 사용자에게 묻는 걸 금지한다. 그때 글에는 둘 다 없다. 스킬은 움직이는 표적이라 인용 시점을 적어두는 편이 낫다는 걸 새삼 배웠다.

우로보로스는 판정자에서 모델을 빼낸다

반대편은 규모가 완전히 다르다. 로컬에 설치된 ouroboros 0.50.8 은 파일 511개, 28만 줄이다. harness/ orchestrator/ evaluation/ evolution/ mcp/ 같은 하위 트리가 각각 따로 있다.

그 안에서 이번 비교에 정확히 맞는 조각이 harness/traceguard_validator.py 다. 291줄. 하는 일은 이렇다.

부모 합성(synthesis)이 내놓은 주장들과, 저널에서 뽑아낸 정본 증거 매니페스트(fact_id / chunk_id 쌍)를 받는다. 그리고 어느 주장이 인용된 증거에 근거하고 어느 것이 아닌지를 판정한다. 판정 방식이 핵심이다 — 모델을 호출하지 않고, IO 도 하지 않고, 순수한 집합 소속 판정만 한다. 소스의 표현으로는 판정이 정확한 집합 소속이기 때문에 “the verdict cannot be reward-hacked”(판정을 리워드 해킹할 수 없다).

거절 사유도 닫힌 어휘 5개다. 매니페스트에 없는 사실, 그 사실에 묶이지 않은 증거 핸들을 인용한 경우, 사실 없이 청크만 인용한 경우, 식별자가 빠진 경우, 그리고 주장을 담은 표면이 아예 없는 경우. 라우팅이 코드만 보고 분기하므로 사유가 자유 서술로 새지 않는다.

이 설계의 요점은 크기가 아니라 누가 판정자 자리에 앉는가다. LLM 이 만든 산출물을 LLM 에게 채점시키면, 채점자를 설득하는 것이 통과의 지름길이 된다. 집합 소속은 설득이 안 된다.

같은 적, 반대편 끝

두 물건을 관통하는 실패는 하나다. 동의를 결정으로 착각하는 것.

grilling 이 막으려는 건 사용자 쪽 버전이다. 에이전트가 묻고 사람이 계속 “네” 하면, 질문을 스무 개 거쳐도 결정된 건 하나도 없다. 에이전트가 세운 가정에 사람이 서명만 한 상태가 된다. 그래서 프런티어를 라운드로 쪼개고 추천답을 명시적으로 붙인다 — 반박할 표적을 주는 것이다.

TraceGuard 가 막으려는 건 에이전트 쪽 버전이다. 작업이 끝나고 “확인했습니다, 반영했습니다” 라는 문장이 나오는데 그 근거가 어디에도 없는 상태. 이건 하네스 운영에서 제일 비싼 실패다. 결과물이 아니라 결과 보고가 오염되기 때문에, 그 위에 쌓는 판단이 전부 오염된다.

  grilling TraceGuard
막는 대상 사용자의 무의식적 동의 에이전트의 근거 없는 주장
시점 코드 이전 (사전) 산출물 이후 (사후)
강제 수단 프롬프트 결정론적 코드
우회 가능성 모델이 지시를 안 따르면 그만 집합 소속이라 우회 불가
비용 319단어 291줄 + 증거 매니페스트 인프라

이 표의 3행이 사실상 전부다. grilling 의 보장은 “모델이 이 글을 잘 따를 것” 이 전부다. 강제력이 없다. 모델이 프런티어를 대충 계산하고 세 라운드에 끝내버려도 아무도 못 막는다. 반면 TraceGuard 는 모델의 협조를 전제하지 않는다.

그래서 둘은 경쟁 관계가 아니다. 크기 차이가 곧 강제력 차이다. 프롬프트 한 장으로 될 일에 28만 줄을 들일 이유가 없고, 결정론이 필요한 자리를 프롬프트로 덮을 수도 없다.

여기서 나온 제일 중요한 발견

traceguard_validator.py 의 첫 문단에 이런 사실이 적혀 있다. 원래 이 리포에는 TraceGuardValidatorProtocol(인터페이스)만 들어 있었고 구현이 없었다. 그래서 게이트가 production 에서 아무 일도 하지 않는 상태였다. 이 파일이 그 계약의 실제 구현이다.

선언된 게이트가 실행되지 않고 있었다는 뜻이다. 타입 시그니처는 있고, 이름도 있고, 문서에서 참조도 되는데, 실제로는 통과 여부를 아무도 안 보고 있었던 것.

이게 내가 이 블로그에서 반복해 쓴 얘기의 가장 선명한 사례다. 보호를 제공하지 않는 안전 통제는 없느니만 못하다. 없으면 없는 줄 알고 조심이라도 하는데, 있으면 통과했다는 사실이 안전의 증거처럼 읽힌다. 게이트 이름이 로그에 찍히는 것과 게이트가 무언가를 거절한 적이 있는 것은 완전히 다른 사실이다.

설치본 0.50.8 에서는 이 부분이 살아 있다. orchestrator/parallel_executor.pytraceguard_validator=validate_evidence_claims 로 실제 구현을 주입하는 호출 지점을 확인했다. 즉 한때 inert 였다가 지금은 배선돼 있다. 다만 이건 코드 경로 확인이지, 게이트가 실제로 주장을 거절하는 걸 실행해서 본 것은 아니다.

여기서 가져갈 체크리스트는 짧다. 어떤 하네스를 쓰든 게이트에 대해 물어야 할 질문은 “있는가” 가 아니다.

  1. 그 게이트가 마지막으로 무언가를 거절한 게 언제인가?
  2. 거절 사유가 닫힌 집합인가, 아니면 자유 서술인가?
  3. 판정자가 모델인가 코드인가? 모델이면 설득이 통과의 지름길이 된다.

그래서 뭘 쓰나

간단하다. 둘 다 쓰되 자리를 섞지 않는다.

아직 결정이 안 된 것        → grill-me (프런티어를 비운다)
결정된 것을 실행한 결과     → 증거 게이트 (근거 없는 주장을 거절한다)

grill-me 는 무료에 가깝다. 마크다운 두 장이고, 어느 리포에도 종속되지 않고, 소프트웨어가 아닌 결정에도 쓸 수 있다. 잃을 게 없으니 안 쓸 이유가 별로 없다. 대신 세션이 끝나면 결정도 같이 사라지므로, 나온 결정을 어딘가에 남기는 건 사람 몫이다.

우로보로스급 인프라는 값이 비싸다. 증거 매니페스트를 만들고 저널을 남기고 게이트를 배선해야 한다. 그 비용을 낼 가치가 있는 자리는 명확하다 — 에이전트의 보고를 사람이 일일이 검증할 수 없는 규모가 됐을 때다. 하루에 에이전트 하나가 작업 세 개를 하면 사람이 읽어서 확인하면 된다. 열 개 세션이 병렬로 돌면 그게 불가능해지고, 그때부터 “다 했습니다” 라는 문장의 신뢰도가 시스템의 상한이 된다.


검증 범위. grill-me 수치는 2026-08-27 기준 mattpocock/skills main 브랜치의 skills/productivity/grill-me/SKILL.md(7줄)와 skills/productivity/grilling/SKILL.md(319단어)를 직접 받아 센 값이다. 우로보로스 수치는 로컬 venv 에 설치된 ouroboros 0.50.8 패키지에서 .py 파일을 센 값이며(테스트 제외 508파일 280,757줄), 공개 리포를 클론한 것이 아니라 설치본 기준이라 배포 형태에 따라 다를 수 있다. TraceGuard 의 동작 설명은 harness/traceguard_validator.py 의 소스와 독스트링을 읽은 결과이고, 게이트를 실행해 거절을 재현하지는 않았다. 두 도구의 성능·효과를 비교 측정한 것이 아니라 설계와 강제 방식을 대조한 글이다.

References