17 KiB
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 <jsonschema> |
⚠️ 미확인 → 드라이버 검증·재시도로 보강 |
| 설정 포맷 | JS | .toml agent / CLI flag |
agent.json / CLI flag |
검증 메모: 초기 조사 때 "Codex/Antigravity 서브에이전트는 웹·스키마 없음"이라 판단했으나, 그건 interactive subagent 문서만 본 오판이었다.
codex execheadless 모드에는--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. 목표 / 비목표
목표
- 원본 6페이즈·상수·퇴화 경로를 1:1 충실 이식.
python -m deep_research --backend {codex|antigravity} "<질문>"단일 진입점으로 두 플랫폼에서 동일 동작.- 결정론 제어 로직을 순수 함수로 분리 → 단위테스트로 원본 동작 회귀 검증.
- 웹·스키마를 각 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 인터페이스 계약
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 <tmp.json> --cd <neutral_dir> -s read-only "<prompt>"subprocess. JSONL stdout에서 최종 메시지 파싱 → Pydantic 검증. 웹은 codex 네이티브 web_search. - antigravity.py:
agy -p "<prompt>" ...subprocess. 출력 파싱 → Pydantic 검증, 실패 시 "스키마에 맞춰 JSON만" 재프롬프트 N회(기본 2). 웹은 agy 네이티브 도구. - mock.py: 생성자에
{label_prefix: 응답객체}주입. 네트워크·subprocess 없이 즉답. 테스트 전용.
중립 작업 디렉터리: CLI executor는 repo가 아닌 임시/중립
--cd에서 호출 → 코드베이스 컨텍스트(AGENTS.md/CLAUDE.md)를 안 물고 순수 리서치 에이전트로 동작. read-only 샌드박스.
4. 상수 (원본과 동일 — config.py)
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): hostnamewww.제거 + pathname 후행/제거 + lowercase. 파싱 실패 시u.lower().seen: set, relevance 순(high<medium<low) 정렬 후 순회.- 이미 본 키 →
dupes에 적재, 스킵. fetchSlots<=0이고 relevance가 medium/low(rank≥1) →budgetDropped에 적재, 스킵.- 통과 시
seen에 추가,fetchSlots--.
6.3 Fetch + Extract (novel URL별 1 agent, pipeline 2단계 parallel)
- 에이전트가 CLI 네이티브 webfetch로 본문 취득 → 출처품질 평가 + falsifiable claim 2-5개(직접 인용 포함) 추출(
Extract). - fetch 실패/무관/페이월 →
claims:[],sourceQuality:"unreliable"(원본과 동일). - backend가 None 반환(실패/skip) → 드롭(원본
.filter(Boolean)).
6.4 Rank (순수 — core.rank_claims)
importance(central<supporting<tangential) → sourceQuality(primary<…<unreliable) 순 정렬 → 상위 MAX_VERIFY_CLAIMS=25.
6.5 Verify (배리어 — claim당 3 agent 병렬)
- claim당
VOTES_PER_CLAIM=3verifier를 병렬, "refute 우선" 프롬프트(Verdict). - 정족수 산식 (
core.tally, 원본 1:1, abstention 엣지 포함):valid = [v for v in verdicts if v is not None](None=기권)refuted = sum(v.refuted for v in valid)survives = (len(valid) >= 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 — 반드시 보존)
- claim 0개: 검증 0건 요약 + sources/stats 반환.
- 전건 refute: "adversarial verification에서 전부 기각, 결론 불가" 요약 + refuted 목록 반환.
- 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:
codexCLI 설치 + 로그인(구독). 웹검색 활성(--jsonweb_search). - antigravity backend:
agyCLI 설치 + 로그인(구독). 웹 도구 활성. - 별도 검색 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 단계)
schemas.py+config.py+prompts.py(원본 문자열 이식).core.py순수함수 +test_core.py(원본 동작 고정).backends/base.py+mock.py+test_pipeline_mock.py(퇴화 경로 포함) — 여기까지 네트워크 0으로 충실도 검증 완료.pipeline.py(asyncio, dedup 임계구역 직렬화) — MockBackend로 통과.backends/codex.py(codex exec --json --output-schema) → 실 스모크.backends/antigravity.py(agy -p+ 검증·재시도) → 실 스모크.report.py+__main__.py+ README.