# Deep-Research 하네스 → Codex / Antigravity CLI 이식 설계 - **상태**: design (구현 전) - **작성일**: 2026-06-09 - **원본 하네스**: Claude Code `Workflow` 도구 기반 JS 스크립트 `~/.claude/projects/-home-donghyeon-dev-llm-wiki-private/88afa9ca-1e45-4353-9ff0-6361812d9053/workflows/scripts/deep-research-wf_aecef33f-4cc.js` - **목표**: claude code의 deep-research를 **codex-cli와 antigravity-cli 각각이 그대로 수행**할 수 있도록 단일 외부 Python 드라이버로 이식. 완전 충실(원본 동작·상수·퇴화 경로 1:1). --- ## 0. 한 줄 요약 원본 deep-research는 Claude `Workflow` 도구 위에서 도는 **결정론적 JS 오케스트레이터**다. Codex/Antigravity는 이 도구가 없고 오케스트레이션이 모델 주도이므로, **결정론 제어 로직을 외부 Python 드라이버 1벌**에 이식하고, 각 플랫폼은 `run_agent()` **backend 어댑터**로 흡수한다. 에이전트 실행기는 **각 CLI의 headless 모드**(`codex exec`, `agy -p`)이며, 웹 조사는 **각 CLI의 네이티브 web_search·webfetch 도구**가 수행한다(별도 검색 API·비용 없음). 포팅 = 프롬프트 3벌 복제가 아니라 **backend flag 하나로 분기하는 1벌 도구**. --- ## 1. 배경 / 문제 ### 1.1 원본 하네스의 2층 구조 | 층 | 내용 | 이식 난이도 | |---|---|---| | **층1 — 결정론 제어** (JS 코드, 모델이 못 건드림) | URL 정규화·dedup, fetch 예산 회계, claim 랭킹, 3-vote 정족수 산식, barrier 동기화, stats | 플랫폼 무관 — 코드로 1:1 이식 | | **층2 — 에이전트 프롬프트 + 스키마 + 페이즈** | Scope→Search→Fetch→Verify→Synthesize 6단계 프롬프트, 5개 JSON 스키마, 3-vote adversarial 패턴 | 플랫폼 무관 콘텐츠 — 그대로 이식 | ### 1.2 플랫폼 능력 검증 결과 (공식 소스 기반) | 능력 | Claude Code | Codex CLI (`codex exec`) | Antigravity CLI (`agy`) | |---|---|---|---| | 결정론적 코드 제어 | Workflow 네이티브 | ❌ (모델 주도) → **외부 드라이버 필요** | ❌ (모델 주도) → **외부 드라이버 필요** | | 병렬 실행 | `parallel()` | subprocess 병렬 | Async Subagent / subprocess 병렬 | | headless 단발 실행 | — | `codex exec` | `agy -p "prompt"` (Command Mode) | | **네이티브 웹검색** | WebSearch | ✅ `codex exec --json`에 web_search 항목 | ✅ harness 내 web/research 도구 | | **네이티브 웹페치** | WebFetch | ✅ | ✅ webfetch | | **스키마 강제 출력** | tool-layer 검증+재시도 | ✅ `--output-schema ` | ⚠️ 미확인 → 드라이버 검증·재시도로 보강 | | 설정 포맷 | JS | `.toml` agent / CLI flag | `agent.json` / CLI flag | > **검증 메모**: 초기 조사 때 "Codex/Antigravity 서브에이전트는 웹·스키마 없음"이라 판단했으나, 그건 *interactive subagent* 문서만 본 오판이었다. `codex exec` **headless 모드**에는 `--json`(web_search 이벤트 포함) + `--output-schema`가 있고, `agy -p`도 동일 harness의 웹 도구를 쓴다. 따라서 별도 검색 API(Tavily 등)·bare SDK는 불필요하며, **사용자가 이미 구독으로 쓰는 CLI 네이티브 도구**를 그대로 활용한다. 출처: - Codex non-interactive(`--json`/`--output-schema`/web_search): https://developers.openai.com/codex/noninteractive - Codex CLI 웹검색 설정: https://codex.danielvaughan.com/2026/05/09/codex-cli-web-search-configuration-cached-live-domain-allow-lists-prompt-injection-defence/ - Antigravity CLI features: https://antigravity.google/docs/cli-features - OpenAI Agents SDK 언어(Py/TS): https://openai.github.io/openai-agents-js/ - Antigravity Python SDK: https://github.com/google-antigravity/antigravity-sdk-python ### 1.3 왜 SDK가 아니라 CLI headless인가 - **비용**: CLI는 구독제 → 호출당 추가 과금 없음. bare SDK + 검색 API는 중복 비용. - **충실도**: 원본은 에이전트가 *직접* WebSearch/WebFetch를 호출한다. CLI headless도 에이전트가 네이티브 웹도구를 호출 → 동일 의미. - **스키마**: Codex `--output-schema`가 tool-layer 검증을 대체. Antigravity는 드라이버가 Pydantic 검증·재시도로 보강. --- ## 2. 목표 / 비목표 ### 목표 1. 원본 6페이즈·상수·퇴화 경로를 **1:1 충실** 이식. 2. `python -m deep_research --backend {codex|antigravity} "<질문>"` 단일 진입점으로 두 플랫폼에서 동일 동작. 3. 결정론 제어 로직을 **순수 함수**로 분리 → 단위테스트로 원본 동작 회귀 검증. 4. 웹·스키마를 각 CLI 네이티브 기능으로 충족(추가 비용 0). ### 비목표 - Claude를 backend로 추가하지 않음(원본 그 자체이므로 불필요). - repo의 `.claude/.codex/.agents` 프롬프트 미러 체계에 편입하지 않음(이 드라이버는 그 밖의 새 카테고리 — 1벌 도구). - 원본 상수·페이즈 구조 변경/최적화 없음(충실 우선). - GUI·서버·스케줄러 없음(단발 CLI 실행만). --- ## 3. 아키텍처 ``` scripts/deep-research/ ├── pyproject.toml # deps: pydantic>=2, (stdlib asyncio/subprocess/json) ├── README.md # 사용법, 인증 전제, backend별 주의 ├── deep_research/ │ ├── __init__.py │ ├── __main__.py # CLI 진입점, argparse, .env/키 점검, report 출력 │ ├── config.py # 상수 (원본과 동일): VOTES_PER_CLAIM 등 + 동시성 cap │ ├── schemas.py # 5 Pydantic v2 모델 + JSON Schema export 헬퍼 │ ├── core.py # 결정론 순수함수 (JS 1:1): dedup/budget/rank/tally/assemble │ ├── pipeline.py # asyncio 오케스트레이션 (무배리어 pipeline / verify 배리어) │ ├── prompts.py # 6단계 프롬프트 (원본 문자열 이식) │ ├── backends/ │ │ ├── base.py # AgentBackend ABC: async run_agent(prompt, schema) -> BaseModel │ │ ├── codex.py # codex exec --json --output-schema 호출·파싱 │ │ ├── antigravity.py # agy -p 호출·파싱 + Pydantic 검증·재시도 │ │ └── mock.py # 테스트 더블 (고정 응답 주입) │ └── report.py # Report → markdown + JSON dump └── tests/ ├── test_core.py # dedup/budget/tally/rank 단위테스트 └── test_pipeline_mock.py # MockBackend 통합 스모크 + 퇴화 경로 3종 ``` ### 3.1 모듈 책임 (단일 책임 / 격리) - **core.py** — 네트워크·LLM·플랫폼 의존 0. 입력 dict/list → 출력 dict/list 순수 변환. 단위테스트 100% 가능. *충실도의 심장.* - **backends/** — 유일하게 플랫폼을 아는 곳. interface `run_agent(prompt, schema) -> 검증된 Pydantic 객체`. 한 backend 교체가 core·pipeline에 무영향. - **pipeline.py** — core 함수 + backend 호출을 asyncio로 엮는 곳. 원본 `pipeline()`(무배리어) / `parallel()`(배리어) 의미를 asyncio로 재현. - **web 계층 없음** — 웹은 backend가 호출하는 CLI의 네이티브 도구가 수행. 드라이버는 URL 문자열만 다룬다(정규화·dedup). ### 3.2 backend 인터페이스 계약 ```python class AgentBackend(ABC): @abstractmethod async def run_agent(self, prompt: str, schema: type[BaseModel], *, label: str) -> BaseModel | None: """프롬프트를 1개 CLI headless 에이전트로 실행, schema로 검증된 객체 반환. 실패/사용자-skip → None (원본 .filter(Boolean) 의미).""" ``` - **codex.py**: `codex exec --json --output-schema --cd -s read-only ""` subprocess. JSONL stdout에서 최종 메시지 파싱 → Pydantic 검증. 웹은 codex 네이티브 web_search. - **antigravity.py**: `agy -p "" ...` subprocess. 출력 파싱 → Pydantic 검증, 실패 시 "스키마에 맞춰 JSON만" 재프롬프트 N회(기본 2). 웹은 agy 네이티브 도구. - **mock.py**: 생성자에 `{label_prefix: 응답객체}` 주입. 네트워크·subprocess 없이 즉답. 테스트 전용. > **중립 작업 디렉터리**: CLI executor는 repo가 아닌 임시/중립 `--cd`에서 호출 → 코드베이스 컨텍스트(AGENTS.md/CLAUDE.md)를 안 물고 순수 리서치 에이전트로 동작. read-only 샌드박스. --- ## 4. 상수 (원본과 동일 — `config.py`) ```python VOTES_PER_CLAIM = 3 # claim당 verifier 수 REFUTATIONS_REQUIRED = 2 # 2/3 refute면 kill MAX_FETCH = 15 # fetch 예산 (전체 novel URL 상한) MAX_VERIFY_CLAIMS = 25 # 검증 대상 claim 상한 ANGLES_TARGET = 5 # scope 분해 목표(스키마 minItems3/maxItems6) CONCURRENCY = 10 # asyncio.Semaphore (원본 Workflow cap 대응, 설정 가능) ANTIGRAVITY_RETRIES = 2 # 스키마 검증 실패 시 재프롬프트 횟수 ``` --- ## 5. 스키마 (`schemas.py` — 5 Pydantic v2 모델) 원본 5 SCHEMA를 Pydantic으로 1:1. `model_json_schema()`로 Codex `--output-schema`용 JSON Schema export. - **Scope**: `question:str`, `summary:str`, `angles: list[Angle](min 3, max 6)` / `Angle{label, query, rationale?}` - **Search**: `results: list[SearchResult](max 6)` / `SearchResult{url, title, snippet?, relevance: Enum[high|medium|low]}` - **Extract**: `sourceQuality: Enum[primary|secondary|blog|forum|unreliable]`, `publishDate?`, `claims: list[Claim](max 5)` / `Claim{claim, quote, importance: Enum[central|supporting|tangential]}` - **Verdict**: `refuted:bool`, `evidence:str`, `confidence: Enum[high|medium|low]`, `counterSource?` - **Report**: `summary`, `findings: list[Finding]` / `Finding{claim, confidence, sources:list[str], evidence, vote?}`, `caveats`, `openQuestions?: list[str]` --- ## 6. 데이터 흐름 (6 페이즈 — 원본 충실) ### 6.0 Scope (1 agent) 질문 → 5개 상보적 검색 각도(`Scope`). 빈 질문이면 즉시 에러 반환(원본과 동일). ### 6.1 Search (각도별 1 agent, 무배리어 pipeline 1단계) - 에이전트가 **CLI 네이티브 web_search**로 검색(쿼리 정제 가능) → 원질문 기준 관련도 재랭킹·SEO 스팸 제거 → top 4-6(`Search`). - *드라이버는 검색을 직접 하지 않음.* 에이전트의 인지 단계(관련도 판단) 보존. ### 6.2 Dedup (순수 드라이버 — `core.dedup`) 원본 로직 1:1: - `normURL(u)`: hostname `www.` 제거 + pathname 후행 `/` 제거 + lowercase. 파싱 실패 시 `u.lower()`. - `seen: set`, relevance 순(high= REFUTATIONS_REQUIRED) and (refuted < REFUTATIONS_REQUIRED)` - ⚠️ **all-abstain → refuted=0 → 거짓 생존 금지**: `len(valid) >= 2` 조건이 이를 차단(원본 주석과 동일 의도). 이 엣지는 단위테스트로 고정. ### 6.6 Synthesize (1 agent) 생존 claim → 의미중복 병합·findings 그룹화·confidence 부여·3-5문장 요약·caveats·open questions(`Report`). refuted 목록은 투명성 위해 첨부. ### 6.7 퇴화 경로 3종 (원본 1:1 — 반드시 보존) 1. **claim 0개**: 검증 0건 요약 + sources/stats 반환. 2. **전건 refute**: "adversarial verification에서 전부 기각, 결론 불가" 요약 + refuted 목록 반환. 3. **synthesis 실패/skip**: 보고서 대신 생존 claim raw salvage 반환(전체 run 폐기 금지). ### 6.8 stats (원본 키 보존) `angles, sourcesFetched, claimsExtracted, claimsVerified, confirmed, killed, afterSynthesis, urlDupes, budgetDropped, agentCalls(=1+angles+sources+voted*3+1)`. --- ## 7. 동시성 (`pipeline.py`) - `asyncio.Semaphore(CONCURRENCY)`로 동시 CLI subprocess 수 제한. - **무배리어 pipeline (Search→Dedup→Fetch)**: 각도별 async 체인 — A각도가 Fetch 도는 동안 B각도는 Search 가능. 단, **dedup의 `seen`/`fetchSlots`는 공유 상태**이므로 각 Search 완료 직후 dedup을 적용하는 지점에서만 갱신(원본은 pipeline 2단계 진입 시 순차 도착하며 갱신). → asyncio에서는 dedup 임계구역을 단일 코루틴/락으로 직렬화해 경쟁 제거. - **배리어 (Verify)**: 전체 claim 풀이 모인 뒤 `asyncio.gather`로 일괄 검증(원본 의도적 barrier). - agent 호출 1개 = CLI subprocess 1개. 콜드스타트 ~97콜/run → 구독제라 비용 무관, 속도는 동시성으로 완화. --- ## 8. 에러 처리 | 상황 | 처리 (원본 의미) | |---|---| | backend run_agent 실패/타임아웃 | None 반환 → 상위에서 filter | | fetch 실패/페이월 | `unreliable` + `claims:[]` | | verifier 기권(None) | valid에서 제외, 정족수 미달 시 미생존 | | Codex `--output-schema` 위반 | codex 재시도; 그래도 실패 시 None | | Antigravity 스키마 검증 실패 | 드라이버가 "JSON만" 재프롬프트 `ANTIGRAVITY_RETRIES`회 후 None | | 빈 질문 / scope 실패 | 즉시 에러 dict 반환 | | 키/CLI 미설치 | 시작 시 명확한 에러로 중단 | --- ## 9. 인증 / 설정 전제 - **codex backend**: `codex` CLI 설치 + 로그인(구독). 웹검색 활성(`--json` web_search). - **antigravity backend**: `agy` CLI 설치 + 로그인(구독). 웹 도구 활성. - 별도 검색 API 키 **불필요**. - 시작 시 선택된 backend의 CLI 존재·로그인 점검, 미충족 시 안내 후 종료. --- ## 10. 테스트 전략 ### 10.1 core 순수함수 단위테스트 (`test_core.py`) - `normURL`: www/trailing-slash/대소문자/파싱실패 케이스. - `dedup`: 중복 적재, budget-drop(slot 소진 시 medium/low만), high는 slot 무시 통과 여부(원본 `relRank>=1` 조건 정확 재현). - `tally`: 3-0/2-1/1-2/0-3, **all-abstain(생존 금지)**, 1 valid+2 abstain(미달 미생존), 2 valid 0 refute(생존). - `rank_claims`: importance·quality 2차 정렬 순서. ### 10.2 MockBackend 통합 스모크 (`test_pipeline_mock.py`) - 고정 응답으로 6페이즈 정상 흐름 1건 + **퇴화 경로 3종** 재현. 네트워크·키·subprocess 0. - stats 키·`agentCalls` 계산식 검증. ### 10.3 실 backend 스모크 (수동, 선택) - 짧은 질문 1건을 `--backend codex` / `--backend antigravity`로 각 1회 실행, 보고서·stats 육안 확인. --- ## 11. 리스크 / 미해결 (구현 1차에서 확인) | 리스크 | 영향 | 완화 | |---|---|---| | `agy` output-schema 플래그 유무 미확인 | Antigravity 스키마 보장 약함 | 드라이버 Pydantic 검증·재시도로 보강; 플래그 있으면 채택 | | CLI 콜드스타트 누적 지연 | run 시간 증가 | 동시성 cap 상향, Antigravity Go 런타임, (후속) 세션 재사용/`spawn_agents_on_csv` | | `codex exec` JSONL 최종 메시지 추출 형식 | 파싱 취약 | 이벤트 타입 기반 파서 + 스키마 검증으로 방어 | | 무배리어 pipeline의 dedup 공유 상태 경쟁 | dedup 비결정성 | dedup 임계구역 직렬화(락/단일 코루틴) | | repo 내 `scripts/` 부재(기존 메모) | 신규 디렉터리 | `scripts/deep-research/` 신설 — sync 생성기와 무관(별 카테고리) | --- ## 12. 거버넌스 / repo 정합 - 이 드라이버는 프롬프트 미러(`.claude`/`.codex`/`.agents`) 대상이 **아님** — backend 어댑터로 분기하는 1벌 도구. - 위치: `scripts/deep-research/`. 구현 완료 후 CLAUDE.md에 **"외부 도구: deep-research 드라이버"** 한 줄 등재 검토(별 카테고리, 3-플랫폼 패리티 예외 — invest/project 파이프라인과 유사한 비미러 항목). - 원본 JS 경로를 README에 명시해 충실도 대조 기준 보존. --- ## 13. 구현 순서(개요 — 상세는 plan 단계) 1. `schemas.py` + `config.py` + `prompts.py`(원본 문자열 이식). 2. `core.py` 순수함수 + `test_core.py`(원본 동작 고정). 3. `backends/base.py` + `mock.py` + `test_pipeline_mock.py`(퇴화 경로 포함) — 여기까지 **네트워크 0으로 충실도 검증 완료**. 4. `pipeline.py`(asyncio, dedup 임계구역 직렬화) — MockBackend로 통과. 5. `backends/codex.py`(`codex exec --json --output-schema`) → 실 스모크. 6. `backends/antigravity.py`(`agy -p` + 검증·재시도) → 실 스모크. 7. `report.py` + `__main__.py` + README.