멀티에이전트 ideation으로 뽑은 흡수 후보 7개 중 3개를 지운 작업기
기획은 병렬로 잘 돌았다. 문제는 "만들 수 있나"만 묻고 "만들어야 하나"는 안 물었다는 데 있다.
"설계를 전부 바꿔도 좋으니 성능을 올리자"로 세션을 열었다. LLM 출력을 압축하는 개인 도구를 하나 굴리던 중이었고 비슷한 도구 둘이 각각 다른 걸 잘했다. 하나는 대화 기록·MCP 같은 입력 쪽을 줄였다. 다른 하나는 코드를 덜 짜게 만들었다. 사용자가 셋을 따로 깔게 두느니 하나로 흡수하자는 게 출발점이었다.
기획은 에이전트에 맡겼다. 축을 셋으로 갈라 병렬로 아이디어를 뽑고(생태계·아키텍처·압축 품질), 그 결과를 비판 단계에 넘긴 다음, /my-plan으로 spec과 task 문서까지 분해했다. Codex를 순차 비평자로 붙여 의존성 누락 세 건도 잡았다. 흡수 게이트도 있었다. 어떤 능력이든 다섯 가지 통합 불변식(하나의 정직한 청구서, 전 표면 안전 register, 한국어 우선, 단일 세션 활성화, 검증 레이어)을 같이 들고 오지 않으면 머지 금지.
일주일 뒤, 그중 셋을 지웠다. 구현이 다 끝나고 테스트 216개가 통과하는 상태에서. 그리고 지금 저장소의 git 히스토리에는 그 셋의 흔적이 한 줄도 없다. 아래는 2026년 8월 기준으로 남은 결론과, 거기까지 간 경로다.

