
AI는 신뢰하는 것이 아니라 설계하는 것 ── 연재 총괄·최종장 (Part 6)
요약
AI 에이전트가 시스템을 정확히 이해하도록 설계하는 'cortex' 하네스의 철학과 기술적 도전 과제를 다룹니다. 컨텍스트 윈도우의 한계와 'lost in the middle' 현상을 극복하기 위해 정보를 구조화하는 설계의 중요성을 강조합니다.
핵심 포인트
- AI에게 시스템을 정확히 이해시키는 것이 에이전트 설계의 핵심
- 컨텍스트 윈도우 확장은 'lost in the middle' 문제로 인해 근본적 해결책이 아님
- 단순 정보 전달이 아닌, 정보를 구조화하여 재귀적으로 판단하게 하는 설계 필요
여러분 안녕하세요! AirCloset에서 CTO를 맡고 있는 츠지입니다.
5편의 연재를 통해 cortex의 하네스(harness)가 어떻게 구성되어 있는지를 기구별로 써왔습니다. 총론 / 지식 그래프 (Knowledge Graph) / Auto Review / Self-Healing + 재발 방지 / 비엔지니어 PR ── 한 차례 훑어본 뒤에, 마지막으로 한 단계 더 낮은 이야기를 해두고 싶습니다. 애초에 왜 이것을 만들고 있는가, 뿌리가 되는 사상에 대한 이야기입니다.
5편의 기사는 각각 독립적으로 보일 수도 있지만, 뿌리는 하나이며 그것을 언어화해 두지 않으면 연재의 윤곽이 흐릿해져 마무리되지 않는다는 감각이 있습니다. 아울러, 지금까지의 기사에서는 "잘 돌아가고 있는 결과"를 중심으로 써왔기에, 그 이면에서 겪었던 실패 ── 무엇을 버렸는가 / 무엇에서 걸려 넘어졌는가 ── 도 되돌아보려 합니다. 비슷한 일을 시작하려는 분들에게 참고가 되기를 바랍니다.
연재 목록
| # | 테마 | 키 씬 (Key Scene) | 기사 |
|---|---|---|---|
| 1 | 총론: cortex의 하네스 | PR 무인 머지 (Merge) / 장애를 인지하기 전에 수정됨 | ai-harness-intro |
| ... |
기점 ── 2025년에 생각했던 것
cortex를 만들기 시작했을 무렵, 제가 답을 내고 싶었던 질문은 하나였습니다.
어떻게 하면 AI에게 시스템을 정확하게 이해시킬 수 있을까
AI가 시스템을 정확하게 이해할 수만 있다면, PR 리뷰도 버그 조사도 수정도 맡길 수 있고, 엔지니어가 아닌 멤버에게도 개발을 개방할 수 있다는 직감이 있었습니다. 반대로 말하면, "정확하게 이해시키는" 단계에서 막혀 있는 한, 그 이후의 모든 것은 불안정한 상태 위에 놓여 있습니다. 그래서 기구별 공법을 고민하기 전에, 전제 조건을 어떻게 구현할 것인가를 생각하는 시간이 길었습니다.
다만, 이에 대한 솔직한 접근 방식들은 모두 벽에 부딪힙니다.
벽 1: 컨텍스트 윈도우 (Context Window)의 한계
가장 먼저 생각하는 것은 "필요해 보이는 정보를 전부 전달하면 되지 않을까"라는 접근입니다. 코드베이스 전체 + 문서 + DB 스키마 + 인프라 정의를 프롬프트(Prompt)에 담으면, AI는 전체상을 파악할 수 있을 것입니다.
이는 규모 면에서 파탄이 납니다. 사내 코드, 문서, 스키마, 인프라를 모두 더하면 현실적인 컨텍스트 윈도우에 담을 수 없습니다.
"그럼 컨텍스트 윈도우가 더 커지면 해결되지 않을까" ── 이 또한 생각하면 할수록 미래가 없다고 느꼈습니다.
Gemini처럼 컨텍스트 윈도우가 매우 큰 모델이라도, 한계 근처까지 사용하면 거동이 불안정해집니다. 중간의 정보를 잡지 못하거나, 관련 없는 정보에 끌려 결론이 이상한 방향으로 쏠리기도 합니다. 이는 어텐션 (Attention)의 구조적인 문제로, 관련 없는 토큰이 섞일수록 관련 있는 토큰에 대한 어텐션 비율이 기계적으로 낮아집니다. 연구상으로는 "lost in the middle" (긴 입력의 처음과 끝에 있는 정보는 사용되지만, 중간에 놓인 정보는 사실상 무시되기 쉬운 현상)으로 알려져 있으며, 컨텍스트 윈도우에 무엇이든 채워 넣으면, 넣었다고 생각한 정보가 모델에게 보이지 않는 상황이 흔히 발생합니다.
"lost in the middle" 자체는 Long-context 모델이 진화하면 완화될 가능성이 있으며, 그 부분은 경험적인 방증으로 받아들이고 있습니다. 정말로 효과가 있는 것은 더 근본적인 재귀(Recursion)의 문제입니다. "크기"를 해결해도, 다음으로 "그중에서 무엇이 필요하고 무엇이 불필요한지를 판단하기 위한 상위의 컨텍스트"가 필요해집니다. 이는 재귀적인 문제로, 컨텍스트 윈도우의 사이즈로는 원리적으로 풀 수 없습니다. 정보를 구조화하지 않는 한, AI는 올바른 판단을 해주지 않습니다 ── 이는 인간에게도 해당되는 이야기지만, 인간보다 한 단계 더 나쁜 점은, LLM은 "모른다"는 것을 깨닫지 못한 채 자신 있게 대답한다는 점입니다. 조용히 틀리는 것이 명확하게 막히는 것보다 다루기 까다롭습니다.
컨텍스트 윈도우를 크게 만드는 방향으로는 근본적인 해결책이 보이지 않았습니다.
벽 2: 학습에 의존하는 방향도 제외
또 다른 솔직한 접근 방식은 "AI 자체를 학습시키는 것"입니다. 조직마다 파인튜닝 (Fine-tuning)을 하여 자사의 코드베이스, 문서, 업무를 익히게 하는 것입니다. 이것도 고려해 보았지만, 현재는 제외하고 있습니다.
이유는 두 가지입니다. 하나는 학습을 실용 단계에 올리는 것은 (2025년 당시에도, 쓰고 있는 2026년 현재도) 아직 연구 단계(research phase)이며, 실제 운영 환경에 적용하기까지는 시간이 걸린다는 점입니다. 또 다른 하나는 더 까다로운 문제로, 설령 학습할 수 있다고 하더라도 '잊게 만드는 것'이 극도로 어렵다는 점입니다.
업무 시스템에서는 '현재의 진실'이 요건입니다. 설계 변경 / DB 스키마 변경 / 비즈니스 규칙 변경이 발생하면, 오래된 지식은 능동적으로 지우고 싶어 합니다. 하지만 LLM의 가중치(weight)에 각인된 지식을 '특정한 것만 핀포인트로 지우는 것'은 미해결 난제이며, machine unlearning이라는 분야명이 붙어 있을 정도로 어렵습니다. 게다가 새로운 것을 학습시키는 과정에서 무관한 기존 지식까지 파괴되어 버리는 현상(destructive interference / catastrophic forgetting)도 알려져 있어, 학습에 치중하면 이 두 가지가 동시에 작용하여 정합성 관리 비용이 천정부지로 치솟게 됩니다.
'학습하지 않는 것'을 부정적으로 받아들이는 것이 아니라, 학습하지 않기 때문에 외부 지식을 교체하는 것만으로 현상을 반영할 수 있다고 솔직하게 받아들이는 편이 정합성 관리가 수월해진다 ── 이것이 당시의 판단이었습니다.
돌파구 ── GraphRAG + MCP에 도달하다
컨텍스트 윈도우(context window)의 방향도, 학습의 방향도 미래가 보이지 않던 타이밍에 GraphRAG의 개념을 만납니다.
GraphRAG 자체는 외부에서 널리 논의되고 있는 개념이지만, 저에게 있어 그 의미는 '필요한 타이밍에, 필요한 컨텍스트만을 공급한다'는 발상이었습니다. 여기에 MCP(Anthropic이 정의한 LLM과 외부 도구의 연결 프로토콜)를 결합하면, AI가 스스로 필요한 정보를 가져올 수 있는 형태가 됩니다.
결정적이었던 것은, 이것이 agentic(AI 주도적)하게 그래프를 따라갈 수 있는 구조를 가지고 있었다는 점입니다. AI에게 '전부 읽게 해서 추론으로 관련 부분을 찾아내게 하는 것'이 아니라, AI가 스스로 필요한 노드(node)까지 도달하여 사실로서 추출할 수 있는 것. 이는 다음과 같이 정리할 수 있습니다.
AI에게 추론을 시키는 것이 아니라, 사실을 컨텍스트로서 공급한다
이 한 문장이 이후 cortex 전체 설계 사상의 핵심이 되었습니다.
처음 착수한 것은 정적 분석 기반의 code-graph였으나, 이는 시행착오 끝에 버리고 annotation-base의 **product-graph (cpg)**에 도달하게 됩니다 (상세 내용은 시행착오 장에서 다룹니다).

