멀티 에이전트, 하나의 하네스 (Multi-agent, One Harness)
요약
다양한 AI 코딩 에이전트(Claude Code, Cursor, Aider 등) 사용 시 발생하는 컨벤션 불일치 문제를 해결하기 위한 전략을 제시합니다. '공유된 콘텐츠, 도구별 연결' 원칙을 통해 단일 진실 공급원(SSOT)을 구축하는 방법을 다룹니다.
핵심 포인트
- 도구별로 파편화된 컨벤션 규칙을 동기화하여 드리프트 현상 방지
- 이식 가능한 규칙은 프로젝트 루트의 공유 파일에 통합 관리
- 각 에이전트의 설정 파일이 공유 파일을 참조하도록 연결
- 슬래시 명령어 등 도구 특화 기능은 별도로 관리
팀원 중 절반은 Claude Code를 사용합니다. 3분의 1은 Cursor를 사용합니다. 두 명의 엔지니어는 Aider를 신뢰합니다. 한 명은 스프린트 기간 동안 호기심에 Codex를 실행해 보았습니다. 다섯 명의 서로 다른 에이전트가 동일한 컨벤션(Conventions)의 다섯 개 복사본을 읽어야 했지만, 예상대로 그렇지 않았습니다. Claude Code 사용자들은 가장 완전한 CLAUDE.md를 가지고 있었습니다. Cursor 사용자들은 그것과 중복되는 .cursor/rules 파일을 가지고 있었습니다. Aider 사용자들은 .aider.conf.yml과 더불어 다른 이들과는 동떨어진 CONVENTIONS.md를 가지고 있었습니다.
팀 내에서 도구 간의 드리프트(Drift)가 발생하고 있었습니다. 이는 엔지니어들이 코드를 작성하는 방식에 대해 의견이 달라서가 아니라, 각 에이전트가 규칙의 약간씩 다른 복사본을 가지고 있었고 아무도 이를 동기화하지 않았기 때문입니다.
해결책은 아무도 가장 먼저 하고 싶어 하지 않았던 것이었습니다. 바로 단일 진실 공급원(Single source of truth)을 구축하고, 각 에이전트의 도구별 설정(Tool-specific config)이 이를 어떻게 가리킬지에 대한 전략을 세우는 것이었습니다. 이 포스트가 바로 그 내용입니다. 공유 파일에는 무엇을 넣을지, 무엇을 도구별로 유지할지, 그리고 하네스(Harness)가 어디에서 분기(Fork)되어야 하는지에 대해 다룹니다.
깔끔하게 변환되는 것들
하네스의 대부분은 평이한 영어로 작성됩니다. "마이그레이션(Migrations) 디렉토리를 수정할 때는 모킹된 클라이언트(Mocked client)가 아니라 실제 Postgres를 대상으로 테스트 스위트(Test suite)를 실행하세요."라는 문장은 Claude Code, Cursor, Aider, 그리고 자연어를 읽고 행동하는 모든 에이전트에게 의미가 있습니다. 이 규칙은 도구에 관한 것이 아니라 코드베이스(Codebase)에 관한 것이기 때문에 이식성(Portable)을 가집니다.
컨벤션 규칙(Convention rules)은 이식됩니다. 아키텍처 제약 조건(Architectural constraints)도 이식됩니다. 에스컬레이션 패턴(Escalation patterns)도 이식됩니다(대부분 그렇습니다; 중단하는 구문은 제각각입니다). "조율 없이 legacy/를 편집하지 마세요"라는 규칙도 이식됩니다. 디렉토리 구조를 공유하므로 파일 경로 범위 지정(File-path scoping)도 이식됩니다.
건강한 CLAUDE.md의 대부분은 이식 가능합니다. 우리에게 효과적이었던 형태는 모든 에이전트가 읽을 수 있는 프로젝트 루트(Project root)의 파일에 이식 가능한 규칙을 넣고, 각 도구의 설정이 해당 파일을 가리키도록 하는 것이었습니다.
Claude Code의 경우, 이는 최상위 레벨의 CLAUDE.md를 의미합니다. Cursor의 경우, 동일한 내용을 가져오는(import) 최상위 레벨의 .cursor/rules/ 파일, 또는 심볼릭 링크(symlink), 혹은 공유 소스로부터 도구별 파일을 생성하는 빌드 단계(build step)를 의미합니다. Aider의 경우, CONVENTIONS.md 플래그가 공유 파일을 가리키도록 합니다. codex의 경우, 그에 상응하는 설정(config)을 사용합니다.
원칙은 '공유된 콘텐츠, 도구별 연결(shared content, per-tool wiring)'입니다. 다섯 개의 복사본을 만드는 것이 아닙니다.
번역(이식)되지 않는 것들
어떤 규칙들은 번역(이식) 과정에서 유지되지 않는 방식으로 도구에 특화되어 있습니다.
슬래시 명령어(Slash commands) 및 스킬(skills). Claude Code에는 슬래시 명령어와 스킬이 있고, Cursor에는 자체적인 커스텀 모드(custom modes)가 있으며, Aider에는 매크로(macros)가 있습니다. "머지(merge)하기 전에 /security-audit 스킬을 통해 보안 감사(security audit)를 실행하십시오"라는 규칙은 Claude Code 전용 문장입니다. 다른 도구들을 위한 상응하는 규칙은 다른 명령어를 가리키는 별개의 문장이 됩니다. 이들은 공유되지 않으며, 각 도구는 자신만의 블록을 가집니다.
도구별 실패 모드(failure modes). 각 에이전트는 저마다의 나쁜 습관을 가지고 있습니다. Claude Code는 지나치게 의욕적인 리팩터링(refactor)을 하는 경향이 있습니다. Cursor는 다중 파일 변경 시 다른 형태의 특징적인 실패 양상을 보입니다. 컨텍스트 로딩(context loading)과 관련된 Aider의 실패 모드는 앞선 두 도구와 다릅니다. 도구별 실패 모드를 잡아내는 규칙들은 도구별 설정(tool-specific configs)에 속해야 합니다. 왜냐하면 이 규칙들은 다른 에이전트들에게는 관련이 없으며, 어디에 위치하든 토큰 비용(token cost)을 발생시키기 때문입니다.
훅(Hook) 구문 및 통합(integration). Claude Code의 settings.json 훅, Cursor의 명령어, Aider의 프리 커밋(pre-commit) 통합 등, 이를 참조하는 규칙들은 도구별 메커니즘을 가리킵니다. "프리 커밋 훅(pre-commit hook)이 이를 거부할 것입니다. 우회하지 마십시오"라는 규칙은 이식 가능합니다. 하지만 "verify 스킬은 .claude/skills/verify/SKILL.md에 정의된 체크를 실행합니다"라는 규칙은 이식 불가능합니다.
컨텍스트 윈도우 (Context window) 제한. 각 에이전트는 서로 다른 컨텍스트 윈도우 계산 방식을 가집니다. "파일이 2,000줄 이상이면 파일 전체를 읽는 대신 필요한 섹션만 읽으세요"라는 규칙은 어떤 도구에게는 핵심적인 역할을 할 수 있지만, 다른 도구에게는 무관할 수 있습니다. 에이전트가 자신의 컨텍스트를 어떻게 관리해야 하는지에 대한 규칙은 해당 컨텍스트를 소유한 도구의 영역에 속합니다.
경험적인 원칙: 코드베이스에 관한 것은 공유하고, 도구에 관한 것은 유지합니다.
분기(Forking) 문제
가장 어려운 부분은 쉬운 분리가 아닙니다. 가장 어려운 부분은 대부분 공유되어야 하지만, 도구마다 약간의 변형이 필요한 규칙들입니다.
에스컬레이션 단계 (Escalation ladder)가 전형적인 예시입니다. "한 번에 3개 이상의 파일을 편집하기 전에 중단하고 질문하세요"는 이식 가능한 규칙입니다. 에이전트가 중단 상황을 전달하는 방식("어시스턴트 메시지를 통해 질문" vs "사이드 패널 열기" vs "채팅에 게시")은 도구별로 특화되어 있습니다. 이때 규칙을 하나로 작성할 것인지, 아니면 두 개로 작성할 것인지의 문제입니다.
성공적이었던 패턴은 다음과 같습니다: 공유 파일에 이식 가능한 규칙을 평이한 영어 명령문으로 작성합니다. 각 도구의 설정(Config)에는 "이 규칙이 적용될 때, 이 도구에서는 X를 수행하라"는 도구별 메커니즘을 담은 한 줄의 추가 사항을 넣습니다.
따라서 공유 파일에는 규칙이 있고, 각 도구의 파일에는 구현 훅(Implementation hook)이 있습니다. 공유 파일이 5개의 도구별 절로 비대해지지 않으며, 각 도구의 파일이 공유 규칙을 중복해서 작성하지도 않습니다. 분기는 가능한 가장 작은 범위에서 이루어집니다.
이 방식은 도구별 예외 사항이 있는 규칙의 약 80% 정도에 적용 가능합니다. 나머지 20%는 진정으로 충분히 다르기 때문에 별도의 파일에 별도의 규칙으로 작성됩니다. 이들이 같다고 가정하는 것은 노력을 절약해주지 않습니다.
동기화(Sync) 문제
단순한 해결책은 하나의 공유 파일과, 이를 모두 참조하는 도구별 설정(Tool-specific configs)을 두는 것입니다. 이 참조는 심볼릭 링크 (Symlink), 포함 지시어 (Include directive), 빌드 단계 (Build step), 또는 관례 (Convention)가 될 수 있습니다.
우리가 발견한 유지보수 비용이 가장 낮은 버전은 프로젝트 루트(root)에 공유된 CONVENTIONS.md를 두고, 각 도구의 설정(config)이 이를 포함(include)하거나 참조(reference)하도록 하는 방식입니다. Claude Code의 CLAUDE.md는 Read CONVENTIONS.md for the team's portable conventions.(팀의 이식 가능한 관례를 확인하려면 CONVENTIONS.md를 읽으세요)라는 문구로 시작하며, 그 뒤에 Claude 전용 규칙들이 뒤따릅니다. Cursor의 설정도 동일하게 작동합니다. Aider의 CONVENTIONS.md 플래그는 해당 파일을 직접 가리킵니다.
에이전트들은 모든 세션에서 동일한 관례(conventions) 내용을 읽습니다. 파일이 단 하나뿐이기에 드리프트(drift, 괴리)는 구조적으로 불가능합니다.
단순한 시스템이 대개 승리하는 것과 같은 이유로, 더 단순한 구조가 승리합니다. 실패 모드(failure modes)가 명확하기 때문입니다. 만약 공유 파일이 누락되면 모든 에이전트가 동일한 방식으로 실패하며, 당신은 이를 한 번만 수정하면 됩니다. 만약 내용이 틀렸다면, 한 번만 수정하면 다음 세션에서 모든 에이전트가 개선됩니다.
팀 관행 (The team practice)
구조를 유지하는 팀 관행은 단 하나의 규칙입니다. 이식 가능한(portable) 하네스(harness) 변경 사항은 공유 파일에 담습니다. 도구별(tool-specific) 하네스 변경 사항은 도구별 파일에 담습니다.
PR(Pull Request) 리뷰가 이를 강제합니다. 누군가 공유 파일에 도구별 조항을 추가하면, 이를 이동시키라는 피드백을 받게 됩니다. 누군가 도구별 파일에 이식 가능한 조항을 추가하면, 반대로 이동시키라는 피드백을 받게 됩니다. 이 규율은 작지만 지속적입니다.
두 번째 관행: 새로운 도구가 팀에 합류할 때, 이를 온보딩(onboarding)하는 엔지니어가 도구별 설정(tool-specific config)을 작성하며, 팀은 무엇을 공유 파일로 옮겨야 할지 검토합니다. 이 리뷰 과정을 통해, 누구도 알아차리지 못한 채 암묵적이고 도구별로만 존재했던 관례들이 드러나게 됩니다.
세 번째 관행: 드리프트 감사(drift audits). 분기에 한 번, 누군가가 모든 도구별 설정을 읽고 그 안에 몰래 끼어든 이식 가능한 콘텐츠가 있는지 확인합니다. 감사는 30분 정도 소요되며, 보통 이동시켜야 할 규칙 두세 개를 찾아냅니다.
하네스가 분기되어야 하는 지점 (Where the harness has to fork)
하네스가 진정으로 분기되어야 하는 경우가 있습니다. 에이전트의 역량이 서로 다르기 때문입니다. 한 번에 많은 컨텍스트 (Context)를 읽을 수 있는 에이전트에게 적합한 규칙은, 아주 적은 양만 읽을 수 있는 에이전트에게 적합한 규칙과 다릅니다.
만약 제가 두 모델을 모두 실행하고 있다면, 1M 컨텍스트 모델을 사용하는 Claude Code의 하네스는 더 작은 모델을 사용하는 Claude Code의 하네스와는 다를 것입니다. "편집하기 전에 전체 파일을 읽으시오"라는 규칙은 첫 번째 사례에서는 괜찮지만, 두 번째 사례에서는 실행 불가능합니다.
만약 여러분의 팀이 매우 다른 역량을 가진 에이전트들을 운영하고 있다면, 하네스의 분기는 실재하며 이를 추상화하여 없앨 수는 없습니다. 공유 파일 (Shared file)은 그들이 공통적으로 가진 것입니다. 도구별 파일 (Tool-specific files)이 단순한 통합용 접착제 (Integration glue)가 아닌, 실제 작업을 수행합니다.
제가 함께 일했던 팀 중 가장 깔끔한 설정을 가졌던 팀은, 각각 약 60%의 공유 콘텐츠와 40%의 도구별 콘텐츠로 구성된 두 개의 설정 (Configs)을 가지고 있었습니다. 그 비율이 그들의 상황에 대한 진실이었습니다. 더 많은 공유 콘텐츠를 지향하려고 시도했다면, 어느 에이전트에게도 잘 맞지 않는 규칙들을 만들게 되었을 것입니다.
교훈: 공유를 목표로 하되, 분기가 실재할 때는 이를 받아들이고, 분기된 것을 공유된 것처럼 가장하지 마십시오.
공유 파일이 되어서는 안 되는 것
공유 파일은 누군가가 하네스에 넣고 싶어 하는 모든 것을 쏟아붓는 쓰레기통이 아닙니다. 그것은 이 코드베이스를 다루는 모든 에이전트에게 진정으로 적용되는 규칙이어야 합니다. 이 파일은 팀 내의 모든 에이전트에 의해 읽히기 때문에, 기준은 낮아지는 것이 아니라 더 높아져야 합니다.
저는 팀들이 하네스 전체를 "공유" 파일에 넣어두고는, 에이전트들이 일관성 없게 행동한다고 불평하는 것을 보았습니다. 에이전트들은 동일한 파일을 읽고 있습니다. 다만 그 파일이 수행하도록 요청받은 작업에 맞게 설계되지 않았을 뿐입니다. 규칙의 절반은 도구별 (Tool-specific)이며, 해당 규칙이 필요하지 않은 에이전트들은 매 세션마다 토큰 비용 (Token cost)과 어텐션 비용 (Attention cost)을 지불하고 있습니다.
공유 파일은 작고 밀도가 높습니다. 각 도구의 파일은 더 크며 도구별 작업을 담고 있습니다. 이러한 전형적인 "공유 = 더 큼"이라는 가정을 뒤집는 것이 구조를 안정적으로 만듭니다.
Keystone은 설계 단계부터 에이전트 불가지론적 (Agent-agnostic)입니다
Keystone은 이러한 분리를 기반으로 제가 구축한 스캐폴더 (Scaffolder)입니다. 하네스 (Harness)는 하나의 마크다운 (Markdown) 디렉토리이며, 에이전트별 와이어링 (Wiring)은 도구당 하나의 작은 파일로 구성됩니다.
하네스는 프로젝트 루트 (Project root)에 위치합니다. 원칙 (Principles), 관용구 (Idioms), 도메인 (Domain), 상태 (State), 프로세스 (Process)의 다섯 가지 계층으로 이루어져 있습니다. 이 중 그 어느 것도 어떤 에이전트가 이를 읽을지 알지 못합니다. 그것이 설계 의도입니다. 하네스는 사후에 덧붙여진 것이 아니라, 기초 단계부터 에이전트 불가지론적 (Agent-agnostic)으로 설계되었습니다.
나머지는 어댑터 패턴 (Adapter pattern)이 처리합니다. 각 에이전트는 harness/adapters/<agent>/ 아래에 lifecycle.md, sensors.md, activation.md라는 세 개의 파일이 포함된 작은 디렉토리를 할당받습니다. 활성화 (Activation) 파일은 에이전트가 가장 먼저 읽는 파일입니다. Claude Code의 경우 CLAUDE.md, Codex CLI의 경우 AGENTS.md, Cursor의 경우 .cursor/rules/000-harness.mdc가 해당됩니다. 이 파일의 역할은 관례 (Convention)에 따라 에이전트가 공유 하네스를 가리키도록 안내하는 것입니다.
Claude Code, Codex CLI, pi.dev를 위한 어댑터는 실제로 구현되어 있습니다. 나머지는 기능적 공백 (Features gaps)이 있는 상태로 제공됩니다. 즉, 시작하기에 충분한 수준의 최소한의 라이프사이클 (Lifecycle) 파일과 작동하는 메뉴가 포함되어 있습니다. 이러한 공백은 init 과정 중 경고 메시지와 함께 문제를 해결하기 위한 제안된 작업(Suggested actions)을 통해 처리됩니다. 또한 범용 폴백 (Generic fallthrough)도 존재합니다. 이는 어댑터가 수행해야 할 계약 (Contract)과 공유 코퍼스 (Shared corpus)를 읽으라는 기본 지침을 포함합니다. 팀에 새로운 에이전트가 합류하는 것은 재작성 (Rewrite)이 아니라, 스텁 (Stub)을 채워 넣는 과정입니다.
이 방식의 이점은 동기화 (Sync)입니다. 표준에 따라 모든 에이전트가 가리키는 단 하나의 디렉토리가 존재합니다. 규칙이 변경되면 한 곳에서 변경됩니다. 드리프트 (Drift) 문제는 다시 발생할 수 없습니다. 왜냐냐 드리프트가 발생할 수 있는 지점 자체가 없기 때문입니다.
여러 에이전트를 운영하는 사람에게 해주고 싶은 말
도구별 설정 (Tool-specific configs)을 감사 (Audit)하여, 도구에 관한 내용이 아니라 실제 코드베이스 (Codebase)에 관한 내용이 포함되어 있는지 확인하십시오. 코드베이스 관련 내용은 하나의 공유 파일로 옮기십시오. 그리고 각 도구의 설정이 해당 파일을 참조하도록 만드십시오.
이식 가능한 콘텐츠 (Portable content)는 공유 파일에, 도구별 콘텐츠 (Tool-specific content)는 도구별 파일에 담는다는 규율을 세우십시오. 이를 PR 리뷰 (PR review)에서 강제하고, 분기별로 감사하십시오.
일부 규칙은 진정으로 분기(fork)되어야 한다는 점을 받아들이십시오. 이러한 분기는 아키텍처(architecture)의 실패가 아닙니다. 오히려 도구 간의 차이점에 대한 진실을 아키텍처가 당신에게 알려주는 것입니다. 분기를 평탄화(flatten)하려고 시도하는 팀은 결국 어떤 에이전트(agent)에게도 잘 맞지 않는 규칙을 갖게 됩니다.
통합 하네스(unified harness)는 팀이 합의한 하나의 파일과 도구별 글루(tool-specific glue)로 구성됩니다. 모든 것을 수행하는 척하는 단 하나의 파일이 아닙니다. 규율이란 공유 파일은 공유된 상태로 유지하고, 특정 파일은 특정 용도로만 유지하는 것입니다.
신뢰할 수 있는 단일 원천(source of truth)이 하나라면 드리프트(drift)는 사라집니다. 나머지는 배선(wiring)의 문제입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기