--- title: "Spec D — research-fanout Workflow (scoped, Claude 전용, opt-in)" source_type: llm-generated status: draft confidence: medium tags: [harness, claude-code, workflow, design, automation] last_reviewed: 2026-06-06 --- # Spec D — research-fanout Workflow > 상위 감사: [[2026-06-06-harness-audit-report]] §3 G2. 확정 결정: **scoped research-fanout Workflow**(전체 명령 변환 아님), **승인 완료**. **구현(파일 Write + Workflow 실행)은 opt-in 시.** ## 1. 문제 (감사에서) + 재설계 - **G2 (P2)**: `/branch-spec`·`/project-spec` 오케스트레이션이 산문-자문(번호 매긴 LLM 지시). controller 가 단계를 건너뛰어도 탐지 안 됨. deep-research 의 코드 barrier 와 대비. - **재설계 (브레인스토밍 발견):** 두 명령은 **비-기계적 부분**이 본질적이다 — `project-spec §3` 의 `AskUserQuestion`(사용자 소유 결정), ca-tmpl ground-truth 정독(판단), 노트 채움(user-content 보존), 다이어그램 작성. **전체를 Workflow 로 변환하면 이 상호작용·판단이 깨진다(YAGNI 위반).** - 따라서 **순수 fan-out 서브페이즈만** 추출: `/branch-spec §5`(결정별 bounded 자동조사 — `wiki-decision-researcher` ×N, cap 6, deferred). 이것이 deep-research 의 `pipeline(angles → research → schema)` 와 정확히 동형이다. ## 2. 핵심 원리 / 비목표 - **산문 명령은 유지** (3-플랫폼). Claude 만 *옵션으로* Workflow 를 호출해 §5 의 병렬 조사를 빠르고 schema-강제·cap-보장으로 수행. Codex/Antigravity 는 기존 순차 dispatch — **parity 안 깨짐.** - A/B/C 메커니즘의 **진짜 Workflow 형태**: tool-layer `agent({schema})`(B 를 hope→enforce 로) · funnel `found=processed+dropped`(C) · cap+deferred(C no-silent-truncation). - **autonomous** 조사(deep-research 처럼 user-approval 없이) — 산문 `wiki-decision-researcher`(WebSearch→승인→fetch)와의 trade-off. 둘 병존, 사용자 선택. - **비목표**: 전체 명령 변환 · `project-spec` 변환(AskUserQuestion 본질적, v2 후보) · verify gate 의 Workflow 화(별도). branch-note 직접 편집(Workflow 는 input 만 반환, 산문 controller 가 §7 채움). ## 3. 설계 결정 (확정) ### DD1 — scoped fan-out only Workflow 는 §5(결정별 조사 burst)만. §1~4(전제·ground-truth·결정추출)와 §6~9(라벨·채움·게이트)는 산문 controller. ### DD2 — autonomous + schema-enforced 각 결정을 `agent({schema: ALT_SCHEMA})` 로 병렬 조사. user-approval 없음(autonomous). 결과는 구조화된 비교매트릭스. ### DD3 — Claude 전용, 산문은 불변 `.claude/workflows/research-fanout.*` 신규. `/branch-spec §5` 에 "옵션: Claude 에서 research-fanout Workflow 호출" 한 줄만 추가(산문 흐름·3-플랫폼 미러 불변). ## 4. 아키텍처 — Workflow 스크립트 ```js export const meta = { name: 'research-fanout', description: 'Bounded parallel alternatives-research for N branch decisions (autonomous, schema-enforced)', phases: [ { title: 'Scope', detail: 'validate + cap decisions (max 6), split deferred' }, { title: 'Research', detail: 'parallel autonomous web research per decision, schema-enforced' }, { title: 'Synthesize', detail: 'comparison matrices + funnel stats + branch-note DEM input' }, ], } const MAX_DECISIONS = 6 const ALT_SCHEMA = { type: "object", required: ["decision", "alternatives", "recommendation", "confidence"], properties: { decision: { type: "string" }, alternatives: { type: "array", minItems: 2, maxItems: 5, items: { type: "object", required: ["name", "pros", "cons", "sources"], properties: { name: { type: "string" }, pros: { type: "string" }, cons: { type: "string" }, sources: { type: "array", items: { type: "string" } }, // URL quote: { type: "string" }, // verbatim 인용 sourceType: { enum: ["official-doc", "company-tech-blog", "personal-blog", "unknown"] }, }, }}, recommendation: { type: "string" }, confidence: { enum: ["high", "medium", "low"] }, unsupported: { type: "boolean" }, // 조사 후에도 근거 부족 → UNSUPPORTED_DECISION }, } // ── Scope: cap + deferred (no silent truncation) ── phase("Scope") const decisions = (args && Array.isArray(args.decisions)) ? args.decisions : [] if (!decisions.length) { return { error: "No decisions. Pass args.decisions = [{topic, parentBranch, constraints, n}]." } } const capped = decisions.slice(0, MAX_DECISIONS) const deferred = decisions.slice(MAX_DECISIONS) log(`${decisions.length} decisions → research ${capped.length}, defer ${deferred.length}`) const researchPrompt = (d) => "## Alternatives Researcher (autonomous)\n\n" + "Branch decision: \"" + d.topic + "\"\n" + "Parent branch: " + (d.parentBranch || "(none)") + "\n" + "Constraints: " + (d.constraints || "(none)") + "\n\n" + "## Task\n" + "1. WebSearch official docs + 대기업 기술블로그 for " + (d.n || 3) + " viable alternatives.\n" + "2. WebFetch each; extract Pros/Cons + a verbatim quote + source URL + sourceType.\n" + "3. company-tech-blog 만으로 '공식 best practice' 승격 금지(독립 사례 2+ 또는 official 병행).\n" + "4. 근거가 한쪽으로 명확하면 그대로 recommendation. 가짜 5:5 균형 금지.\n" + "5. 조사 후에도 근거 부족하면 unsupported=true.\n\nStructured output only." // ── Research: 진짜 병렬 + tool-layer schema 강제 ── phase("Research") const results = (await parallel( capped.map(d => () => agent(researchPrompt(d), { label: "research:" + d.topic.slice(0, 30), phase: "Research", schema: ALT_SCHEMA }) .then(r => r ? { ...r, topic: d.topic } : null) ) )).filter(Boolean) // ── Synthesize: funnel + branch-note DEM input ── phase("Synthesize") const failures = capped.length - results.length const stats = { found: decisions.length, processed: results.length, dropped: deferred.length + failures, dropped_reason: [ deferred.length ? `${deferred.length} over cap(${MAX_DECISIONS})` : null, failures ? `${failures} research failed/skipped` : null, ].filter(Boolean).join("; ") || "none", } // 불변식: found = processed + dropped (decisions.length = results + deferred + failures) ✓ log(`done: ${results.length} researched, ${stats.dropped} dropped`) return { matrices: results, deferred: deferred.map(d => d.topic), unsupported: results.filter(r => r.unsupported).map(r => r.topic), stats, // 산문 controller 가 §7 Decision Evidence Map 채움에 쓰는 input (Workflow 는 노트 직접 편집 안 함) branchNoteInput: results.map(r => ({ decision: r.topic, recommendation: r.recommendation, confidence: r.confidence, sources: r.alternatives.flatMap(a => a.sources), unsupported: !!r.unsupported, })), } ``` ### 4.1 산문 `/branch-spec` 변경 (한 줄 — Claude 전용 옵션) §5 에 추가: > **(옵션, Claude 전용)** 결정 수가 많거나 빠른 병렬 조사를 원하면 `research-fanout` Workflow 를 호출한다(`Workflow({name:'research-fanout', args:{decisions:[...]}})`, ultracode/opt-in 필요). 반환된 `matrices`/`branchNoteInput` 으로 §7 Decision Evidence Map 을 채우고, `stats`/`deferred` 를 §9 요약에 반영. **autonomous 조사라 user-approval 이 없으므로**, 승인-gated 가 필요하면 기존 `wiki-decision-researcher` 순차 dispatch 를 쓴다. Codex/Antigravity 는 항상 순차 dispatch. ## 5. 위험 | 위험 | 완화 | |---|---| | autonomous 조사가 user-approval 우회 | 산문 `wiki-decision-researcher`(승인-gated)와 병존 — 사용자가 선택. §5 에 trade-off 명시. | | Workflow Claude 전용 → parity | 산문 명령 불변(3-플랫폼). Workflow 는 *추가* 옵션. project 파이프라인처럼 명시적 Claude 예외. | | cap 초과 silent 절단 | `deferred` 명시 반환 + funnel `dropped_reason`. | | schema 강제로 agent 재시도 비용 | deep-research 와 동일 — tool-layer 검증이 신뢰성↑. cap 6 으로 비용 bound. | | Workflow 실행 opt-in 필요 | spec/plan 은 지금, 실행은 ultracode opt-in. 산문 경로는 항상 가용(non-opt-in fallback). | ## 6. 수용 기준 (검증 가능 — opt-in 실행 시) 1. `Workflow({name:'research-fanout', args:{decisions:[3개]}})` → `matrices` 3개 + `stats.found=3, processed+dropped=3`. 2. 8개 decisions → 6 researched + `deferred` 2개 명시 + `stats.dropped≥2 with dropped_reason`. 3. 빈 decisions → `{error}` (graceful). 4. 각 matrix 가 ALT_SCHEMA 충족(alternatives ≥2, sources 배열, recommendation/confidence). 5. `branchNoteInput` 이 산문 §7 채움에 바로 쓰일 형태(decision/recommendation/sources/unsupported). 6. 산문 `/branch-spec` 의 기존 순차 경로(비-Workflow)가 여전히 동작(Workflow 미사용 fallback). ## 7. 구현 순서 (writing-plans — 실행은 opt-in) 1. (opt-in) `.claude/workflows/research-fanout.*` 에 §4 스크립트 Write. 2. (opt-in) `Workflow({name:'research-fanout', args:{decisions:[테스트 2-3개]}})` 실행 → §6.1·6.4 검증. 3. (opt-in) 8개 decisions 로 cap/deferred 검증(§6.2). 4. `/branch-spec §5` 에 옵션 호출 한 줄 추가(§4.1) — 이건 산문 편집이라 opt-in 불요(언제든). 5. (opt-in) §6 전 항목 스모크. > **경계:** 4번(산문 한 줄)은 비-opt-in 으로 지금 가능. 1~3·5(Workflow Write+실행)는 `Workflow` 툴 opt-in 시. plan 이 이 경계를 task 별로 명시. ## 8. 메모 - 이건 "전체 오케스트레이션 결정론화"(원래 G2 야망)가 아니라 *기계화 가능한 한 조각*만 deep-research 형태로. 나머지 산문 오케스트레이션은 interactive/judgment 라 산문이 옳다 — 그 결론 자체가 D 의 산출. - A(구조 게이트)·B(verdict schema)·C(stats)·D(research-fanout Workflow)로 deep-research 8원칙이 하네스 전반에 이식됨 — 단 P2(코드 오케스트레이션)는 *기계화 가능한 부분에 한해*.