エージェント設計の概要
このセクションは SPECA をエージェントシステムとして見たときの設計 — プロンプト、フェーズ間のコンテキスト流通、それを動かすハーネス — を扱います。エンドユーザー向けではなく、設計を理解・改造したいエンジニア向けです。
要点を 1 行で言うと、LLM エージェントの集まりが予測可能に動くのは、各エージェントの入力契約が狭く出力が境界で検証されているときに限る、ということです。SPECA はこれを 3 つの仕組みで実現しており、それぞれがこのセクションの 1 ページに対応します。
| ページ | 内容 |
|---|---|
| ハーネス | 非同期 Python オーケストレータ — サーキットブレーカー、コストトラッカー、ウォッチドッグ、resume manager、バッチ / キュー / partial ファイル設計 |
| プロンプトとスキル | スキルフォーク vs インラインプロンプト、フェーズごとの MCP サーバー、ツール許可リスト、モデル割り当て |
| コンテキスト工学 | 自明でない設計判断: subgraph index、コード事前解決、partial による resume、recall-safe なゲート順序 |
1 ページだけ読むなら コンテキスト工学 を推奨します — コードを読んだだけでは見えない設計判断が集約されています。
3 層の関係
┌──────────────────────────────────────────────────────────────────────┐
│ ハーネス — scripts/orchestrator/ │
│ ──────────────────────────────────────────────────────────────── │
│ • PhaseConfig がフェーズごとの IO 契約を宣言 │
│ • BaseOrchestrator がバッチを並列化、resume を管理 │
│ • ClaudeRunner がバッチごとに claude CLI を起動 + サーキットブレーカー│
│ • CostTracker が BudgetExceeded で予算を強制 │
│ • LogWatcher が stream-json ログをリアルタイム追従 │
└──────────────────────────────┬───────────────────────────────────────┘
│ invokes
▼
┌──────────────────────────────────────────────────────────────────────┐
│ プロンプト / スキル — prompts/ + .claude/skills/ │
│ ──────────────────────────────────────────────────────────────── │
│ • 01a / 01b は Claude Code skill (context: fork) として実行 │
│ • 01e / 02c / 03 / 04 はインラインプロンプト (no fork) │
│ • 各フェーズが固有のモデル + ツール許可 + MCP サーバーを持つ │
└──────────────────────────────┬───────────────────────────────────────┘
│ produces
▼
┌──────────────────────────────────────────────────────────────────────┐
│ コンテキスト流通 — outputs/<phase>_PARTIAL_*.json │
│ ──────────────────────────────────────────────────────────────── │
│ • Pydantic スキーマがフェーズ境界ですべて検証 │
│ • 01b_SUBGRAPH_INDEX.json — 02c 用の仕様レベル参照 │
│ • 02c の事前解決 — 03 でトークンを 40〜60% 節約 │
│ • Partial は first-class — 上書きしない │
└──────────────────────────────────────────────────────────────────────┘
設計が強制する 4 つの不変条件
- すべてのフェーズ境界で検証する。 フェーズ間の書き出しはすべて Pydantic モデル。上流が壊れた出力を出しても下流が静かに腐ることはない。
- Partial は first-class。 各バッチは即座に
PARTIAL_*.jsonを書く。Resume はそれをスキャンするだけ。クラッシュしたランの損失は実行中バッチ分だけ。 - コストはフェーズ単位で上限を設ける、コール単位ではなく。
CostTrackerがフェーズ USD を集計し、ランナー層でBudgetExceededを投げる。終了コードは CI に伝搬する。 - 失敗モードを区別する。
MaxTurnsExhausted(決定論的、リトライ無意味) と一時的 API エラー (指数バックオフでリトライ) とCircuitBreakerTripped(フェーズ全停止) は別物として扱う。
これら 4 つの不変条件があるからこそ、論文に載せられるレベルの再現性を確保できています。これらが無ければ RQ1 の "100% recall" はそもそも安定した主張になりません。