Files
llm-wiki/docs/superpowers/specs/2026-06-06-spec-d-research-fanout-workflow-design.md

9.9 KiB
Raw Permalink Blame History

title, source_type, status, confidence, tags, last_reviewed
title source_type status confidence tags last_reviewed
Spec D — research-fanout Workflow (scoped, Claude 전용, opt-in) llm-generated draft medium
harness
claude-code
workflow
design
automation
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 §3AskUserQuestion(사용자 소유 결정), 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)만. §14(전제·ground-truth·결정추출)와 §69(라벨·채움·게이트)는 산문 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 스크립트

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(코드 오케스트레이션)는 기계화 가능한 부분에 한해.