하네스 엔지니어링 전략

Cloud_ Ghost·2026년 4월 9일

AI

목록 보기
40/41

하네스 엔지니어링 전략 카탈로그 + 도입 프레임워크

한 줄 요약: 하네스 엔지니어링 = "모델 호출"이라는 단일 함수를 둘러싼 6개 레이어(Transport / Loop / Context / Tool / Permission / Extension)를 명시적으로 분리하고, 각 레이어를 검증된 OSS 패턴 중 하나로 구현한 뒤, 레이어 간 결합을 최소화하는 것.


Part 0. "하네스"의 두 층위 분리

층위의미본 레포에서의 위치
빌드 하네스소스 → 실행 가능한 바이너리/번들로 만드는 스캐폴딩scripts/build.mjs, stubs/, vendor/
런타임 하네스모델 호출을 둘러싸고 루프·툴·권한·컨텍스트를 조립하는 스캐폴딩src/query.ts, src/Tool.ts, src/services/tools/*, src/utils/permissions/*, src/utils/hooks.ts, src/services/compact/*

이 문서는 런타임 하네스가 중심입니다(자체 에이전트를 만들 때 가장 큰 가치가 거기 있기 때문).


Part 1. Claude Code에서 추출한 핵심 전략 (8개)

1.1 AsyncGenerator 기반 스트리밍 루프

  • 무엇: 메인 루프를 async function*로 구현. 모든 진행 상태(stream_request_start, 메시지, 툴 결과, 에러)를 yield로 흘려보냄.
  • 어디: src/query.ts:219 query(), src/query.ts:241 queryLoop(), while(true) 메인 루프 :307
  • 왜:
    1. UI/SDK 양쪽에 동일 인터페이스로 점진적 결과 전달
    2. 취소(abort)를 yield 포인트에서 자연스럽게 처리
    3. 메모리 효율(누적 안 함)
    4. 테스트가 쉬움(이벤트 시퀀스 단위 assertion)
  • 차용 방식: 에이전트의 entry를 단순 Promise<Result>가 아니라 AsyncIterable<AgentEvent>로 정의. CLI/HTTP/SSE/IDE 어댑터가 이 한 인터페이스만 소비.

1.2 계층화된 에러 복구 (Layered Recovery)

  • 무엇: 에러 종류별로 "작은 재시도 → 중간 재시도 → 큰 재시도 → graceful fail"의 4단 사다리.
  • 어디: src/query.ts:1050 collapse_drain, :1119 reactive_compact, :1188 max_output_tokens_recovery (최대 3회), :1267 stop hook blocking
  • 왜: 한 종류 에러를 만나도 즉시 실패하지 않고, 점점 더 비싼 복구 수단으로 escalate. 재시도 가드(hasAttemptedReactiveCompact)로 무한 루프 차단.
에러1단계 복구2단계 복구3단계
Prompt too long (413)reactive compact (요약)collapse drain (탐욕적 재평가)yield error + exit
Max output tokensescalate 8k→64krecovery message 주입 + 재시도 (최대 3회)yield error + exit
Stream fallbacktombstone orphaned + executor reset
Stop hook blockuser message 주입 + continue
  • 핵심 코드 패턴:
    if (err === 'PROMPT_TOO_LONG' && !state.hasAttemptedReactiveCompact) {
      state = {
        ...state,
        messages: compact(state.messages),
        hasAttemptedReactiveCompact: true,
        transition: { reason: 'reactive_compact_retry' }
      }
      continue
    }
  • 차용: 에러를 단일 try/catch가 아니라 유한 상태 머신의 전이로 다루기. 각 복구 단계마다 상태 가드를 두어 한 번만 시도되게.

1.3 도구의 자기-선언적 동시성 (Self-declared Concurrency)

  • 무엇: 도구가 자신이 read-only인지(isConcurrencySafe, isReadOnly, isDestructive)를 직접 선언. 오케스트레이터는 이 선언만 보고 배치를 분할.
  • 어디: src/Tool.ts:362+, src/services/tools/toolOrchestration.ts:91 partitionToolCalls(), src/services/tools/StreamingToolExecutor.ts:40
  • 분할 규칙:
    • 연속된 read-only → 하나의 병렬 배치
    • write/destructive → 각자 직렬 배치
    • 동시성 상한: CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY (기본 10)
  • 왜: 오케스트레이터가 도구별 의미를 알지 않아도 됨 → 새 툴 추가 시 정책 코드 변경 0. 안전성 ≥ 성능 원칙.
  • 차용: 툴 인터페이스에 concurrency: 'safe' | 'serial' | 'destructive' 필드를 강제. 오케스트레이터는 이 필드만 보고 자동 분할.

1.4 4단계 권한 필터 (Permission Funnel)

  • 무엇: 도구 호출 전에 설정 규칙 → Hook → 분류기(ML) → 사용자 UI 순으로 통과시키는 funnel.
  • 어디: src/utils/permissions/permissions.ts:200 hasPermissionsToUseTool(), src/hooks/useCanUseTool.tsx:27, src/utils/permissions/yoloClassifier.ts
  • 왜: 빠른 결정(allowRules)부터 점점 비싼 결정(사용자 클릭)까지 단계적으로. 사용자 피로 최소화 + 안전 보장.
  • 차용: 권한 결정을 단일 if가 아니라 filter chain으로 구성. 각 필터는 allow | deny | next를 반환. ML 분류기는 옵션(피처 플래그).

1.5 우선순위 기반 시스템 프롬프트 조립

  • 무엇: 시스템 프롬프트를 단일 문자열이 아닌 선언적 우선순위 슬롯으로 조립.
  • 어디: src/utils/systemPrompt.ts:41-123 (override → coordinator → agent → custom → default → appendSystemPrompt)
  • 왜: 사용자 정의(--system-prompt), 서브에이전트 프롬프트, 모드별 프롬프트가 충돌 없이 합성됨.
  • 차용: 시스템 프롬프트를 Section[] 배열로 두고, 각 섹션이 priority, mode, compute()를 갖게 함. 조립 함수는 그저 정렬 + concat.

1.6 시스템 프롬프트 섹션 캐싱 (정적/동적 명시 분리)

  • 무엇: 각 프롬프트 섹션이 "캐시 안전(정적)"인지 "캐시 깨짐(동적)"인지 코드에 명시.
  • 어디: src/constants/systemPromptSections.tssystemPromptSection() (정적, /clear까지 메모이즈) vs DANGEROUS_uncachedSystemPromptSection() (호출 때마다)
  • 왜: prompt cache hit이 비용/지연의 결정적 변수. 캐시 깨짐을 우발적이 아닌 명시적 선택으로 만들기.
  • 차용: 모든 동적 컨텍스트 빌더에 cacheable: boolean 플래그를 강제. 동적 섹션은 함수명에 Dangerous 같은 접두사로 경고.

1.7 컨텍스트 컴팩션의 계층적 완화 (Graduated Compaction)

  • 무엇: 토큰 한계 접근 시 가벼운 → 무거운 순으로 단계적 압축.
  • 어디: src/services/compact/autoCompact.ts, src/query/tokenBudget.ts (BudgetTracker로 추세 감지: 3턴 연속 <500 토큰 델타 → diminishing returns)
  • 단계:
    1. 이미지 제거
    2. 세션 메모리 압축
    3. 전체 대화 요약
    4. 3회 연속 실패 시 circuit breaker
  • 왜: 매번 풀 컴팩션은 비싸고 캐시도 깨짐. 추세 감지 + 단계적 완화로 비용 최소화.
  • 차용: 컴팩션을 단일 함수가 아닌 Strategy[] 체인으로. 각 전략의 비용/복원율을 메타데이터로.

1.8 SubAgent: Context Clone + Cache-Safe Fork

  • 무엇: 서브에이전트는 부모 컨텍스트를 clone하되, 프롬프트 캐시 적중을 위해 cache-safe params를 명시 보존.
  • 어디: src/utils/forkedAgent.ts:57-81 CacheSafeParams, src/tools/AgentTool/forkSubagent.ts, src/tools/AgentTool/runAgent.ts
  • 왜: 서브에이전트는 격리되어야 하지만(부모 메시지를 오염시키면 안 됨) 비용 절감 위해 prompt cache는 공유. CacheSafeParams가 부모/자식의 호모그래프를 검증.
  • 차용: subagent fork를 단순 "재귀 호출"이 아니라 (SharedCacheParams, IsolatedState) 페어로 구조화. 자식이 cache prefix를 깨지 않도록 강제.

Part 2. 다른 OSS에서 추출한 핵심 전략 (Claude Code에 없는 것 위주)

2.1 Aider — Edit Format & Repo Map (PageRank)

  • Edit format fallback chain: SEARCH/REPLACE 정확 매칭 실패 → 공백 유연 → 엘립시스 청크 매칭 → 80% 유사도 퍼지 매칭 → 진단 메시지로 모델 자가 수정 유도. 편집기가 곧 하네스의 핵심임을 보여주는 가장 정교한 사례.
  • PageRank repo map: tree-sitter로 함수/클래스 그래프 구축 → PageRank로 중요도 매기고 토큰 예산 안에서 시그니처만 노출. 채팅에 파일이 추가되면 map 동적 수축.
  • 차용 가치: ★★★★★ — 코드 편집 정확도와 코드베이스 이해도 두 핵심 영역에서 가장 검증된 기법.

2.2 OpenHands — Event Stream + Action/Observation

  • 에이전트 루프를 메서드 체인이 아니라 pub/sub 이벤트 버스로. Action/Observation이 1급 시민. Runtime이 REST(/execute_action)로 분리되어 controller와 실제 실행 환경이 네트워크로 격리.
  • 차용 가치: ★★★★ — 멀티 클라이언트(CLI/Web/IDE), 멀티 에이전트, 원격 실행을 처음부터 고려할 때 가장 확장 가능한 아키텍처.

2.3 SWE-agent — ACI (Agent-Computer Interface) 철학

  • 핵심 명제: "인간용 터미널 툴(vi, less, grep)은 LLM에게 부적합하다. LLM 전용 인터페이스를 새로 설계하라." SWE-bench 성능 2x+ 향상으로 입증.
  • 구체적 결정:
    1. 파일 viewer는 ~100 라인 윈도우 + scroll/search
    2. 편집은 즉석 lint 검증, 실패 시 거부
    3. 빈 출력에도 명시적 "No output" 메시지
    4. state_command로 매 턴 현재 상태(열린 파일, cwd, 커서) JSON으로 동적 주입
  • 차용 가치: ★★★★★ — 가장 저평가된 전략. 좋은 프롬프트보다 좋은 인터페이스가 더 효과적.

2.4 smolagents — Code-as-Action + AST 인터프리터 샌드박스

  • 행동을 JSON tool call이 아닌 파이썬 코드로 표현. 이유: composability(툴 출력을 변수에 저장해 다음 툴에 연쇄), 제어 흐름이 자연스러움, LLM 학습 분포에 풍부.
  • 안전 실행: LocalPythonExecutor가 AST를 한 연산씩 인터프리트, import allowlist, 연산 수 상한. 더 강한 격리로 E2B/Modal/Docker/Wasm을 옵션 제공.
  • 차용 가치: ★★★ — 데이터 분석이나 다단계 변환이 많은 도메인이면 강력. 일반 코딩 에이전트면 복잡도가 큼.

2.5 LangGraph — State Machine + Checkpointing + interrupt()

  • 에이전트를 명시적 그래프(노드+엣지)로 모델링. 매 super-step마다 state를 체크포인트(SQLite/Postgres) → 재시작/시간여행/브랜칭 가능. interrupt()로 human-in-the-loop가 1급 기능.
  • 차용 가치: ★★★ — long-running 작업, audit, 분기 시뮬레이션이 필요하면 매우 강력. 단순 코딩 에이전트엔 과함.

2.6 Codex CLI — OS-native 샌드박스 + Approval Cache

  • 컨테이너가 아닌 커널 LSM으로 격리: macOS Seatbelt(sandbox-exec), Linux Landlock, Windows restricted-token. 세 정책 축(filesystem, network, exec)을 분리.
  • Approval store 세션 캐시로 "이번 세션 동안만 허용" 캐싱 → 사용자 피로 감소.
  • 차용 가치: ★★★★ — 프로덕션 보안 엄격하면 사실상 표준. Docker보다 가볍고 빠름.

2.7 Continue.dev — Context Provider 시스템

  • @codebase, @file, @docs, @diff, @terminal, @problems, @url 등 사용자가 명시적으로 컨텍스트를 선택하는 @-mention. 각 provider는 IContextProvider 인터페이스를 구현. Hub로 패키지 공유.
  • 차용 가치: ★★★★ — IDE 통합이나 대화형 도구라면 자율 RAG보다 명시적 컨텍스트 주입이 더 예측 가능.

2.8 Cline / Roo Code — Plan/Act 모드 + Mode-Scoped Permissions

  • Plan mode: 파일 편집 불가, 설계만. Act mode: 실행. 모드별로 다른 모델까지 지정 가능(Plan에 Opus, Act에 Sonnet으로 비용 최적화).
  • Roo Code는 더 나아가 per-mode + file glob 기반 권한 스코핑 (예: security-reviewer 모드는 *.md에만 쓰기).
  • 차용 가치: ★★★★ — 하나의 에이전트가 여러 워크플로우를 안전하게 처리할 때 매우 효과적.

2.9 gptme — 마크다운 코드블록이 곧 툴 호출

  • ```shell, ```ipython, ```save path/to/file.py 같은 언어 태그가 곧 툴 식별자. 가장 낮은 프로토콜 오버헤드. 약한 모델에도 이식 가능.
  • 차용 가치: ★★ — Function calling이 안 되는 모델 지원이 필요할 때만.

Part 3. 전략 분류 매트릭스 (선택 가이드)

3.1 루프 패턴 결정 트리

주된 사용 시나리오는?
├─ 사용자가 매 턴 게이트키퍼       → REPL + reflection (Aider 스타일)
├─ 자율적으로 여러 턴 진행          → Recursive chat loop (Cline) 또는
│                                     AsyncGenerator loop (Claude Code)
├─ 멀티 클라이언트/원격 실행        → Event stream (OpenHands)
├─ 분기/체크포인트/감사 필요        → State machine (LangGraph)
└─ 단순 함수 호출만                 → Submission/Event 큐 (Codex)

3.2 툴 호출 프로토콜 결정 트리

지원할 모델은?
├─ 최신 폐쇄형(Claude/GPT-4)만     → Native function calling
├─ 약한 모델/로컬 모델 포함
│   ├─ 편집 위주                    → SEARCH/REPLACE (Aider)
│   ├─ 범용                        → XML 태그 (Cline)
│   └─ 단순함 추구                  → 마크다운 코드블록 (gptme)
└─ 다단계 데이터 변환 위주          → Code-as-action (smolagents)

3.3 권한 모델 결정 트리

실행 환경은?
├─ 사용자 로컬 머신 (개발자 도구)
│   ├─ 기본 신뢰                    → per-action 승인 + 화이트리스트 (Cline)
│   ├─ 엄격한 격리                  → OS 샌드박스 (Codex CLI)
│   └─ 단순함 추구                  → git 자동 커밋 = 롤백 (Aider)
├─ 서버/CI                          → 컨테이너 격리 (OpenHands, smolagents)
└─ 하이브리드                        → 4단계 funnel (Claude Code)

3.4 컨텍스트 전략 결정 트리

코드베이스 크기는?
├─ 작음 (<10K LOC)                  → 대화 로그 단순 누적 (gptme)
├─ 중간 (<100K LOC)                 → Repo map (Aider)
├─ 큼                              → Embedding RAG (Continue) 또는
│                                     Repo map + 명시적 @-mention
└─ 매우 큼 + 여러 세션              → Event stream + condenser (OpenHands)

토큰 한계 접근 시?
├─ 단순                             → Quarter compression (Cline)
└─ 정교                             → Graduated compaction + circuit breaker (Claude Code)

Part 4. 도입 프레임워크 — "하네스 6 레이어 모델"

┌─────────────────────────────────────────────────────────┐
│  L6. Extension Layer        MCP, hooks, plugins, modes  │
├─────────────────────────────────────────────────────────┤
│  L5. Permission Layer       4-stage funnel + sandbox    │
├─────────────────────────────────────────────────────────┤
│  L4. Tool Layer             self-declared concurrency   │
├─────────────────────────────────────────────────────────┤
│  L3. Context Layer          prompt slots + compaction   │
├─────────────────────────────────────────────────────────┤
│  L2. Loop Layer             AsyncGen + layered recovery │
├─────────────────────────────────────────────────────────┤
│  L1. Transport Layer        streaming model client      │
└─────────────────────────────────────────────────────────┘
레이어최소 구성출처가치
L1 Transport스트리밍 + 취소 가능 model client (한 인터페이스)모든 OSS 공통모델 교체 자유
L2 Loopasync function* + 4단 복구 사다리 + transition 메타Claude Code디버그/테스트 용이
L3 Context우선순위 슬롯 + 캐시 가능/불가능 분리 + graduated compactionClaude Code + SWE-agent ACI비용 + 정확도 동시
L4 ToolTool 인터페이스에 concurrency, readOnly, destructive 강제 + edit-format fallback chainClaude Code + Aider안전성 + 확장성
L5 PermissionallowRules → hooks → classifier → UI filter chain + approval cacheClaude Code + Codex사용자 피로 ↓
L6 ExtensionMCP 클라이언트 내장 + lifecycle hooks (Pre/PostToolUse 등) + mode 추상화Claude Code + Cline/Roo + Continue사용자 정의 ↑

Part 5. 단계별 도입 로드맵

Phase 1 — 골격 (1주차)

  1. L1: 스트리밍 model client (한 어댑터로 OpenAI/Anthropic 통일)
  2. L2 최소: async function* 메인 루프 + AgentEvent 타입 정의
  3. L4 최소: Tool 인터페이스 (name, inputSchema, call, concurrency, readOnly)
  4. L5 최소: allowRules 단일 단계만

✅ 이 시점에 단일 턴 + 단일 툴 호출이 동작.

Phase 2 — 안정성 (2주차)

  1. L2 확장: 4단 복구 사다리 + 재시도 가드(hasAttemptedXxx)
  2. L4 확장: partitionToolCalls() (read-only 병렬, write 직렬)
  3. L3 컴팩션: graduated compaction의 3단계만이라도 (이미지 제거 → 메시지 트리밍 → 요약)
  4. L4 편집기: SEARCH/REPLACE + 공백 유연 fallback (Aider Phase 1만)

✅ 이 시점에 멀티턴 + 에러 복구가 동작.

Phase 3 — 컨텍스트 엔지니어링 (3주차)

  1. L3 프롬프트 슬롯: 우선순위 기반 시스템 프롬프트 조립
  2. L3 캐싱 분리: cacheable: boolean 플래그 강제
  3. L3 ACI: 파일 viewer를 ~100 라인 윈도우로 제한, lint-on-edit, "No output" marker
  4. L3 repo map: tree-sitter + PageRank (작은 코드베이스용 v1)

✅ 이 시점에 정확도가 큰 폭 상승.

Phase 4 — 권한과 확장 (4주차)

  1. L5 funnel: allowRules → hooks → UI (분류기는 옵션)
  2. L5 approval cache: 세션 단위 캐싱
  3. L6 hooks: Pre/PostToolUse, UserPromptSubmit (Claude Code 스키마 그대로 차용)
  4. L6 MCP: stdio 전송만이라도 클라이언트 내장
  5. L6 modes: Plan / Act 최소 2개

✅ 이 시점에 사용자 정의 워크플로우 가능.

Phase 5 — 격리 + 서브에이전트 (5주차+)

  1. L5 sandbox: macOS Seatbelt 또는 Linux Landlock (Docker보다 가볍게)
  2. L2 subagent: cache-safe fork + AgentTool
  3. L6 worktree: 격리된 작업 공간 (Claude Code의 EnterWorktreeTool 패턴)

Part 6. 의사결정 체크리스트

새로운 기능을 하네스에 추가할 때마다 이 질문들을 통과시키세요.

  1. 어느 레이어인가? — L1~L6 중 하나로 분류 안 되면 잘못 자르고 있는 것
  2. prompt cache를 깨는가? — 깬다면 의도된 것인지 명시했는가?
  3. 에러가 났을 때 복구 가능한가? — 가능하다면 어느 단계의 사다리에 들어가는가? 무한 루프 가드는?
  4. 이 도구는 read-only인가, write인가, destructive인가? — 자기-선언으로 표현했는가?
  5. 권한이 필요하다면 4단 funnel 중 어디서 결정되는가? — allowRules가 처리할 수 있는 가장 단순한 케이스인가?
  6. 컨텍스트에 새 정보를 넣는다면: 정적 캐시 가능? 동적이라면 토큰 비용은? graduated compaction에서 가장 먼저 잘릴 후보인가?
  7. L4(코어 도구)인가 L6(확장)인가? — 사용자도 추가할 수 있어야 한다면 L6.
  8. 테스트 가능한가? — AsyncGenerator의 yield 시퀀스로 assertion 가능한가? deps 주입할 곳이 있는가?

Part 7. 빌드 하네스에 적용

빌드 하네스 패턴Claude Code 위치일반화된 전략
컴파일 타임 인트린식 stubstubs/bun-bundle.ts빌드 도구 종속을 1점에 격리
소스 사본 후 변환build.mjs Phase 1+2원본 read-only 보존
반복 stub 생성build.mjs Phase 4 (5라운드)누락 의존성을 fail-loop로 채움
MACRO 주입 2가지 방식transform.mjs (--define) vs build.mjs (replaceAll)빌더가 다르면 같은 결과를 다른 방식으로
네이티브 모듈 vendor stubvendor/*-src/, bun-ffi.ts플랫폼 의존을 stub으로 우회

6 레이어 모델을 빌드 하네스에 매핑하면: L1=빌더(esbuild), L2=빌드 스크립트(반복 루프), L3=stub 디렉토리, L4=transform 규칙, L5=라이선스/보안 필터, L6=커스텀 빌드 hooks.


가장 큰 ROI는 L2(layered recovery) + L3(ACI 인터페이스 + graduated compaction) + L4(self-declared concurrency + edit-format fallback) 세 곳입니다. 이 셋만 잘 만들어도 다른 OSS 코딩 에이전트와 동급의 안정성/정확도가 나옵니다.

profile
행복합시다~

0개의 댓글