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