Workflow で Haiku 群をスポーン — 必ず model: 'haiku'。effort はモデルと独立にルーティングする:
シャードの weight(対象ファイル数)が閾値超なら要約すべき情報量が多く低 effort では欠落しやすいと
見て effort: 'medium' から開始し、それ以外は effort: 'low'(コストメリットは小さいシャードで維持
される)。さらに秘書が集約時に「findings が曖昧・矛盾・不自然に少ない」と判定したシャード
(low_quality_shards)だけ、effort を 1 段階昇格して差分再探索し秘書がマージする
(SWARM.md §3 の「再探索は差分入力で範囲を絞る」の具体化。全シャード再探索は行わない)。
各エージェントは読み取り専用で、構造化 JSON のみを返す:
export const meta = {
name: "swarm-explore",
description: "Haiku fan-out exploration + Sonnet secretary aggregation",
phases: [
{ title: "Explore" },
{ title: "Re-explore" },
{ title: "Aggregate" },
],
};
const FINDING = {
type: "object",
required: ["findings"],
properties: {
findings: {
type: "array",
items: {
type: "object",
required: ["file", "summary", "severity"],
properties: {
file: { type: "string" },
line: { type: "integer" },
summary: { type: "string" },
severity: { enum: ["critical", "high", "medium", "low", "info"] },
depends_on: { type: "array", items: { type: "string" } },
},
},
},
},
};
const SECRETARY_OUT = {
type: "object",
required: ["report", "low_quality_shards"],
properties: {
report: { type: "string" },
low_quality_shards: { type: "array", items: { type: "integer" } },
},
};
const WEIGHT_THRESHOLD = 15;
const effortFor = (weight) => (weight > WEIGHT_THRESHOLD ? "medium" : "low");
const promote = (effort) =>
effort === "low" ? "medium" : effort === "medium" ? "high" : effort;
const exploreShard = (s, i, effort) =>
agent(
`読み取り専用で調査せよ。対象ディレクトリ: ${JSON.stringify(s.dirs)}\n質問: ${args.question}\n` +
`編集禁止。事実のみを findings として返す。推測には severity=info を付ける。`,
{ label: `explore:${i}`, model: "haiku", effort, schema: FINDING },
);
phase("Explore");
const shards = args.shards;
const raw = await parallel(
shards.map((s, i) => () => exploreShard(s, i, effortFor(s.weight))),
);
phase("Aggregate");
const secretary = await agent(
`あなたは秘書エージェント (swarm-secretary/SKILL.md の契約に従う)。以下の生レポート群に対し` +
`「重複排除」「構造化」「依存関係に基づく優先順位づけ」のみを行え。新規調査・解釈の追加は禁止。` +
`ノイズ (severity=info で裏取りのないもの) は除外。\n` +
`各 finding に MAST 分類を付与せよ: 仕様・役割の不備なら design、複数エージェント間の前提のズレなら ` +
`misalignment、テスト・検証の欠落なら verification。分類不能な事実報告は空欄でよい` +
`(この分類が下流の swarm-loop CHECKPOINT・swarm-implement の判定ルーティングに使われる)。\n` +
`加えて、各シャード(_shard 番号)の findings の具体性・密度を評価し、曖昧・矛盾・件数が` +
`不自然に少ない等品質が低いと判断したシャード番号を low_quality_shards に列挙せよ` +
`(新規調査はせず既存 findings の評価のみ。無ければ空配列でよい)。\n` +
`report には以下の Markdown 形式に厳密に従った本文のみを格納すること(Priority Queue の各行に mast 列を含める):\n` +
`# Secretary Report: <ミッション名>\n\n## Stats\n- input findings: N / after dedup: M / dropped as noise: K\n\n` +
`## Priority Queue(依存上流 → 下流、severity 降順)\n\n` +
`| # | file:line | severity | mast | summary | depends_on | sources |\n|---|---|---|---|---|---|---|\n\n` +
`## Root Causes(複数 findings を説明する共通原因)\n- ...\n\n` +
`## Unverified(判断保留 — 人間または Checker の裏取りが必要)\n- ...\n\n` +
`対象生レポート(各要素の _shard がシャード番号):\n` +
JSON.stringify(raw.filter(Boolean).map((r, i) => ({ ...r, _shard: i }))),
{ label: "secretary", model: "sonnet", schema: SECRETARY_OUT },
);
let report = secretary.report;
const retryTargets = secretary.low_quality_shards.filter((i) => shards[i]);
if (retryTargets.length > 0) {
phase("Re-explore");
log(
`品質不足と判定されたシャード ${retryTargets.length} 件を effort 昇格の上で再探索`,
);
const retried = await parallel(
retryTargets.map(
(i) => () =>
exploreShard(shards[i], i, promote(effortFor(shards[i].weight))),
),
);
phase("Aggregate");
report = await agent(
`以下は既存の Secretary Report(Markdown) と、品質不足のため effort を昇格して再探索した` +
`追加の生レポートである。追加レポートの内容を既存レポートへ統合せよ` +
`(重複排除・優先順位づけのルールは変えない)。新規調査は禁止。更新後の Markdown 全文のみを返せ。\n` +
`既存レポート:\n${report}\n\n追加生レポート:\n${JSON.stringify(retried.filter(Boolean))}`,
{ label: "secretary-merge", model: "sonnet" },
);
}
return {
report,
shard_count: shards.length,
dropped: raw.filter((r) => !r).length,
reexplored_shards: retryTargets.length,
};