게이트를 전부 통과했는데 틀린 기능이었다
살아남은 넷은 이렇다. 자기 주입 오버헤드까지 차감하는 통합 청구서, 오프라인 동등성 채점 하네스, 메모리 문서를 코드·경로 바이트 단위 보존을 걸고 줄이는 CLI, 그리고 끄는 명령이 상태표시줄 꼬리표까지 정리하도록 고친 버그 수정.
지운 셋은 이렇다. 코드 과설계를 감사하는 skill, MCP 도구 설명을 줄이는 stdio 프록시, 그리고 서브에이전트 프리셋 3종.
셋 다 게이트를 통과했다. 안전 register를 상속했고 한국어를 인지했고 청구서 스키마를 재사용했다. 머지 시점의 테스트는 각각 187개·204개·216개가 통과했다. 품질 문제가 아니었다. 문제는 게이트가 던진 질문이었다. 다섯 불변식은 전부 "이 기능을 제대로 만들었나"를 물었다. "이 기능이 있어야 하나"를 묻는 칸은 어디에도 없었다.
없으니까 답이 늦게 왔다. 코드 감사 skill은 이미 있는 리뷰 명령 셋과 하는 일이 겹쳤다. MCP 설명 축약은 LLM 없이 보수적으로 줄이는 방식이었는데 실측이 2.6%였다. 프록시를 하나 더 세우고 유지할 값이 아니었다. 서브에이전트 프리셋은 절감을 결정적으로 잴 방법이 없었다. 재고 않고 추정치를 적으면 도구의 유일한 셀링포인트인 "우리 수치는 정직하다"가 무너진다.
세 판단 모두 구현이 끝난 뒤에 내려졌다. 셋을 걷어내고 다시 돌린 테스트는 196개였다. 순효과는 신규 skill 0개, 에이전트 0개, 명령 0개. 즉 그 주에 병렬로 잘 돌아간 기획 파이프라인의 산출물 중 절반 가까이가, 쓸 만한 코드였음에도 커밋한 줄 남기지 못했다.
게이트를 구현 앞으로 옮기니 거기서 하나가 죽었다
다음 사이클에서 순서를 바꿨다. 후보를 24개 뽑아 세 명의 비평자(실용·전략·회의)에게 돌린 것까지는 같은데, 살아남은 항목에 구현 대신 Phase 0 측정을 먼저 붙였다. Phase 0은 제품 코드를 건드리지 않는 측정 전용 하네스다. 벤치마크 디렉터리 안에만 살고 LLM을 한 번도 호출하지 않으며 같은 저장소 상태면 같은 숫자를 낸다. 목적은 기능을 만드는 게 아니라 만들 값이 있는지에 GO/NO-GO를 찍는 데 있다.
여기서 중요한 건 임계값을 새로 고르지 않았다는 점이다. kill 하한 3%는 앞서 2.6%로 폐기한 기능에서 그대로 가져왔다. 폐기 수치를 앵커로 박아두면 다음 후보가 "그건 그때 얘기고" 하며 빠져나가지 못한다.
첫 대상은 매 세션 주입되는 컨텍스트 문서(CLAUDE.md·AGENTS.md·rules/**)에서 중복·사문을 걷어내는 감사 기능이었다. 결과는 GO도 NO-GO도 아닌 WEAK였다. 합성 코퍼스 중앙값은 28.22%로 GO 선을 넘는데, 실제로 쓰는 저장소 문서의 중앙값은 0%였다. 이미 스스로 압축을 먹인 문서라 걷어낼 게 없었다. 어느 쪽도 대푯값이 아니니 판정을 내릴 수 없다.
그래서 감사 기능은 착수하지 않았다. 버릴 코드는 0줄이었다. 남은 건 측정 결과뿐이었다.
아래가 그 게이트의 최소 골격이다. 그대로 받아서 임계값과 코퍼스만 자기 것으로 바꾸면 된다.
#!/usr/bin/env node
// phase0-gate.js — 만들기 전에 "만들 값이 있나"를 결정적으로 재는 게이트.
// LLM을 호출하지 않는다. 같은 입력이면 항상 같은 판정이 나와야 한다.
// 사용: node phase0-gate.js 종료 코드 0=GO, 1=WEAK, 2=NO-GO
// 임계값은 새로 고르지 않는다. 지난번에 무엇을 어떤 수치로 폐기했는지에서 가져온다.
const GATE = {
goMedian: 8, // GO 하한(%)
killMedian: 3, // kill 상한(%) — 앵커: 실측 2.6%로 폐기한 기능이 있었다
goPrecision: 0.8, // 탐지기가 잡은 것 중 진짜 비율
killPrecision: 0.5, // 이 아래는 신호가 아니라 노이즈
};
const median = (xs) => {
// edge: 표본 0개는 "0%"가 아니라 "측정 없음"이다. 0으로 뭉개면 kill zone으로 오판한다.
if (xs.length === 0) return null;
const s = [...xs].sort((a, b) => a - b);
const m = s.length >> 1;
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
};
function verdict({ synthetic, live, precision }) {
const mSyn = median(synthetic); // 합성 표본 — 고정, 재현됨
const mLive = median(live); // 실사용 문서 — 편집하면 값이 움직인다
const all = median([...synthetic, ...live]);
if (all === null) return ['NO-GO', '표본 0 — 잴 것이 없다'];
if (precision < GATE.killPrecision) return ['NO-GO', `precision ${precision} — 노이즈`];
// 체제 발산 가드를 먼저 태운다. 이게 없으면 합성 표본을 늘리는 것만으로
// 전체 중앙값을 GO 위로 밀 수 있다 — 게이트가 자기 자신을 통과시킨다.
const diverged = mSyn !== null && mLive !== null
&& (mSyn >= GATE.goMedian) !== (mLive >= GATE.goMedian);
if (diverged) {
return ['WEAK', `체제 발산 — 합성 ${mSyn}% vs 실사용 ${mLive}%, 대표값 없음`];
}
if (all < GATE.killMedian) return ['NO-GO', `중앙값 ${all}% — kill zone`];
if (all >= GATE.goMedian && precision >= GATE.goPrecision) return ['GO', `중앙값 ${all}%`];
return ['WEAK', `중앙값 ${all}% — GO 하한 미달`];
}
const [v, why] = verdict({
synthetic: [31.43, 25.0], // 합성 샘플의 제거 가능 비율
live: [0, 0, 0, 0], // 실제로 주입 중인 문서들
precision: 1,
});
console.log(`Verdict: ${v} — ${why}`);
// WEAK는 미완이 아니라 끝난 측정이다. 재실행이 아니라 새 결정이 있어야 다시 연다.
process.exit(v === 'GO' ? 0 : v === 'WEAK' ? 1 : 2);
한 가지 덧붙이면, 이 하네스의 산출물은 지우지 않고 저장소에 커밋해 뒀고 테스트가 매번 갱신본과 대조한다. 근거를 지운 판정은 판정이 아니라 그냥 주장이기 때문이다. 실제로 나중에 하네스 코드 자체는 걷어냈지만 측정 결과는 남겼다.
같은 시체가 다음 사이클에 다시 올라온다
여기까지 하고도 한 번 더 데었다. ideation을 다시 돌리면 폐기한 후보가 또 올라온다. 당연하다. 에이전트가 보는 건 현재 저장소와 로드맵이지, 두 달 전 세션에서 무엇을 왜 버렸는지가 아니다. 폐기 근거가 spec 본문·initiative 표·kill 목록 세 군데에 흩어져 있으면 그 근거는 결정으로서의 지위가 없다. 그래서 매 사이클마다 같은 항목을 다시 심의하는 데 시간을 태운다.
고친 방법은 단순하다. 폐기를 결정 문서 한 장으로 승격했다. 항목·폐기 근거·재상정 트리거 세 칸짜리 표를 ADR로 만들었다. 새 ideation은 트리거를 인용하지 않으면 등재 항목을 재상정하지 못하게 했다.

| 항목 | 폐기 근거 | 재상정 트리거 |
| ---- | -------- | ------------ |
| MCP 설명 축약 | 측정 NO-GO(2.6%) — 프록시 복잡도 대비 미달 | 새 코퍼스·단일 절 A/B 양성 측정 |
| 코드 감사 skill | 기존 리뷰 명령과 중복, 수요 신호 0 | 이슈·요청 형태의 실수요 신호 |
| 프로젝트별 register 덮어쓰기 | 3층 상태 우선순위가 stale 활성화를 키움 | 이슈·요청 형태의 실수요 신호 |
트리거를 적는 게 핵심이다. "안 함"만 적으면 다음 사이클의 에이전트도, 두 달 뒤의 나도 그 판단이 영구인지 조건부인지 알 수 없어서 결국 다시 연다. 트리거가 있으면 재상정이 충동이 아니라 조건 충족의 문제가 된다. 그리고 트리거는 실제로 두 종류로 수렴했다. 사람이 낸 실수요 신호이거나, 단일 변수 A/B의 양성 측정이거나.
한 가지는 정직하게 적어둔다. 이렇게 닫은 7개 항목 중 측정으로 닫힌 건 딱 하나다. 나머지 여섯은 "수요 신호가 없다", "기존 것과 겹친다", "미니멀리즘과 자충돌한다"는 판단이다. 판단은 신호가 바뀌면 틀린다. 그래서 트리거 칸이 필요하다.
남은 것
멀티에이전트 기획은 후보를 잘 만든다. 그게 정확히 문제다. 뽑힌 후보는 전부 그럴듯하다. 구현 게이트는 전부 "잘 만들었나"만 물으므로 아무도 안 막으면 그대로 코드가 된다. 게이트를 구현 앞으로 옮겨야 버릴 게 코드가 아니라 측정 결과가 된다. 그리고 폐기 근거를 결정 문서로 남기지 않으면 다음 ideation이 같은 후보를 다시 올린다.
지금 진행 중인 기획 문서를 펴서 후보마다 "이걸 만들어야 하나"에 답하는 칸이 있는지 확인하라. 없으면 그 칸부터 만들어라.