애초에 AI를 신용하지 않는다
기점 장을 한마디로 요약하면, 결론은 이렇습니다.
AI를 신용하지 않는다
여기서 말하는 '신용하지 않는다'는 신뢰하지 않는다와는 다릅니다. Claude / GPT / Gemini의 생성 품질을 의심한다는 이야기가 아니라, 다음과 같은 뜻입니다.
전달되지 않은 컨텍스트에 대해서는 알지 못한다
아무것도 말하지 않아도 이상적인 상태를 만들어 줄 것이라고 생각하지 않는다
전자는 모델이 얼마나 진화하더라도 변하지 않는 진실입니다. LLM의 아키텍처상, 훈련 데이터에 없는 것이나 해당 세션(session)의 컨텍스트에 없는 것은 원리적으로 파악할 수 없습니다. '나중에 모델이 똑똑해지면 자동으로 알아차려 주지 않을까' ── 그런 미래는 오지 않을 것이라고 생각합니다. 똑똑해지는 방향은 있을지언정, 똑똑하기만 해서는 알 수 없는 것이 있습니다.
후자는 인간이 정의하는 책임의 문제입니다. AI는 '무엇이 이상적인가'를 마음대로 결정할 수 없습니다. 결정하려고 하면 현장과 조금 동떨어진 일반론에 도달하게 됩니다. 무엇이 이상적인지는 해당 업무, 해당 조직, 그 시점의 비즈니스 상황에 의존하며, 이는 인간 측에서 언어화하여 전달하지 않는 한 AI에게는 보이지 않습니다.
따라서 'AI를 신용하지 않는다'는 것은 AI의 능력을 과소평가하는 것이 아니라, AI에게 전제 조건을 자동 보완하게 하지 않겠다는 설계 판단입니다.
'AI를 능숙하게 다룬다'는 것은 AI에게 자유를 주는 것이 아니라, AI의 출력을 예측 가능한 범위 안에 가두는 것이라고 생각합니다.
가두기 위한 메커니즘이 바로 연재에서 써온 하네스(harness)입니다.
그래서 하네스로 결정론에 가깝게 만든다
각 기구를 'AI에게 추론을 시키지 않고, 결정론(deterministic)에 가깝게 만든다'는 렌즈로 다시 읽어보면, 5편의 연재에서 쓴 것들은 모두 같은 사상의 서로 다른 발현이라는 것을 알 수 있습니다.
Part 2 ── 나리지 그래프(Knowledge Graph): 코드베이스를 AI가 찾게 하는 대신, 코드베이스 측을 읽기 쉽게 정돈하는 방향으로 기울인 기구입니다. @graph-*
annotation을 통해 코드 / docs / DB / 인프라를 하나의 그래프로 통합하여, AI가 grep과 추론을 통해 관련 부분을 찾아 헤매지 않도록 했습니다. 이는 기점 장에서 언급한 "사실을 컨텍스트(Context)로 공급한다"의 직접적인 구현입니다. → cortex-product-graph
Part 3 ── Auto Review 관점: 9가지 관점(responsibility / severity / type SSoT / etc.)이 사전에 고정되어 있습니다. AI에게 리뷰를 맡길 때, 무엇을 체크할지 추론하게 하지 않도록 설계한 것입니다. "전체적으로 어떤가"라고 물으면 추론의 여지가 너무 넓어지기 때문에, 관점을 세분화하여 관점마다 개별적인 질문으로서 판정하게 합니다. 관점 = 하네스(Harness) 측에서 고정, 평가 = AI에게 맡김이라는 역할 분담입니다. → cortex-auto-review
Part 4 ── Self-Healing + 재발 방지: 알람(Alert) → 조사 → 수정 PR → 재배포 ── 이 흐름 자체가 고정되어 있습니다. AI에게 "장애가 발생하면 어떻게 대응할 것인가"를 매번 생각하게 하지 않습니다. 나아가, 같은 함정에 두 번 빠지지 않도록 lint / CI gate를 추가하는 재발 방지 루프는, 추론이 아닌 기계적인 거부로 동일한 사고를 배제하는 메커니즘입니다. "AI가 두 번 같은 실수를 하지 않기를 기대하는 것이 아니다"라고 바꿔 말해도 좋습니다. → cortex-self-healing
Part 5 ── 비엔지니어 PR: 하네스가 품질을 담보하지 못한다면, 현업 측에서 직접 PR을 내는 것은 성립할 수 없습니다. 역설적으로 말하면, 지금까지의 Part 2~4에서 쌓아 올린 기구(컨텍스트 확정 / 관점 고정 / 함정의 기계적 배제)가 갖춰졌기에, 업무 요건을 가장 잘 파악하고 있는 사람이 직접 작성할 수 있는 상태가 성립된 것입니다. 번역 레이어와 우선순위 대기열이 사라진 것은, 결정론(Determinism)에 가깝게 만든 결과로서 나중에 나타난 현상입니다. → cortex-non-engineer-prs
즉, 지금까지 써온 4가지 기구와 그 위에 성립된 비엔지니어 PR ── 이들은 모두 "AI에게 추론시키지 않고, 결정론에 가깝게 만든다"를 다양한 레이어에서 구현한 결과이며, 뿌리는 동일한 사상에 맞닿아 있습니다.
"AI에게 추론시키지 않고, 결정론에 가깝게 만든다"란
여기까지 몇 번인가 등장했던 이 문구의 해상도를 조금 더 높여 두겠습니다.
"결정론에 가깝게 만든다"는 AI가 추론할 여지를 제로(0)로 만든다는 의미가 아닙니다. 코드 생성 · 리뷰 지적 · 에러 로그로부터의 원인 추정 ── 이러한 "변동이 허용되는 영역"에서는, 오히려 AI가 추론해 주지 않으면 일이 진행되지 않습니다.
우리가 가깝게 만들고자 하는 것은 "변동을 허용해서는 안 되는 영역" 쪽입니다. 구체적으로는:
어떤 코드베이스를 볼 것인가 ── 추론으로 유추하게 하지 않고, 지식 그래프(Knowledge Graph)로부터 확정적으로 추출한다 -
어떤 관점으로 리뷰할 것인가 ── AI가 "중요해 보이는 관점"을 선택하게 하지 않고, 관점 리스트를 사전에 고정한다 -
장애 대응 플로우는 어떻게 움직이는가 ── AI에게 매번 플로우를 생각하게 하지 않고, 알람부터 수정 PR까지의 경로를 결정한다 -
같은 함정에 두 번 빠지지 않는 것 ── AI에게 "주의하자"라고 생각하게 만드는 것이 아니라, lint / CI를 통해 기계적으로 거부한다
이 선긋기 ── "추론시켜도 되는 곳 / 추론시켜서는 안 되는 곳" ── 을 구현한 것이 하네스라는 것이 현재의 정리입니다. Part 5에서 사용한 비유를 이어가자면, 하네스는 "이탈해서는 안 되는 레일"을 깔고 있습니다. 레일 위에서는 자유롭게 달릴 수 있지만(추론은 추론으로서 활용됨), 레일을 이탈하는 방향으로는 움직일 수 없습니다.
다른 방식으로 말하자면, 이는 Part 2(cortex-product-graph)에서 썼던 "할루시네이션(Hallucination)을 어디에 가둘 것인가"와 동치입니다. "추론시키지 않는다"라고 단정 지어 말하면 엄밀히는 어긋나는데, 하네스는 할루시네이션을 제로로 만드는 메커니즘이 아니라, 발생해도 괜찮은 장소(추론시켜도 되는 영역)에만 가두는 메커니즘입니다. 코드베이스 전체의 구조나 사실은 결정론적으로 추출되므로 추출 과정에 할루시네이션이 들어갈 여지가 없으며, 판단 측에서 발생한 할루시네이션은 하류의 테스트 / lint / 관점별 리뷰에서 걸러집니다. 할루시네이션이 허용되는 장소와 허용되지 않는 장소를, 하네스로 물리적으로 나누고 있다는 것이 Part 2에서 세운 이야기의 연장선입니다.
한 단계 더 관점을 넓혀보면, 하네스(Harness)가 하고 있는 일은 추론(Inference)이 발생하는 시점을 늦추는 것이기도 합니다. 그래프에 올라가 있는 annotation(주석)이나 설명문도 원래는 AI가 작성한 것이며, 거기에는 분명히 추론이 포함되어 있습니다. 다만 그것은 write-time ── 단 한 번 발생하여 리뷰를 거쳐 동결된 추론 ── 이지, read-time(쿼리할 때마다 매번 발생하며, 그 시점에는 검증되지 않은 상태)이 아닙니다. 그래프는 「동결되어 검증된 추론」이기에, 읽는 측에서 사실로서 다룰 수 있는 것입니다. 「결정론(Determinism)에 가깝게 만든다」는 말은, 검증되지 않은 추론을 쿼리할 때마다 실행하지 않는 것이라고 바꿔 말해도 좋습니다.

