@mmerterden/multi-agent-pipeline 20.24.0 → 20.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/docs/facts.json +3 -3
- package/docs/features.md +9 -4
- package/install/catalog-history.json +1 -1
- package/install/templates/claude-hooks.json +1 -1
- package/install/templates/codex-hooks.json +18 -0
- package/manifest.json +71 -68
- package/package.json +1 -1
- package/pipeline/agents/task-clarifier.md +1 -1
- package/pipeline/commands/multi-agent/model/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/postmortem/SKILL.md +18 -6
- package/pipeline/commands/multi-agent/review/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/route-on/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +7 -5
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +2 -3
- package/pipeline/contract/fixtures/runs-failed.json +1 -1
- package/pipeline/contract/fixtures/runs-old-schema.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
- package/pipeline/contract/fixtures/runs-running.json +1 -1
- package/pipeline/lib/credential-inventory.sh +5 -5
- package/pipeline/lib/fetch-crashlytics.sh +6 -8
- package/pipeline/lib/model-rung.sh +1 -1
- package/pipeline/lib/plan-todos.sh +21 -4
- package/pipeline/lib/post-pr-review.sh +4 -6
- package/pipeline/lib/route-state.sh +1 -1
- package/pipeline/lib/shadow-git.sh +4 -5
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -5
- package/pipeline/multi-agent-refs/features/cost-analysis.md +4 -0
- package/pipeline/multi-agent-refs/features/model-fallback.md +17 -12
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +5 -5
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/tracker-contract.md +6 -6
- package/pipeline/schemas/plan-todos.schema.json +4 -4
- package/pipeline/schemas/prefs.schema.json +4 -4
- package/pipeline/scripts/_code-graph.mjs +2 -2
- package/pipeline/scripts/_transcript-hosts.mjs +406 -0
- package/pipeline/scripts/build-stack-plugins.mjs +3 -3
- package/pipeline/scripts/capture-flush.sh +7 -7
- package/pipeline/scripts/code-graph-rules/android.json +1 -1
- package/pipeline/scripts/cost-analyze.mjs +26 -19
- package/pipeline/scripts/cost-table.json +35 -19
- package/pipeline/scripts/edit-syntax-gate.mjs +151 -29
- package/pipeline/scripts/edit-syntax-gate.sh +9 -6
- package/pipeline/scripts/gc-abandoned.sh +2 -2
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
- package/pipeline/scripts/git-path.mjs +1 -2
- package/pipeline/scripts/host-hooks.mjs +265 -0
- package/pipeline/scripts/jira-attach.sh +1 -1
- package/pipeline/scripts/keychain.py +3 -3
- package/pipeline/scripts/output-quality-check.sh +1 -1
- package/pipeline/scripts/phase-tracker.sh +5 -6
- package/pipeline/scripts/phase0-exit-gate.mjs +3 -3
- package/pipeline/scripts/pre-push-check.sh +3 -6
- package/pipeline/scripts/transcript-index.mjs +423 -242
- package/pipeline/scripts/uninstall.mjs +90 -4
- package/pipeline/scripts/update-issue-progress.sh +4 -6
- package/pipeline/scripts/usage-report.mjs +2 -2
- package/pipeline/skills/.skill-manifest.json +8 -8
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +6 -6
- package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-postmortem/SKILL.md +10 -7
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +5 -5
- package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +7 -5
- package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +2 -3
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
|
@@ -429,14 +429,16 @@ To set up multi-agent on a new machine:
|
|
|
429
429
|
|
|
430
430
|
All tokens are optional in the sense that every service can be answered with Skip - but the ASKING is not optional: the Step 3 sequential loop still walks every missing service one by one (token → author → host). Phase 0 re-asks at runtime only for tokens the user skipped here.
|
|
431
431
|
|
|
432
|
-
### Step 8 - Enforcement hooks (optional
|
|
432
|
+
### Step 8 - Enforcement hooks (optional)
|
|
433
433
|
|
|
434
|
-
|
|
434
|
+
On Claude Code, offer to merge `$HOME/.claude/templates/claude-hooks.json`: three blocking `PreToolUse` gates (secret scan, agent-guard, read-size) plus four non-blocking hooks (`PreCompact`, `SessionEnd`, `SessionStart`, `PostToolUse`). What each does: `$HOME/.claude/multi-agent-refs/picker-contract.md`.
|
|
435
435
|
|
|
436
436
|
- Ask (picker): "Install the pipeline's hooks into `~/.claude/settings.json`?" Default Yes.
|
|
437
|
-
- On Yes, deep-merge EVERY event in the template's `hooks` object
|
|
438
|
-
- Say
|
|
439
|
-
- Say
|
|
437
|
+
- On Yes, deep-merge EVERY event in the template's `hooks` object (merging `PreToolUse` alone drops the capture hooks); keep existing hooks, never duplicate a matcher calling the same script.
|
|
438
|
+
- Say only the three gates are hookable (no run-specific arguments); the rest are phase-enforced.
|
|
439
|
+
- Say the read-size gate stays inert until `prefs.global.bulkRead.mode` is set; recommend `observe` first (why, and the Phase 3 exemption: `picker-contract.md`).
|
|
440
|
+
|
|
441
|
+
Codex: offer only the syntax gate, then run `node "$HOME/.codex/scripts/host-hooks.mjs" install --host codex`. Copilot CLI: offer no hook (it discards `postToolUse` output as of 1.0.45).
|
|
440
442
|
|
|
441
443
|
### Step 9 - Default stack plugin enablement
|
|
442
444
|
|
|
@@ -11,10 +11,9 @@ allowed-tools: Agent, Bash, Read, Write, Edit, Glob, Grep, TaskCreate, TaskUpdat
|
|
|
11
11
|
|
|
12
12
|
# /multi-agent:testflight-validation - iOS alias for store-ready
|
|
13
13
|
|
|
14
|
-
This command is an alias. The implementation
|
|
14
|
+
This command is an alias. The implementation lives in
|
|
15
15
|
`/multi-agent:store-ready`, which runs the same three gates on iOS and adds the
|
|
16
|
-
Android side, so there is one flow to maintain instead of two that
|
|
17
|
-
started to drift.
|
|
16
|
+
Android side, so there is one flow to maintain instead of two that drift.
|
|
18
17
|
|
|
19
18
|
The name is kept because it is the one people reach for when the target is
|
|
20
19
|
TestFlight, and removing a command is a breaking change to the slash-command
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
#
|
|
4
4
|
# WHY THIS EXISTS
|
|
5
5
|
#
|
|
6
|
-
# A run
|
|
7
|
-
# service-account JSON
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
6
|
+
# A run that asks the user to paste a Crashlytics stack trace by hand while a valid
|
|
7
|
+
# Firebase service-account JSON sits in the Keychain, mapped as `firebase`, has failed
|
|
8
|
+
# even though nothing is broken. From the user's side that is worse than a failure: they
|
|
9
|
+
# supplied the key precisely so this would not happen. So the pipeline asks itself "do I
|
|
10
|
+
# already hold a credential that answers this?" before it asks the user.
|
|
11
11
|
#
|
|
12
12
|
# One command, so there is no excuse for asking blind. Run it before any question that
|
|
13
13
|
# requests data an external system holds, and let the answer shape the question:
|
|
@@ -214,15 +214,13 @@ if [ -z "$SA_JSON_RAW" ]; then
|
|
|
214
214
|
fi
|
|
215
215
|
|
|
216
216
|
# The credential store returns the JSON as stored - encoding is its internal
|
|
217
|
-
# business, not this script's
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
# nor JSON, so both branches failed and the failure surfaced three layers later
|
|
221
|
-
# as a token-exchange error.
|
|
217
|
+
# business, not this script's, so nothing here guesses at a decoding. A guess
|
|
218
|
+
# that misfires (bare hex read as base64, say) fails three layers later as a
|
|
219
|
+
# token-exchange error instead of here.
|
|
222
220
|
#
|
|
223
|
-
# With no decode step there is nothing to stage on disk, so
|
|
224
|
-
#
|
|
225
|
-
#
|
|
221
|
+
# With no decode step there is nothing to stage on disk, so there is no temp file
|
|
222
|
+
# and no cleanup trap: the service-account JSON only ever exists in a shell
|
|
223
|
+
# variable, which is one fewer place a GCP private key can be left behind.
|
|
226
224
|
SA_JSON="$SA_JSON_RAW"
|
|
227
225
|
|
|
228
226
|
# Verify project_id from SA JSON matches the URL - protects against pasting a
|
|
@@ -69,7 +69,7 @@ host_note() {
|
|
|
69
69
|
echo "Claude Code: the fable rung is Fable 5. This switch is live here."
|
|
70
70
|
;;
|
|
71
71
|
copilot)
|
|
72
|
-
echo "Copilot CLI: Fable
|
|
72
|
+
echo "Copilot CLI: Fable is not offered; its personas never sat on this rung."
|
|
73
73
|
echo "This setting is recorded but changes nothing on this host."
|
|
74
74
|
;;
|
|
75
75
|
codex)
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# Subcommands:
|
|
14
14
|
# init <task-id> [title] Initialize with empty todos[] (Phase 2 usually pipes the JSON instead)
|
|
15
15
|
# set <task-id> <plan-json> Replace the whole plan from a JSON blob on stdin or arg
|
|
16
|
+
# (exit 2 when a dep names no todo id exactly)
|
|
16
17
|
# start <task-id> <todo-id> Mark in_progress, set startedAt; refuses if deps unsatisfied
|
|
17
18
|
# or the todo awaits re-plan confirmation
|
|
18
19
|
# complete <task-id> <todo-id> [notes] [--no-tests <reason>]
|
|
@@ -84,10 +85,9 @@ resolve_state() {
|
|
|
84
85
|
local task_id="$1"
|
|
85
86
|
local found=""
|
|
86
87
|
# multi-repo-pipeline.sh writes agent-state.json under a per-project
|
|
87
|
-
# directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/)
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# This globbed ONLY the nested layout; flat-layout runs resolved to nothing.
|
|
88
|
+
# directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/), other
|
|
89
|
+
# writers write it flat; no writer uses ~/.claude/projects/*/state/.
|
|
90
|
+
# ma_resolve_run_file checks both layouts.
|
|
91
91
|
found="$(ma_resolve_run_file "$task_id" agent-state.json 2>/dev/null || true)"
|
|
92
92
|
if [ -z "$found" ]; then
|
|
93
93
|
echo "plan-todos: agent-state.json not found for task=$task_id" >&2
|
|
@@ -155,6 +155,23 @@ do_set() {
|
|
|
155
155
|
echo "plan-todos: every todo must be an object with string .id and .task fields" >&2
|
|
156
156
|
exit 2
|
|
157
157
|
fi
|
|
158
|
+
# Every dependency must name a todo exactly. Ids are compared as written, so
|
|
159
|
+
# `t1` against a todo `T1` would leave the dependent blocked for good; the
|
|
160
|
+
# error names the id that differs only by case.
|
|
161
|
+
local bad_deps
|
|
162
|
+
bad_deps=$(jq -r '
|
|
163
|
+
[.todos[].id] as $ids
|
|
164
|
+
| [.todos[] | .id as $owner | (.deps // [])[]
|
|
165
|
+
| select((type != "string") or (. as $d | $ids | index($d) | not))
|
|
166
|
+
| . as $d
|
|
167
|
+
| ($ids | map(select(($d | type) == "string" and ascii_downcase == ($d | ascii_downcase)))) as $near
|
|
168
|
+
| "\($owner) depends on \($d | tojson), which is no todo id"
|
|
169
|
+
+ (if ($near | length) > 0 then " (did you mean \($near | map(tojson) | join(", "))?)" else "" end)]
|
|
170
|
+
| .[]' <<<"$plan_blob" 2>/dev/null)
|
|
171
|
+
if [ -n "$bad_deps" ]; then
|
|
172
|
+
printf 'plan-todos: %s\n' "$bad_deps" >&2
|
|
173
|
+
exit 2
|
|
174
|
+
fi
|
|
158
175
|
local state_path; state_path=$(resolve_state "$task_id")
|
|
159
176
|
local now; now=$(iso_now)
|
|
160
177
|
local new
|
|
@@ -107,12 +107,10 @@ if [ -z "$TASK_ID" ]; then
|
|
|
107
107
|
fi
|
|
108
108
|
|
|
109
109
|
# Resolve agent-state.json. multi-repo-pipeline.sh writes it under a
|
|
110
|
-
# per-project directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/)
|
|
111
|
-
# -
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
# write flat, so on a real machine the majority of runs resolved to nothing and
|
|
115
|
-
# this script reported "no agent-state.json" for a run whose state existed.
|
|
110
|
+
# per-project directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/),
|
|
111
|
+
# while phase-tracker.sh and most Phase 0 paths write it flat; no writer uses
|
|
112
|
+
# ~/.claude/projects/*/state/. ma_resolve_run_file checks both layouts, so a
|
|
113
|
+
# run whose state exists is always found.
|
|
116
114
|
AGENT_STATE="$(ma_resolve_run_file "$TASK_ID" agent-state.json 2>/dev/null || true)"
|
|
117
115
|
|
|
118
116
|
if [ -z "$AGENT_STATE" ]; then
|
|
@@ -77,7 +77,7 @@ free_savings() {
|
|
|
77
77
|
since=$(( $(date +%s) - 7 * 86400 ))
|
|
78
78
|
jq -rs --argjson since "$since" --slurpfile ct "$COST_TABLE" '
|
|
79
79
|
map(select((.ts | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601? // 0) >= $since))
|
|
80
|
-
| ($ct[0].prices.haiku // {inPerMtok:
|
|
80
|
+
| ($ct[0].prices.haiku // {inPerMtok: 0.5, outPerMtok: 2.5}) as $h
|
|
81
81
|
| {
|
|
82
82
|
n: length,
|
|
83
83
|
ok: (map(select(.ok and (.cached | not))) | length),
|
|
@@ -227,11 +227,10 @@ do_prune() {
|
|
|
227
227
|
printf 'shadow-git: pruned shadow repo for %s\n' "$task_id" >&2
|
|
228
228
|
else
|
|
229
229
|
[ ! -d "$sd/.git" ] && { err "no shadow for $task_id"; exit 1; }
|
|
230
|
-
# Retention never touches a real work-tree:
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
# for a "keep the last N days" policy. Every git call below is
|
|
230
|
+
# Retention never touches a real work-tree: a `reset --hard` with
|
|
231
|
+
# --work-tree=$(pwd) would silently rewrite whatever directory prune is
|
|
232
|
+
# invoked from. It keeps every commit inside the window and drops the
|
|
233
|
+
# older ones, as a "keep the last N days" policy requires. Every git call below is
|
|
235
234
|
# git-dir-only plumbing (log/rev-parse/commit-tree/update-ref/gc - no
|
|
236
235
|
# checkout, no reset, no clean); the one --work-tree `sg` still wants
|
|
237
236
|
# points at $sd itself, which prune already owns.
|
|
@@ -262,8 +262,8 @@ that quietly no-ops is worse than one that says it cannot act.
|
|
|
262
262
|
|
|
263
263
|
| Host | What `model on|off` does | Why |
|
|
264
264
|
|---|---|---|
|
|
265
|
-
| Claude Code | live switch: flips `modelFallback.fableEnabled` and realigns `costBudget.pricingModel` | the only host where the `fable` rung is Fable 5 |
|
|
266
|
-
| Copilot CLI | writes the preference, changes no dispatch | Fable
|
|
265
|
+
| Claude Code | live switch: flips `modelFallback.fableEnabled` and realigns `costBudget.pricingModel` | the only host where the `fable` rung is Fable 5.1 |
|
|
266
|
+
| Copilot CLI | writes the preference, changes no dispatch | Fable is not offered there; its personas never sat on this rung |
|
|
267
267
|
| Codex CLI | writes the preference, changes no dispatch, **deliberately** | the `fable` rung on Codex means `gpt-5.6 @ xhigh` - a different model on a different account. A knob named after an Anthropic model must not silently retune a Codex run |
|
|
268
268
|
|
|
269
269
|
`model-rung.sh` prints which of the three it is on every invocation. The rule it
|
|
@@ -440,9 +440,8 @@ what keeps a secret off argv and what writes the audit entry.
|
|
|
440
440
|
something that is not a timestamp.
|
|
441
441
|
- `sed -E`, never `-r`; `sed -i ''` when editing in place.
|
|
442
442
|
|
|
443
|
-
Clipboard callers may use `pbpaste` / `pbcopy` directly;
|
|
444
|
-
|
|
445
|
-
installs on.
|
|
443
|
+
Clipboard callers may use `pbpaste` / `pbcopy` directly; a `command -v` guard
|
|
444
|
+
for other clipboard tools would serve hosts this package does not install on.
|
|
446
445
|
|
|
447
446
|
---
|
|
448
447
|
|
|
@@ -46,6 +46,10 @@ discovered later:
|
|
|
46
46
|
- A host with no such transcripts (Copilot, Codex) reports `UNMEASURED`, never
|
|
47
47
|
zero. Zero would read as "you spent nothing" when it means "I could not see".
|
|
48
48
|
|
|
49
|
+
A record is priced by its exact wire ID: a rung's `modelId`, a Copilot CLI
|
|
50
|
+
`copilotModelId` at its own model's rate from the table's `models` map, or a
|
|
51
|
+
previous-generation ID in the table's `legacy` map at that ID's own rate. A
|
|
52
|
+
dated snapshot (`<id>-<date>`) prices as the longest known ID it extends.
|
|
49
53
|
Records whose model is not in `cost-table.json` are COUNTED as unpriced and left
|
|
50
54
|
out of the total. Folding them in at zero would make an unknown model look free,
|
|
51
55
|
which is the direction that hurts.
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
- [Codex CLI](#codex-cli)
|
|
12
12
|
<!-- /toc -->
|
|
13
13
|
|
|
14
|
-
> Contract last revised in **
|
|
14
|
+
> Contract last revised in **v20.25.0** (current Claude generation; Copilot CLI model ids per rung). The version tag here tracks the last substantive change to this contract, not the pipeline release.
|
|
15
15
|
|
|
16
16
|
Personas route to the top available intelligence tier they declare in
|
|
17
17
|
`preferredModel`. That tier can be quota-limited or temporarily unavailable.
|
|
18
18
|
This contract defines when and how the orchestrator falls back, deterministically,
|
|
19
19
|
without ever editing persona files at runtime.
|
|
20
20
|
|
|
21
|
-
> **Fable
|
|
22
|
-
>
|
|
21
|
+
> **Fable is the top tier.** The `fable` rung resolves to `claude-fable-5-1`.
|
|
22
|
+
> Architect (`ios/android/backend-architect`), Reviewer-1 (`code-reviewer`),
|
|
23
23
|
> and triage personas declare `preferredModel: fable` - the deepest-reasoning
|
|
24
|
-
> roles. **If Fable
|
|
24
|
+
> roles. **If Fable is unavailable, the first fallback is opus (`claude-opus-5-5`).**
|
|
25
25
|
> Security and other `preferredModel: opus` personas keep opus as their top tier.
|
|
26
26
|
> The `premiumTierUntil` date gate stays in the contract as a generic mechanism
|
|
27
27
|
> for any plan-window-limited premium tier.
|
|
@@ -35,7 +35,7 @@ fable -> opus -> sonnet -> haiku
|
|
|
35
35
|
The orchestrator walks **one step down per trigger** from whichever tier a
|
|
36
36
|
persona prefers: a `fable` persona degrades `fable -> opus` first, then
|
|
37
37
|
`opus -> sonnet`, then `sonnet -> haiku`; an `opus` persona starts at
|
|
38
|
-
`opus -> sonnet`. So a Fable
|
|
38
|
+
`opus -> sonnet`. So a Fable outage transparently promotes Opus into the
|
|
39
39
|
architect/reviewer roles with no file edits.
|
|
40
40
|
|
|
41
41
|
### Rung names are the contract; model IDs are not
|
|
@@ -46,11 +46,16 @@ model ID each rung resolves to lives in exactly two places: `scripts/cost-table.
|
|
|
46
46
|
`phases/phase-3-review.md`. A generation move edits those; it never renames a rung and
|
|
47
47
|
never touches a persona file.
|
|
48
48
|
|
|
49
|
-
Current resolution: `fable` -> `claude-fable-5`, `opus` -> `claude-opus-5`,
|
|
50
|
-
`sonnet` -> `claude-sonnet-5`, `haiku` -> `claude-haiku-
|
|
49
|
+
Current resolution: `fable` -> `claude-fable-5-1`, `opus` -> `claude-opus-5-5`,
|
|
50
|
+
`sonnet` -> `claude-sonnet-5-5`, `haiku` -> `claude-haiku-5-5`. These are the Claude Code
|
|
51
|
+
wire IDs. Copilot CLI dispatches only the models `copilot help config` lists, with dotted
|
|
52
|
+
names, so its column in the reviewer table names those (`claude-opus-4.7`,
|
|
53
|
+
`claude-sonnet-4.6`), recorded per rung as `copilotModelId` in the cost table (haiku:
|
|
54
|
+
`claude-haiku-4.5`). The fable rung has none and is not offered on Copilot CLI; a persona
|
|
55
|
+
that prefers it dispatches the opus rung there.
|
|
51
56
|
|
|
52
|
-
**Rate limits do not follow the rung.**
|
|
53
|
-
|
|
57
|
+
**Rate limits do not follow the rung.** A new generation can draw on a rate-limit pool
|
|
58
|
+
separate from the previous one, so a generation move does not inherit the previous
|
|
54
59
|
generation's headroom. Budget-ceiling downgrades below are unaffected - they read the
|
|
55
60
|
cost ledger, not the provider's quota - but a 429 on the opus rung after a generation
|
|
56
61
|
move is a limits question, not a fallback-contract question.
|
|
@@ -119,8 +124,8 @@ Scope is Claude Code only, and the reason differs per host:
|
|
|
119
124
|
|
|
120
125
|
| Host | Effect of `fableEnabled: false` | Why |
|
|
121
126
|
|---|---|---|
|
|
122
|
-
| Claude Code | Every `preferredModel: fable` persona (`ios/android/backend-architect`, `code-reviewer`, triage) starts on `opus` | This is the only host where the rung means Fable 5 |
|
|
123
|
-
| Copilot CLI | none | Fable
|
|
127
|
+
| Claude Code | Every `preferredModel: fable` persona (`ios/android/backend-architect`, `code-reviewer`, triage) starts on `opus` | This is the only host where the rung means Fable 5.1 |
|
|
128
|
+
| Copilot CLI | none | Fable is not offered there; its personas never sat on this rung |
|
|
124
129
|
| Codex CLI | none, deliberately | The `fable` rung there means `gpt-5.6 @ xhigh`, a different model on a different account. Switching it off from a knob named after an Anthropic model would surprise a Codex user, so it does not |
|
|
125
130
|
|
|
126
131
|
**Phase 4 loses a reviewer, and says so.** Reviewer 1 lands on `opus`, which
|
|
@@ -250,7 +255,7 @@ better internals.
|
|
|
250
255
|
- No edits to `pipeline/agents/*.md` at runtime; frontmatter is install-time
|
|
251
256
|
configuration only.
|
|
252
257
|
- Copilot CLI reviewer set is out of scope: Copilot CLI pins its own three
|
|
253
|
-
reviewer models (GPT-5.4 + Opus + Sonnet - Fable
|
|
258
|
+
reviewer models (GPT-5.4 + Opus + Sonnet - Fable is not offered there) and
|
|
254
259
|
does not use this persona ladder. Only Claude Code dispatches Reviewer-1 on
|
|
255
260
|
Fable.
|
|
256
261
|
|
|
@@ -652,7 +652,7 @@ Log: `Phase 0 Step 7.7: testDepth = {unit|unit+ui|unit+mcp} (source {user|autopi
|
|
|
652
652
|
|
|
653
653
|
5. Phase 1 Analysis reads `state.clarification.userAnswers` (when present) as additional context - fold answers into the Explore prompt so downstream phases inherit the resolution.
|
|
654
654
|
|
|
655
|
-
**Reference:** `$HOME/.claude/agents/task-clarifier.md` - scoring rubric, question-quality rules and the ~$0.
|
|
655
|
+
**Reference:** `$HOME/.claude/agents/task-clarifier.md` - scoring rubric, question-quality rules and the ~$0.0004-per-call cost note.
|
|
656
656
|
|
|
657
657
|
#### Telemetry
|
|
658
658
|
|
|
@@ -237,10 +237,10 @@ Launch Agent instances **in parallel** using the shared `code-reviewer` subagent
|
|
|
237
237
|
|
|
238
238
|
| Reviewer | subagent_type | Claude Code | Copilot CLI | Codex CLI | Focus | Skills Referenced |
|
|
239
239
|
| ---------- | --------------- | --- | --- | --- | --- | --- |
|
|
240
|
-
| Reviewer 1 | `code-reviewer` | `claude-fable-5` | `claude-opus-
|
|
241
|
-
| Reviewer 2 | `code-reviewer` | `claude-opus-5` | `gpt-5.4` | `gpt-5.4` @ `high` | Edge cases, different perspective | cross-model diversity |
|
|
242
|
-
| Reviewer 3 | `code-reviewer` | `claude-sonnet-5` | `claude-sonnet-
|
|
243
|
-
| Triage | triage persona | `claude-fable-5` | `claude-opus-
|
|
240
|
+
| Reviewer 1 | `code-reviewer` | `claude-fable-5-1` | `claude-opus-4.7` | `gpt-5.6` @ `xhigh` | Deep security + architecture | `api-security-best-practices`, `architecture` |
|
|
241
|
+
| Reviewer 2 | `code-reviewer` | `claude-opus-5-5` | `gpt-5.4` | `gpt-5.4` @ `high` | Edge cases, different perspective | cross-model diversity |
|
|
242
|
+
| Reviewer 3 | `code-reviewer` | `claude-sonnet-5-5` | `claude-sonnet-4.6` | `gpt-5.6` @ `medium` | Quality + correctness + naming | `ai-backend-toolkit:clean-code`, stack-specific skill |
|
|
243
|
+
| Triage | triage persona | `claude-fable-5-1` | `claude-opus-4.7` | `gpt-5.6` @ `max` | Filter false positives + out-of-scope | - |
|
|
244
244
|
|
|
245
245
|
Reviewer count per host: **Claude Code 3, Copilot CLI 3, Codex CLI 3** - **2** on Claude Code with the fable rung off, per `$HOME/.claude/multi-agent-refs/features/model-fallback.md`.
|
|
246
246
|
|
|
@@ -265,7 +265,7 @@ borderline finding. Where the diversity budget goes instead:
|
|
|
265
265
|
|
|
266
266
|
Each reviewer inherits the `code-reviewer` agent's focus areas (Security, Architecture, Quality, Performance) and output contract. The orchestrator overrides only the model and the stack-specific skill per-reviewer - no prompt duplication.
|
|
267
267
|
|
|
268
|
-
**Model override wiring:** `code-reviewer.md` declares `preferredModel: fable`, so Reviewer 1 uses the persona default (Fable 5). Reviewer 2 (`claude-opus-5` on Claude Code, `gpt-5.4` elsewhere) and Reviewer 3 (
|
|
268
|
+
**Model override wiring:** `code-reviewer.md` declares `preferredModel: fable`, so Reviewer 1 uses the persona default (Fable 5.1). Reviewer 2 (`claude-opus-5-5` on Claude Code, `gpt-5.4` elsewhere) and Reviewer 3 (sonnet rung, per host above) set `PHASE_MODEL_OVERRIDE=<model>` before dispatch - the orchestrator exports `CLAUDE_CODE_SUBAGENT_MODEL` on Claude Code, or passes `--model` on Copilot CLI. Full precedence rule: `skills/shared/core/multi-agent/SKILL.md#agent-dispatch--per-persona-model-routing`. Fable dispatches are subject to the fallback contract (`$HOME/.claude/multi-agent-refs/features/model-fallback.md`): dispatch-error retry walks `fable -> opus -> sonnet` and budget-ceiling downgrade.
|
|
269
269
|
|
|
270
270
|
**Stack-specific skills loaded per reviewer** (from Phase 1 `detectedStack`). All three columns are used on every host; Reviewer 2 reads them as Opus on Claude Code and as GPT-5.4 elsewhere.
|
|
271
271
|
|
|
@@ -190,6 +190,6 @@ park it writes `agent-state.json` beside its tracker with `taskId`,
|
|
|
190
190
|
|
|
191
191
|
## Deterministic gates note
|
|
192
192
|
|
|
193
|
-
Claude Code's `PreToolUse` exit-2 hooks are the HARD blocking gates. Three ship, none needing run-specific arguments so they are naturally hookable: (1) `pre-commit-check.sh` scans the staged diff on every `git commit` and blocks on a detected secret; (2) `agent-guard.sh` runs on `git commit` + `git push` and blocks AI/assistant attribution in a commit message and force-push to a protected branch (main/master/develop); (3) `check-read-size.sh` runs on `Read` and on the shell commands that read a file whole, and routes an oversized read to a cheap worker (`bulk-read.sh`) instead of the caller's own rung. The first two inspect what a run WRITES; the third inspects what it pays to READ, and it is inert until `bulkRead.mode` is set to `observe` or `enforce`, so merging the block changes nothing until the user opts in. Its `observe` mode blocks nothing and only logs, which is how the baseline is measured before anything is routed. All three are self-contained and never execute the inspected command. On an internal error they let the call through in an attended session; `agent-guard.sh` blocks instead under `MULTI_AGENT_UNATTENDED=1`, where nobody is watching to notice a gap. Three capture hooks ship in the same block and block nothing: `SessionEnd` runs `capture-flush.sh --if-stale` (writing a killed run's findings into the per-repo stores, so they do not depend on the run reaching Phase 5) plus `note-session.sh` (the mechanical shape of a non-pipeline session: tools used, commands that failed, calls the user refused - never an argument, never any output), `PreCompact` runs `capture-flush.sh` without `--if-stale` (a compaction summarizes a long phase mid-flight, so it is the moment unflushed findings are at risk; the staleness test exists only so a session exit does not re-flush a finished run, and both store writes are idempotent), and `SessionStart` runs `capture-resume.sh`, at most two lines about an unfinished run and a stale observation queue. None of them calls a model; all exit 0 on every path. One `PostToolUse` hook ships in the same block and blocks nothing either: `edit-syntax-gate.sh` on `Edit|Write|MultiEdit` parses the file the edit just wrote (`swiftc -parse`, `node --check`, `py_compile` into a temp dir, `JSON.parse`, `bash -n`; Kotlin is skipped, no parser answers inside its 2 s deadline) and hands a syntax error to the model as `additionalContext`; it is silent when a parser is missing or slow, and inert until `editSyntaxGate.enabled` is true.
|
|
193
|
+
Claude Code's `PreToolUse` exit-2 hooks are the HARD blocking gates. Three ship, none needing run-specific arguments so they are naturally hookable: (1) `pre-commit-check.sh` scans the staged diff on every `git commit` and blocks on a detected secret; (2) `agent-guard.sh` runs on `git commit` + `git push` and blocks AI/assistant attribution in a commit message and force-push to a protected branch (main/master/develop); (3) `check-read-size.sh` runs on `Read` and on the shell commands that read a file whole, and routes an oversized read to a cheap worker (`bulk-read.sh`) instead of the caller's own rung. The first two inspect what a run WRITES; the third inspects what it pays to READ, and it is inert until `bulkRead.mode` is set to `observe` or `enforce`, so merging the block changes nothing until the user opts in. Its `observe` mode blocks nothing and only logs, which is how the baseline is measured before anything is routed. All three are self-contained and never execute the inspected command. On an internal error they let the call through in an attended session; `agent-guard.sh` blocks instead under `MULTI_AGENT_UNATTENDED=1`, where nobody is watching to notice a gap. Three capture hooks ship in the same block and block nothing: `SessionEnd` runs `capture-flush.sh --if-stale` (writing a killed run's findings into the per-repo stores, so they do not depend on the run reaching Phase 5) plus `note-session.sh` (the mechanical shape of a non-pipeline session: tools used, commands that failed, calls the user refused - never an argument, never any output), `PreCompact` runs `capture-flush.sh` without `--if-stale` (a compaction summarizes a long phase mid-flight, so it is the moment unflushed findings are at risk; the staleness test exists only so a session exit does not re-flush a finished run, and both store writes are idempotent), and `SessionStart` runs `capture-resume.sh`, at most two lines about an unfinished run and a stale observation queue. None of them calls a model; all exit 0 on every path. One `PostToolUse` hook ships in the same block and blocks nothing either: `edit-syntax-gate.sh` on `Edit|Write|MultiEdit` parses the file the edit just wrote (`swiftc -parse`, `node --check`, `py_compile` into a temp dir, `JSON.parse`, `bash -n`; Kotlin is skipped, no parser answers inside its 2 s deadline) and hands a syntax error to the model as `additionalContext`; it is silent when a parser is missing or slow, and inert until `editSyntaxGate.enabled` is true. The syntax gate also runs on Codex, on `PostToolUse` for `apply_patch` (every file the patch adds, updates or moves to). `multi-agent:setup` Step 8 asks once and registers it there with `host-hooks.mjs install --host codex` from `install/templates/codex-hooks.json`: idempotent, leaving every other hook alone, `status` reports it and uninstall removes it. Codex runs it only after the user trusts it in `/hooks`, which the helper says. Copilot CLI gets no registration: as of 1.0.45 it discards a `postToolUse` hook's output and applies no matcher, so the gate would cost a spawn per tool call and say nothing. Every other registration here is Claude Code's. The recommended hook block ships at `install/templates/claude-hooks.json`; `multi-agent:setup` offers to merge it into `~/.claude/settings.json`. The other deterministic gates (evidence, consensus, intent, learnings) are invoked by the pipeline phases with per-run arguments (a build-log path, the triage JSON, the free-text input), so they are phase-enforced by contract, not OS-hookable.
|
|
194
194
|
|
|
195
195
|
Copilot CLI has no `PreToolUse` equivalent, so the secret scan there is workflow-enforced (run as a phase step, not OS-blocked) plus a CI smoke-gate step.
|
|
@@ -146,11 +146,11 @@ acted on, not read.
|
|
|
146
146
|
#### Accounting is a gate, not a request
|
|
147
147
|
|
|
148
148
|
`update <N> completed` exits **3** for phases 1-4 when nothing was recorded via
|
|
149
|
-
`tokens`.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
a runtime omission; the tool that would have received the call can.
|
|
149
|
+
`tokens`. A doc that asks for the call, and a lint
|
|
150
|
+
(`smoke-tracker-tokens-invocation.sh`) that checks the doc *mentions* it, cannot
|
|
151
|
+
see a run that skips it: every phase would price at `-` and every completion line
|
|
152
|
+
would print without numbers while every gate passes. A lint over prose cannot
|
|
153
|
+
observe a runtime omission; the tool that would have received the call can.
|
|
154
154
|
|
|
155
155
|
The refusal is recoverable and loses no work:
|
|
156
156
|
|
|
@@ -160,7 +160,7 @@ phase-tracker.sh tokens <N> <in> <out> [cached]
|
|
|
160
160
|
phase-tracker.sh update <N> completed
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
`model` resolves a full model id to its cost-table family (`claude-opus-5` →
|
|
163
|
+
`model` resolves a full model id to its cost-table family (`claude-opus-5-5` →
|
|
164
164
|
`opus`) and says so out loud when it cannot, because an unpriced name is the
|
|
165
165
|
other way a phase silently costs nothing. A phase that truly ran no LLM call
|
|
166
166
|
completes with `--no-llm`, which is recorded and keeps it out of the run
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"properties": {
|
|
28
28
|
"id": {
|
|
29
29
|
"type": "string",
|
|
30
|
-
"pattern": "^
|
|
31
|
-
"description": "Stable identifier
|
|
30
|
+
"pattern": "^[tT][0-9]+$",
|
|
31
|
+
"description": "Stable identifier: t1, t2 when written directly, T1, T2 when converted from a planning-output document. Used in dependency edges and progress lines."
|
|
32
32
|
},
|
|
33
33
|
"task": {
|
|
34
34
|
"type": "string",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"type": "array",
|
|
44
44
|
"default": [],
|
|
45
45
|
"description": "Array of todo IDs that must reach `completed` (or `skipped`) before this one can move to `in_progress`. Empty for parallelizable steps.",
|
|
46
|
-
"items": { "type": "string", "pattern": "^
|
|
46
|
+
"items": { "type": "string", "pattern": "^[tT][0-9]+$" }
|
|
47
47
|
},
|
|
48
48
|
"startedAt": { "type": "string", "format": "date-time" },
|
|
49
49
|
"completedAt": { "type": "string", "format": "date-time" },
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
"properties": {
|
|
149
149
|
"because": {
|
|
150
150
|
"type": "string",
|
|
151
|
-
"pattern": "^
|
|
151
|
+
"pattern": "^[tT][0-9]+$",
|
|
152
152
|
"description": "The step that drifted."
|
|
153
153
|
},
|
|
154
154
|
"at": { "type": "string", "format": "date-time" },
|
|
@@ -568,7 +568,7 @@
|
|
|
568
568
|
"fableEnabled": {
|
|
569
569
|
"type": "boolean",
|
|
570
570
|
"default": false,
|
|
571
|
-
"description": "Whether the fable rung is available at all on Claude Code. Ships OFF: the top rung is the most expensive thing a run can reach, and a cost control that is on by default is not a control. false makes every persona that declares preferredModel: fable dispatch on opus from the first call, with no dispatch attempt on fable and no error to recover from - a cost control, not a fallback. Claude Code only: Copilot CLI does not offer Fable
|
|
571
|
+
"description": "Whether the fable rung is available at all on Claude Code. Ships OFF: the top rung is the most expensive thing a run can reach, and a cost control that is on by default is not a control. false makes every persona that declares preferredModel: fable dispatch on opus from the first call, with no dispatch attempt on fable and no error to recover from - a cost control, not a fallback. Claude Code only: Copilot CLI does not offer Fable, and on Codex CLI the fable rung means gpt-5.6 @ xhigh, which this switch deliberately does not touch. Turning it off also collapses the Phase 3 Claude Code reviewer panel from three to two (Reviewer 1 lands on opus, which Reviewer 2 already holds), and consensus.reviewerCount records 2."
|
|
572
572
|
},
|
|
573
573
|
"onDispatchError": {
|
|
574
574
|
"type": "boolean",
|
|
@@ -1211,7 +1211,7 @@
|
|
|
1211
1211
|
"clarifyAmbiguous": {
|
|
1212
1212
|
"type": "object",
|
|
1213
1213
|
"additionalProperties": false,
|
|
1214
|
-
"description": "v8.6+ - Phase 0 Step 9 clarifying-question loop. Before Phase 1 starts, a cheap Haiku classifier scores task ambiguity (0-10) and emits up to N questions if score < threshold. Cost: ~$0.
|
|
1214
|
+
"description": "v8.6+ - Phase 0 Step 9 clarifying-question loop. Before Phase 1 starts, a cheap Haiku classifier scores task ambiguity (0-10) and emits up to N questions if score < threshold. Cost: ~$0.0004 per Haiku call. Off by default - flip on for teams burned by ambiguity-driven rework or when working on cross-team issues where the spec lives in someone else's head.",
|
|
1215
1215
|
"properties": {
|
|
1216
1216
|
"enabled": {
|
|
1217
1217
|
"type": "boolean",
|
|
@@ -1841,7 +1841,7 @@
|
|
|
1841
1841
|
"editSyntaxGate": {
|
|
1842
1842
|
"type": "object",
|
|
1843
1843
|
"additionalProperties": false,
|
|
1844
|
-
"description": "Parse the file an
|
|
1844
|
+
"description": "Parse the file an edit just wrote and tell the model when it no longer parses: swiftc -parse, node --check, python3 py_compile (bytecode to a temp dir), JSON.parse, bash -n, chosen by extension, each under a 2 s deadline. Kotlin and every other extension are skipped. The hook is pipeline/scripts/edit-syntax-gate.sh: PostToolUse on Claude Code (install/templates/claude-hooks.json) and on Codex apply_patch (codex-hooks.json, registered by host-hooks.mjs). Copilot CLI is not registered: as of 1.0.45 it discards postToolUse output. It never blocks: the message arrives as PostToolUse context and the hook exits 0. A parser that is not installed or runs past the deadline means no message.",
|
|
1845
1845
|
"properties": {
|
|
1846
1846
|
"enabled": {
|
|
1847
1847
|
"type": "boolean",
|
|
@@ -2006,7 +2006,7 @@
|
|
|
2006
2006
|
},
|
|
2007
2007
|
"localRepo": {
|
|
2008
2008
|
"type": "string",
|
|
2009
|
-
"description": "Repo that localPath is relative to, e.g. $HOME/multi-agent-plugins. Without it localPath reads as relative to the current repo,
|
|
2009
|
+
"description": "Repo that localPath is relative to, e.g. $HOME/multi-agent-plugins. Without it localPath reads as relative to the current repo, so an entry can silently point at the UPSTREAM tree: nothing else in the config says which checkout holds the derived copies."
|
|
2010
2010
|
},
|
|
2011
2011
|
"upstreamMarketplace": {
|
|
2012
2012
|
"type": "string",
|
|
@@ -212,8 +212,8 @@ const IMPORT_LINE_RE = /^[ \t]*import[ \t].*$/gm;
|
|
|
212
212
|
*
|
|
213
213
|
* Swift's submodule form - `import class DesignKit.PrimaryButton` - otherwise
|
|
214
214
|
* feeds the `class` definition pattern, which registers the MODULE name as a
|
|
215
|
-
* declared type. On a large Swift app that alone
|
|
216
|
-
*
|
|
215
|
+
* declared type. On a large Swift app that alone lifts module names into the
|
|
216
|
+
* top ten god-nodes. Imports are extracted
|
|
217
217
|
* before this runs, so nothing is lost.
|
|
218
218
|
*
|
|
219
219
|
* @param {string} stripped - output of stripCode
|