이는 연재 Part 1(총론)에서 썼던 「모델은 범용화(Commodity)된다, 하네스로 차별화한다」의 근거이기도 합니다. 모델 단독의 품질은 Claude / GPT / Gemini 사이의 격차가 줄어들고 있지만, 하네스는 코드베이스 고유 / 업무 고유의 성격을 띠므로, 이 부분을 정비하는 것이 조직으로서의 차별화가 됩니다.
참고로 말씀드리자면, 이 경계선의 위치는 모델 성능에 의존하여 움직입니다. agentic search나 추론 능력이 향상되면, 오늘 결정론이 필요로 했던 영역도 내일은 추론만으로 충분할지도 모릅니다 (실제로 cortex 자체도 하네스 내에서 AI가 agentic하게 그래프를 traverse(순회)하게 함으로써 AI의 발견 능력에 의존하고 있습니다). 하지만, 경계선 그 자체가 사라지지는 않습니다. 정보를 어떻게 구조화할 것인가, 어디서부터 어디까지를 추론에 맡길 것인가 ── 이를 명시적인 설계(Design)로서 보유하고 있는가가 어떤 모델 세대에서도 조직의 차이를 만듭니다.
덧붙여, 이 발상 ── 결정론을 통해 적절한 컨텍스트(Context)를 전달하고, AI에게 추론을 시키지 않는다 ── 는 cortex의 하네스에만 국한된 이야기가 아닙니다. 사내 DB 스키마를 자연어로 검색할 수 있게 하는 db-graph MCP, 비엔지니어가 AI로 만든 앱을 안전하게 공개할 수 있는 Sandbox MCP 등, 사내에서 AI를 사용하는 다른 기반 시설들도 같은 스탠스로 만들고 있습니다. AI를 사용하여 무언가를 성립시키는 모든 기반에 관통하는 사고방식이라고 생각합니다.
한 단계 더 추상화해서 말하자면, 개별 기능 그 자체에 본질적인 가치가 있는 것은 아니다라는 뜻이기도 합니다. 가치가 있는 것은 사상(Philosophy)의 측면이며, cortex / db-graph / Sandbox MCP 각각의 구현은 그 사상을 자신들의 유스케이스(Use case)에 구체화한 결과에 불과합니다.
여기서 말하는 「설계」를 저는 다음과 같이 정의합니다.
설계란, 추상적인 사상을 자신들의 유스케이스에 맞춰 구체적인 구현으로 떨어뜨리는 것이다.
클래스 다이어그램을 그리는 작업도 아니고, 아키텍처 도면을 정리하는 작업도 아닙니다. 「자신들의 업무 / 코드베이스 / 제약 조건 아래에서 이 사상이 어떻게 형태를 갖추는가」를 번역하는 일이야말로 설계입니다. 여기에 각 조직의 고유성이 깃들며, 복제할 수 없는 가치가 생겨납니다.
역으로 말하면, 다른 조직이 cortex의 외형을 그대로 복사한다고 해서 본질을 재현한 것은 아니며, 자신들의 유스케이스에 대해 이 사상을 어떻게 구체화할 것인가가 각 조직에 던져지는 과제라는 뜻이기도 합니다.
여기에 도달하기까지의 시행착오
지금까지는 「잘 돌아가고 있는 결과」를 중심으로 써왔지만, 최종 형태에 도달하기까지는 수없이 버린 선택지들이 있습니다. 대표적인 3가지를 남겨두겠습니다.
정적 분석 기반의 code-graph를 2개월에 걸쳐 버리다
처음 착수한 것은 정적 분석으로 코드 구조를 추출하여 그래프화하는 code-graph라는 방향이었습니다. AST(추상 구문 트리)로부터 import 관계 / 함수의 호출 그래프 / 타입 의존 관계를 기계적으로 추출하여 그래프 DB에 올리는 방식입니다. 「AI에게 코드베이스를 이해시킨다」는 측면에서 가장 솔직한 구현처럼 보였습니다.
cortex를 만들기 시작한 첫 3개월 중 2개월을 여기에 소비하여 일단 돌아가는 형태는 만들 수 있었습니다.
하지만, 버렸습니다.
이유는 정적 분석이 구조를 파악하는 데는 능숙하지만, 의도나 비즈니스 문맥을 따라갈 수 없기 때문입니다. 구체적으로 막혔던 점은 세 가지입니다:
- 의미(Meaning)로 검색할 수 있는 입구가 없다 ── "회원의 구독료를 계산하는 함수를 찾아줘"라는 쿼리로 코드를 끌어오고 싶은데, 함수명이나 파일명을 미리 알지 못하면 찾아갈 수 없습니다. 정적 분석(Static Analysis)만으로 만든 그래프에는 "무엇을 위한 처리인가"라는 입구의 의미 태그(Semantic Tag)가 붙어 있지 않습니다.
- 노드가 코드뿐이다 ── 내부 함수 / utility / 타입 / 인자(Argument)가 모두 노드가 되기 때문에, 특정 함수에서 관련 지점을 따라가다 보면 몇 번의 홉(Hop) 만에 helper나 primitive까지 휘말리며 폭발적으로 늘어납니다. 의미적인 관련성으로 범위를 좁힐 축이 없습니다.
- 본래 원했던 것은 "코드 + DB 스키마 + 문서 + 인프라"가 동일한 그래프 위에 올라가 있는 것 ── 특정 함수를 봤을 때, 그 함수가 건드리는 DB 테이블과 설계가 적힌 문서, 그리고 연결된 비즈니스 요구사항까지 단 한 번의 쿼리로 일괄 추출하고 싶습니다. 코드만 있는 그래프로는 이것이 불가능합니다.
→ annotation-based 접근 방식(JSDoc 태그로 각 코드에 비즈니스 책임을 작성하고, 이를 DB 스키마 / docs / 인프라 정의 그래프와 통합하는 방식)으로 전환했습니다. 의미로 검색할 수 있고, 추적할 때도 관련성 있는 것들만 나오는 것이 현재의 product-graph (cpg) 입니다. 매몰 비용(Sunk Cost)에 끌려다니면 최종 형태에 도달할 수 없다 ── 2개월간의 투자를 회수하려 하지 않고 버리기로 한 판단이, 이후 모든 기구(Mechanism)의 토대가 되었습니다.
Coverage 90% 이상을 목표로 했더니 구현이 저하되었다
테스트 커버리지(Test Coverage)는 현재도 90% 이상을 조건으로 하고 있습니다 (Part 3에서도 언급했듯이). 이것 자체는 유지하고 있지만, 과거에 커버리지(Coverage)만을 단독 목표로 취급했던 시기가 있었고, 그때는 반대로 구현이 약해지는 현상이 나타났습니다.
구체적으로는:
- 기본값(Default Value)을 남용하여 조건 분기를 보이지 않게 만들기:
function(input = {})와 같은 방식으로, 누락된 입력에 대한 조건 분기를 테스트 경로에서 지워버립니다. 커버리지는 올라가지만, 예상치 못한 입력에 대한 보호 기능은 사라집니다. - 에러를 throw하지 않고 묵묵히 삼키기: try / catch로 에러를 뭉개고
return null을 합니다. throw를 작성하지 않으므로 "throw하지 않는 케이스"의 테스트만으로 커버리지를 채울 수 있습니다. 하지만 부정적인 상태가 사이런트(Silent)하게 진행됩니다. - 조기 리턴(Early Return)으로 로직을 너무 단순화하기: 복잡한 조건을 "일단 return"으로 회피합니다. 테스트는 통과하지만, 본래 있어야 할 유효성 검사(Validation)가 누락됩니다.
결과적으로 "커버리지는 90%이지만, 품질은 떨어져 있는" 상태가 됩니다. 커버리지라는 메트릭(Metric)을 단독으로 보면, 이를 충족하는 최단 경로로서 "테스트만 통과하는 약한 구현"이 선택됩니다.
교훈은 두 가지입니다:
- 수치 목표를 세우면 그것이 목적이 된다. 커버리지는 "최소한 지켜져야 할 바닥"이지 "달성해야 할 목표"가 아닙니다.
- 메트릭은 단독으로 평가하지 않는다. 커버리지에 더해, 책임 분할 / 예외 설계 / 경계값 망라 / etc.를 별도의 관점으로 평가할 필요가 있습니다.
그리고 대책으로서, 커버리지를 채우기 위해 구현을 약하게 만드는 경로 자체를 기계적으로 차단하는 lint를 정비해 나갔습니다. 구체적인 사례 두 가지:
no-silent-catch: 빈 catch 블록이나.catch(() => null)와 같은 silent 패턴을 금지합니다. catch 내부에서 어떠한 함수 호출(logger 포함) / re-throw / new / await가 없으면 에러가 발생합니다. "throw를 지우고 뭉개면 커버리지는 올라가지만, 운영 환경에서는 실패를 관측할 수 없다"는 전형적인 괴리를 구조적으로 방지합니다. 위반 시 메시지를 통해@cortex/otel/logger를 이용한 구조화된 로그(Structured Log) 출력을 안내하며, Cloud Run의 OTel 파이프라인을 통해 Loki / Grafana에 도달하는 경로까지 갖추어 두었습니다.vitest-strong-matchers:toBeTruthy/toBeDefined/toContain/toBe(true|false)/expect.any/expect.objectContaining등의 약한 매처(Matcher)를 금지합니다. "일단 통과하는 assertion"으로 커버리지만 챙기는 패턴을 AST 레벨에서 저지하고,toStrictEqual/toMatchInlineSnapshot등을 사용하도록 유도합니다.
출력 전체를 고정하는 matcher로 유도합니다. 이는 Coverage (커버리지) 그 자체보다 한 단계 높은 **테스트 품질 (Test Quality)**에 관한 이야기이지만, "수치를 목적화하면 구현이 왜곡된다"라는 동일한 반성에서 비롯되었습니다.
더불어, cortex의 테스트 품질 가이드라인 서두에 "커버리지는 목적이 아니라 보조 지표이다"라고 명문화하였고, 임계값(Threshold)을 낮추거나 istanbul ignore를 통한 회피를 Critical (치명적) 사유로 반려하도록 Auto Review (자동 리뷰) 관점에 포함했습니다. 커버리지를 충족하는 구현이라 할지라도, "조건 분기를 의도적으로 삭제했다"거나 "예외를 무시(swallow)했다"고 판단되면 Major (주요) 지적 사항이 반환됩니다.
수치 목표로 인해 구현이 왜곡되었던 반성을 바탕으로, 가이드라인으로 사상을 명시 → lint로 기계적으로 차단 → Auto Review로 관점으로서 평가까지 이어지도록 설계하여, 비로소 Coverage 90%가 "최소한의 바닥"으로서 기능하게 되었습니다. 이것 또한 Part 4에서 기술했던 재발 방지 (같은 함정에 두 번 빠지지 않는 구조)의 계보입니다.
병렬 sub-agent에서 순차 평가로의 전환
세 번째는 Auto Review의 내부 구조에 관한 이야기입니다. 9가지 관점을 병렬 sub-agent에 분산하여 동시에 리뷰하게 하는── 언뜻 보기에는 "병렬이면 빨라지고 품질도 보장될 것 같다"고 보이는 이 설계를 처음에 시도했다가 버렸습니다.
실제로 확인해 보니, 시간, 비용, 정밀도 모두 하락한다는 것을 알게 되었습니다.
시간이 더 걸림 ── sub-agent의 기동, 각 sub-agent로의 컨텍스트 로드(Context Load), 결과의 집계 등 각각에 오버헤드가 발생합니다. "9개 병렬이니까 9배 빠르다"가 되지 않으며, 하나의 session (세션)에서 순차적으로 평가하는 것보다 느려지는 경우도 있었습니다.
비용이 더 듦 ── 각 sub-agent는 독립적으로 PR의 diff와 가이드라인, 관련 코드를 다시 읽어야 하므로, 공통 컨텍스트 로드가 9번 실행됩니다. 실측 결과 9배가 아닌 4배 미만의 토큰 소비가 발생했습니다 (diff 이외의 컨텍스트는 많은 관점에서 공통되었기에 9배까지 불어나지 않고 수렴했다는 내역입니다).
정밀도가 낮음 ── 병렬 sub-agent끼리는 서로의 판정 결과를 보지 않고 독립적으로 평가하기 때문에, 동일한 문제에 대해 한쪽은 "APPROVE", 다른 한쪽은 "REQUEST_CHANGES"와 같이 엇갈린 지적이 나옵니다. 동일한 지적의 중복도 빈번하게 발생합니다. "전체적으로 어떤 PR인가"를 고려한 판정을 내릴 수 없으므로, 관점을 나눌수록 국소 최적화(Local Optimization)가 되어 전체적인 정합성이 떨어집니다.
순차 평가로 전환하면, 동일한 session에서 9가지 관점을 순서대로 판정하므로 컨텍스트 로드는 1번으로 충분하며, 이전 관점에서 내린 판정을 바탕으로 다음 관점을 살펴볼 수 있습니다. 속도, 비용, 정밀도 세 가지가 동시에 개선되었습니다.
물론 순차 평가에는 **관점 간의 순서 의존성 (Order Dependency)**이 발생합니다 ── 이전 관점의 판정이 후속 평가에 영향을 미칠 수 있다는 트레이드오프(Trade-off)입니다. 이는 인지한 상태에서 허용했습니다. 관점이 완전히 독립적이어서 서로 상충하는 리뷰를 반환하는 것보다, 순서 의존성을 허용하더라도 관점 간의 정합성을 맞추는 것이 9가지 관점을 조합한 리뷰로서 더 실용적이라는 판단입니다.
교훈은, "병렬로 하면 빨라지고 품질이 올라간다"라는 분산 시스템적인 직관이 AI Harness (AI 하네스)에서는 전제가 깨질 수 있다는 것입니다. 손에 든 CPU 코어를 병렬화하는 이야기와 달리, AI의 경우 **컨텍스트가 "공유 메모리"가 아니라 "각 프로세스의 소유물"**이 됩니다. 하나의 session에서 연속적으로 평가하는 것이 속도, 토큰 효율, 관점 간의 정합성 모두를 동시에 개선합니다 ── 이는 설계 시 놓치기 쉬운 구조적인 특성입니다.
이 장에서 하고 싶은 말
연재를 통해 써 내려온 최종 형태는 수많은 시행착오(Trial and Error)를 거친 결과입니다. 처음부터 정답이 보였고 그대로 만들어온 것이 아닙니다. 매몰 비용(Sunk Cost)을 안고 버리는 판단, 자신이 선택한 메트릭스(Metrics)를 목적화하게 되는 함정, 자연스러워 보이는 분산 구성이 역효과를 내는 패턴 ── 이러한 것들을 딛고 지금의 형태에 도달했습니다.
쉽다고 생각하지 않습니다. 하지만 이 정도까지 해내면 제대로 된 성과가 따라온다는 것이 지금의 실감입니다.
연재를 마치며
6편의 연재를 통해 전달하고 싶었던 것은 결국 하나라고 생각합니다.
AI 코딩은 "사용법"의 문제가 아니라, "사용 환경의 설계"에 관한 문제다.
다른 방식으로 말하자면, 이렇게도 말할 수 있습니다.
AI는 신뢰하는 것이 아니라, 설계하는 것이다.
대규모 코드베이스(Large-scale codebase)를 전제로 한다면, 프롬프트 엔지니어링 (Prompt Engineering) / 모델 선택 / 도구 선정 ── 이들 모두 개별적으로는 중요하지만, 그것들을 연마하는 것만으로는 PR(Pull Request)의 자동 머지(Merge)나 장애의 자동 복구, 혹은 비엔지니어의 개발 단계까지는 도달할 수 없습니다. 그 단계에 도달하기 위해서는 **AI가 추론할 필요가 없는 코드베이스 / 업무 흐름 (Workflow) / 관측 (Observability) / 수정 사이클 (Correction Cycle)**을 정비해야 하며, 이는 개별적인 AI 기술의 문제가 아니라 환경 설계 (Environment Design)의 문제입니다 (반대로, 파일이 수십 개 정도인 작은 프로젝트라면 현재의 AI 모델 단독으로도 충분히 기능하는 상황이 있습니다. 하네스 (Harness)가 필수적이 되는 시점은, 한 사람이 전체를 머릿속에 다 담을 수 없는 규모에 도달했을 때입니다).
그리고 환경 설계의 뿌리에 있는 사상은, 반복해서 말씀드리지만 "AI를 신뢰하지 않는 것" ── 전달되지 않은 컨텍스트 (Context)는 알 수 없으며, 아무것도 말하지 않으면 이상적인 상태가 되지 않는다는 현실을 직시하는 것입니다. 이 전제를 받아들이면 무엇을 만들어야 할지는 자연스럽게 결정됩니다.
되돌아보면, 여기까지 오면서 효과적이었다고 생각되는 판단은 4가지가 있습니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기