skill
vzt-agent-protocol
Automatic model routing for Claude Code — Fable 5, Opus 4.8, Sonnet 5, Haiku 4.5. Right model, right task, zero manual switching. Part of the VZT Protocol ecosystem.
Set up
Skills are installed by placing their folder where your agent loads skills.
The download comes from github.com/vonzelle-vzt/vzt-agent-protocol itself, not from our server, so what you get is exactly what its author published.
Install instructions come from the README
README
synced Oct 22, 2026VZT Agent Protocol
Automatic model routing for Claude Code — Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5. Right model, right task, zero manual switching.
Part of the VZT Tech Consulting Protocol ecosystem.
The problem
Running every prompt on your best model burns through weekly usage limits in
days. Running everything on a cheap model caps quality. Manually flipping
/model per task is friction nobody sustains.
The solution
The VZT Agent Protocol classifies every prompt and routes the work to the cheapest model tier that can do it well — automatically, on every prompt, with zero API cost for the routing itself:
| Tier | Model | Owns |
|---|---|---|
| 4 | Fable 5.1 | No-prior-art architecture, impossible bugs, root-cause, security analysis |
| 3+ | Opus 5.5 @ max | Routine planning — architecture, specs, roadmaps, migration plans (the opus@max rung) |
| 3 | Opus 5.5 | Large refactors, dense algorithms, performance surgery, load-bearing review |
| 2 | Sonnet 5.5 | Standard implementation — the default (burns its own separate weekly bucket) |
| 1 | Haiku 5.5 | Search, summaries, renames, formatting, commit messages — nearly free |
Why this preserves your limits: Max plans meter a Sonnet-only weekly bucket separately from the all-models bucket that Fable/Opus consume. Routing execution to Sonnet and mechanical work to Haiku means your premium quota is spent only where premium reasoning actually changes the outcome.
Recommended setup — Opus first line: sit on Opus 5.5 (/model opus) so strong
reasoning is always on tap, and let the protocol delegate routine builds down
to Sonnet and mechanical work down to Haiku, reaching up to Fable only on the
≤10% of turns that are genuinely frontier-hard. Opus 5.5 is a step change on deep
reasoning at 40% of Fable's price, so planning now runs on the opus@max rung
rather than the frontier tier. See Chair Profiles for
every chair's behavior.
Quick start
No clone needed — run it straight from GitHub with npx:
# install globally for every project
npx github:vonzelle-vzt/vzt-agent-protocol install --global
# or install into a single project's .claude/
npx github:vonzelle-vzt/vzt-agent-protocol install --target /path/to/your/project
# verify
npx github:vonzelle-vzt/vzt-agent-protocol doctor --global
Or clone first if you prefer:
git clone https://github.com/vonzelle-vzt/vzt-agent-protocol.git
cd vzt-agent-protocol
node cli/vzt-agent.js install --global # or: install --target /path/to/project
node cli/vzt-agent.js doctor --global
Restart Claude Code. Pick the chair that matches how you work — the protocol adapts the routing doctrine to it either way:
- Opus 5.5 chair (
/model opus) — build and plan inline, delegate routine execution down to Sonnet and mechanical work to Haiku, escalate up to Fable only for no-prior-art architecture and impossible bugs. Best when you want strong first-line reasoning on tap and Sonnet as your workhorse below it. - Sonnet 5.5 chair (
/model sonnet) — most work stays inline on the Sonnet-only bucket; escalate up to Opus/Fable only when a task earns it. Best for maximum quota efficiency.
How it works — four real routing layers
Every layer here uses a mechanism Claude Code actually enforces — not prompt-only suggestions:
1. Per-prompt classifier hook (UserPromptSubmit)
A deterministic, <50ms, zero-API-cost classifier scores every prompt against
the routing matrix (25+ signal patterns + length heuristics) and injects a
[VZT-ROUTE] directive: which tier, which agent, whether to handle inline.
Every decision is logged to ~/.claude/vzt-router/decisions.jsonl.
2. Chair-aware session profiles (SessionStart)
The protocol reads which model your session launched with and inverts the doctrine to match:
- Fable chair → tokens are scarce: plan inline, delegate ALL execution down
- Opus chair → clock is scarce: build inline, push mechanical work down
- Sonnet chair → capability is scarce: escalate up only when a task earns it
- Haiku chair → dispatcher mode: delegate almost everything
3. Model-pinned agent fleet (.claude/agents/)
Ten agents with model: + effort: frontmatter — Claude Code runs each on
its pinned model regardless of your session model:
| Agent | Model | Effort | Role |
|---|---|---|---|
vzt-planner | fable | max | No-prior-art plans only — novel/greenfield architecture, one-way-door distributed-systems calls |
vzt-oracle | fable | max | Root-causes impossible bugs; returns a fix packet, not a guess |
vzt-architect | opus | max | The opus@max rung — routine planning, with a step-routing table (each step tagged with its cheapest sufficient tier) |
vzt-heavy-builder | opus | high | Tightly-coupled multi-file surgery, algorithms, migrations |
vzt-reviewer | opus | high | Reviews only the load-bearing seam the plan flags |
vzt-art-director | opus | high | Authors DESIGN.md — visual taste, when none is written down yet |
vzt-builder | sonnet | medium | The workhorse — all routine implementation |
vzt-stylist | sonnet | medium | Applies DESIGN.md — restyles, spacing, palette, dark mode |
vzt-scout | haiku | low | Recon: find/count/summarize, read-only |
vzt-mechanic | haiku | low | Mechanical edits: renames, formatting, bumps |
4. Turn-level skills (skill model: override)
When up- or down-tier work needs the full conversation context (subagents start fresh), these switch the current turn's model in place:
/vzt-design <task>— plan this turn on Opus 5.5 @max(theopus@maxrung — the default planning turn)/vzt-plan <task>— plan this turn on Fable 5.1 (only when the design has no prior art to reason from)/vzt-fix <bug>— root-cause this turn on Fable 5.1/vzt-build <step>— execute this turn on Sonnet 5.5/vzt-quick <task>— mechanical turn on Haiku 5.5/vzt-ui [extract|apply]— the visual lane: author a repo'sDESIGN.mdfrom its real token layer, then apply it. (No model pin — the tier depends on whether the taste cache exists; see below.)/vzt-fable-mode— run this turn under the five frontier working gates (scope, evidence, attack, verify, report) (no model pin — runs on the active model)/vzt-diagnose <symptom>— parallel hypothesis fan-out for a hard bug: N≤4 read-only agents each test one root cause with one real command and return CONFIRMED/REFUTED with the output pasted. Run it before escalating to/vzt-fix— cheap parallel evidence first, frontier reasoning only once it is earned. (No model pin — the probes run on Haiku/Sonnet.)/vzt-ship <the system to build>— spec-first long-horizon execution: writes a SPEC to disk before any code, gates it with a command, then drives it as supervised background workers. See Long-horizon work below.
The session model returns on your next prompt.
The standard pipeline
you: "build feature X" (a non-trivial, multi-part feature)
└─ PLAN → vzt-architect (Opus 5.5, effort max) · or /vzt-design for an in-context turn
· (novel/greenfield design instead? → vzt-planner (Fable 5.1) or /vzt-plan)
└─ plan with step-routing table + load-bearing seam flagged
├─ steps tagged sonnet → vzt-builder (parallel)
├─ steps tagged haiku → vzt-mechanic/scout (parallel)
├─ steps tagged opus → vzt-heavy-builder
├─ steps tagged ui → vzt-stylist (Sonnet, applies DESIGN.md)
│ └─ no DESIGN.md yet? → vzt-art-director (Opus) writes it ONCE
└─ seam review → vzt-reviewer (Opus, only the risky seam)
On an Opus chair, a routine one-shot request skips planning entirely — the
classifier delegates the build straight down to vzt-builder (Sonnet) and any
mechanical part to vzt-mechanic (Haiku), while you stay on Opus as coordinator.
Full walkthroughs per chair: Chair Profiles.
Long-horizon work — /vzt-ship
Scope language ("entire codebase", "from scratch", "greenfield", "end-to-end", "multi-tenant") used to route straight to Fable — the slower, more expensive model. That was a bug. Long-horizon work doesn't fail because the model isn't smart enough; it fails because context compaction eats the plan halfway through the run, and the back half gets built against a plan the chair no longer remembers. A slower model doesn't fix that. A plan on disk does.
Escalate the PROCESS, not the MODEL. Scope language now routes to Opus
under a new task kind, HORIZON, and the gate is two-factor: scope
language alone is still a planning question and stays on Fable ("design the
architecture for the whole system"); scope + a build verb
(build/implement/ship/create/scaffold/rewrite/...) is a shipping question and
becomes HORIZON. Fable narrows to genuinely hard debugging (/vzt-fix) and
stays ≤10% of turns.
A HORIZON classification points at /vzt-ship, which runs four phases:
- SPEC — forbidden from editing source. Writes
.vzt/ship/<slug>/SPEC.mdfromtemplates/spec.md: contract, out-of-scope, the interfaces that cross unit boundaries (these become a serial "barrier" unit), a file manifest, and units whoseFILES_IN_SCOPEsets are pairwise disjoint, each with one machine-checkable oracle chosen before the unit is built. - GATE —
vzt-agent ship-check <SPEC.md>is a command, not an opinion: it exits non-zero on overlapping scopes, a manifest file no unit owns, a unit with no oracle, an unknown agentType, or an undeclared external connection (see below). - RUN — launches
workflows/vzt-ship.jsvia the Workflow tool: barrier → parallel units → independent read-only verification of each oracle (builders never grade themselves) → bounded repair (≤2 rounds) → integration gate. - LAND — verifies artifacts on disk, not reports, then stops before commit/deploy.
It survives compaction because the coherence lives on disk, not in the
conversation: SPEC.md + LEDGER.jsonl on disk, vzt-agent ship-status
reconstructs run state, and the UserPromptSubmit classifier hook re-injects
a [VZT-SHIP] block on every prompt — compaction does not re-fire
SessionStart, so the classifier is the only hook that survives it.
Optional: watch a run in an agent multiplexer (orca/)
workflows/vzt-ship.js is the headless path. When you'd rather watch the units
work in parallel — live panes, diffs, agent state — the same gated SPEC drives a
supervised run in an agent multiplexer. Three backends behind one --mux flag:
orca (desktop ADE, default),
herdr (terminal-native, persistent over SSH/mobile), or
vscode (each unit opens as a native VS Code integrated terminal via the
companion extension in vscode/ — no external binary, just VS Code):
vzt-agent ship-watch .vzt/ship/<slug>/SPEC.md # orca (default)
vzt-agent ship-watch .vzt/ship/<slug>/SPEC.md --mux herdr # herdr
vzt-agent ship-watch .vzt/ship/<slug>/SPEC.md --mux vscode # native VS Code terminals
The vscode backend needs no multiplexer install: it uses git worktree + a
filesystem queue the companion extension drains into terminals, and a Stop hook
for idle detection. Setup + the honest constraints (VS Code can't rename terminal
tabs, so PASS/FAIL shows in a "VZT Ship" output channel + status bar) are in
docs/VSCODE.md.
For daily VS Code use, install the local companion once from this checkout:
npm --prefix vscode install
npm run install:vscode:local
After reloading VS Code, the extension is passively active in every workspace:
the VZT: ... status bar item shows ready, setup needed, running, or
blocked; VZT: Doctor reports hook and mux state; and VZT: Start Ship
Watch From Spec launches a --mux vscode run from an existing SPEC.md.
One command: dispatch each dependency wave of units as a claude worktree pane, at
most --max-concurrent (default 4, 0 = unlimited) in flight at once → wait for each
to finish → auto-run its oracle, stamp its card, record the ledger → integration gate →
stop at "ready to review + merge". A unit declaring dependsOn opens its worktree
already seeded with its dependencies' finished work (committed, uncommitted, and
untracked, applied and committed before launch), so "build against the interface the
barrier wrote" is a worktree that actually has it rather than a race; a unit whose
dependency did not pass is recorded BLOCKED and never dispatched. Each pane is
auto-bootstrapped (orca/worktree-bootstrap.sh symlinks node_modules/.env* from the
primary checkout, so a worktree can actually build), and the ledger resolves to the
primary checkout so parallel worker writes are never lost or conflicted. This is
not the fan-out that vzt-route rejects — units within a wave are pairwise-disjoint,
not a race. Full guide: orca/README.md.
Claude + Codex collaboration
This repo carries a Codex-facing AGENTS.md and a shared
docs/CLAUDE-CODEX-COLLABORATION.md
contract. Claude Code owns orchestration, product interpretation, and HITL
gates; Codex owns bounded implementation, mechanical refactors, focused review,
and local verification.
Use files, not memory, for work that must survive compaction or an agent
handoff: .vzt/team/handoff.md, .vzt/team/decisions.md,
.vzt/team/verification.md, and dated review notes under .vzt/team/reviews/.
When this execution-layer repo changes routing doctrine, model tiers, worker
briefs, hook wiring, ship orchestration, or VS Code mux behavior, mirror the
corresponding protocol note into the sibling
../VZT-Tech-Consulting-Protocol checkout before release. If the sibling repo
is dirty, patch only the relevant docs/templates and preserve unrelated work.
VS Code agent bus
Claude Code and Codex can sit in two split VS Code terminals and talk to each
other through a durable local message bus. Terminals only host the processes;
they are never the transport. Messages live in a WAL SQLite file
($VZT_BUS_DIR, default ~/.vzt/bus/bus.db) driven by short-lived
vzt-agent bus calls, so queued mail survives a window reload. The only bytes
ever typed into an agent terminal are one fixed wake line and, 400 ms later, a
bare Enter. Full contract: .vzt/ship/vscode-agent-bus/SPEC.md.
Messages are typed (message, task, question, reply, gate, report)
and move through an explicit state machine; tasks need an idempotency --key
and run one at a time per agent.
vzt-agent bus register --alias A --kind claude|codex --window W --json
vzt-agent bus send --to ALIAS --type message|task|question --subject S --body B [--key K]
vzt-agent bus check --json # read your mail (agent side, VZT_BUS_AGENT set)
vzt-agent bus ack --id ID
vzt-agent bus reply --id ID --body B
vzt-agent bus ask --to ALIAS --subject S --body B [--timeout-ms N] # blocks until answered
vzt-agent bus complete --id TASK --outcome succeeded|failed
vzt-agent bus gate-resolve --id ID --decision approve|reject
vzt-agent bus agents --json # registry with liveness: live | stale | never-started
vzt-agent bus inbox --json # threads, for the sidebar
vzt-agent bus doctor --json # node:sqlite, hook wiring, Codex hook trust
# used by the extension: argv, bootstrap-prompt, lease, wake-plan, wake-sent, hook
Exit codes: 0 ok, 2 usage/validation, 3 inside Orca (use orca orchestration
there instead), 4 conflict, 5 ask timeout, 70 Node without node:sqlite
(the bus needs Node >= 22.13).
Install. vzt-agent install wires four lifecycle hooks (SessionStart,
UserPromptSubmit, Stop, PermissionRequest) into ~/.claude/settings.json and
~/.codex/hooks.json ($CODEX_HOME honoured). They no-op unless the terminal
carries VZT_BUS_AGENT. Codex skips new hooks until you trust them: open
codex, run /hooks, and trust the 4 vzt-bus hooks. vzt-agent bus doctor
lists untrusted ones, and the VS Code Pair command refuses to launch Codex until
they are trusted. The hook command is a byte-stable constant, so trust survives
reinstalls.
Codex Stop-hook limitation. Claude receives mail at the end of a turn
through a Stop-hook decision: block. Codex never does: a Stop-hook block can
permanently wedge a Codex session
(openai/codex#20783), so the
Codex Stop hook prints nothing and Codex is reached only by the wake line,
typed after it goes idle and confirmed by its next UserPromptSubmit beat (one
retry Enter, then the agent is flagged wake_unconfirmed).
Live gate. Offline tests cover the bus; the real round trip is a manual gate because it launches real agents and spends quota:
vzt-agent install # then trust the 4 Codex hooks via /hooks
node test/live/bus-roundtrip-live.mjs
It replays the extension's poller against Claude and Codex running under PTYs
in this repo: a harness alias gives Claude a task to bus ask Codex for 6*7
and write the answer to .vzt/live-bus/<run>/answer.txt. Exit 0 needs the
answer 42, the task completed/succeeded, and at least one confirmed Codex
wake within 10 minutes; a failure prints the reason and both screen tails.
It uses the vzt-agent on your PATH (the same one the hooks call), so update
the global install first.
Orca-style workspaces
The vzt-mux extension (0.8.0) brings Orca's project → worktree → agent model
into VS Code, on top of the agent bus above. Contract:
.vzt/ship/vscode-orca-workspaces/SPEC.md.
| Orca | vzt-mux |
|---|---|
| project | Projects view |
| worktree | New Agent Workspace |
| orchestration send/ask | vzt-agent bus send/ask |
| status glyphs | Projects view glyphs |
| bell | attention |
| Agent Dashboard | Agent Dashboard |
- Projects come from an explicit registry,
~/.vzt/projects.json(VZT_PROJECTS_FILEoverrides it); nothing scans the disk. Each project opens in its own VS Code window. - Worktrees go to
<parent of root>/<project>-worktrees/<slug>on branch<slug>. Removing one keeps the branch. - The bus is project-scoped: every worktree of a project shares one scope,
so agents address each other by alias across worktrees. An alias is unique
per project. Other projects stay unreachable unless
--cross-workspaceis given.
vzt-agent projects add <dir> [--name N] [--base REF] [--json]
vzt-agent projects remove <name> [--json] # registry only; disk untouched
vzt-agent projects list [--json] # projects with their git worktrees
vzt-agent worktree create --project N --name SLUG [--base REF] [--json]
vzt-agent worktree remove --project N --name SLUG [--force] [--json]
Exit codes: 0 ok, 2 usage/not found, 4 conflict (the name, path or branch already exists).
Visual work — DESIGN.md is a taste cache
Visual work was the one kind this protocol was blind to. "Restyle the dashboard", "fix the spacing", "the palette is off" matched no signal and fell into the zero-signal default bucket, where they got done from whatever the model imagined the product looked like. That is how one org ends up with thirty repos and thirty palettes.
The fix is not a better model. It is a file.
A DESIGN.md at the repo root moves visual taste off the model tier and onto
disk — the same move /vzt-ship makes for long-horizon plans, for the same reason.
Once the taste is written down, applying it is not judgement; it is execution:
| taste comes from | tier | agent | |
|---|---|---|---|
No DESIGN.md | the model | Opus | vzt-art-director — decide once, write it down |
DESIGN.md exists | the file | Sonnet | vzt-stylist — apply it faithfully |
The classifier gates on this with a filesystem check, structurally identical to
the two-factor HORIZON gate except the second factor is a file rather than a second
regex. So the routing control is a git-diffable artifact: create DESIGN.md and
visual work routes down forever; delete it and taste work routes back up.
Two properties keep it honest. A DESIGN.md under 400 bytes doesn't count — a
placeholder would down-route every visual request in the repo while containing no
taste to apply, and a cache that lies is worse than no cache. And every generated
DESIGN.md carries a ## Compliance section with a runnable check, recorded as
a baseline and ratcheted, because a design doc nothing enforces is decorative: the
repos that already carry 300-line design systems also carry a thousand-plus raw
palette classes.
Start with /vzt-ui extract. Template: templates/DESIGN.md.
This is visual design. Technical design — architecture, schemas, APIs, migration plans — is a different lane:
/vzt-designandvzt-architect.
External side effects — CONNECTIONS_IN_SCOPE
FILES_IN_SCOPE is the collision boundary for what a worker writes inside the
repo. Until now nothing bounded what it could reach outside one — and the ship
path is exactly where that gap bites hardest. orca/worktree-bootstrap.sh symlinks
the primary checkout's node_modules and .env* into every unit's worktree,
because a worktree that cannot build cannot be graded. The side effect is that a
fan-out of parallel agents starts life holding whatever production credentials the
repo holds, and the only thing standing between a builder and a live customer API
is that nobody happened to tell it to call one.
So external access gets the same treatment as filesystem access — declared in the spec, checked by a command, default-deny:
// .vzt/connections.json — git-tracked, and never contains a credential
{
"version": 1,
"connections": [
{ "id": "stripe-test", "service": "stripe", "mode": "test",
"credentialEnv": "STRIPE_TEST_SECRET_KEY", "allow": ["read", "write"] }
]
}
// a unit in SPEC.md, or CONNECTIONS_IN_SCOPE in a worker brief
{ "id": "u2-invoice", "filesInScope": ["src/invoice.ts"],
"connectionsInScope": ["stripe-test"] }
ship-check exits non-zero when a unit names a connection the registry does not
declare, when the registry is absent entirely, when it is malformed, or when an
entry carries an inline token/secret/apiKey/key/credential/password —
the registry names the env var, never the value, because a file whose whole
job is to bound blast radius is the last place a live key should live.
Three properties keep it honest:
- Omitted means none, not unrestricted. A unit that declares nothing is repo-local work — nearly all work — and pays no friction for it.
- The absent registry is a red gate, not an open one. "No registry" means nothing has been declared, so a unit claiming a connection fails.
- The boundary is rendered into the worker's prompt, on every unit, including
the ones declaring nothing. A boundary that binds the plan but never reaches the
agent is decorative — the worktree still has the symlinked
.env, so the only enforcement the worker ever sees is the sentence stating the rule. Silence about outbound calls reads as permission to make them.
Template: templates/connections.json. Same move as
SPEC.md and DESIGN.md, one axis out: put the boundary on disk, and a command
can enforce what doctrine only requested.
Mobile store releases — approval is not delivery
The furthest-outside side effect the protocol touches is a store release, and it is the one place where the absence of an error is not evidence of success. Every failure shape here is silent: nothing throws, no test goes red, the console is green, and users simply never receive what you shipped.
Shipping is three events, not one — the store approves, the storefront starts serving, and users are told. The third is usually a row in your own database that nothing updates on its own. Measured twice on real releases: the storefront moved to a new version while the release row still named the old one, so the in-app "update available" banner stayed dark for every install until a human ran the sync.
The rest of the doctrine — an OTA reaching exactly one runtime (a version bump
orphans everyone else, and publishing into an audience of zero looks identical to
publishing well); store policy declarations that deadlock the API; a permission a
dependency's manifest added for you; the rule that a capability gate and its
permissions must move together because both ways of getting it wrong ship
rather than fail a build; the Play service-account chain including the
iam.disableServiceAccountKeyCreation org policy that is on by default — lives
in skills/vzt-mobile-release/SKILL.md.
Two habits generalise beyond mobile and are worth stating here:
- Verify the artifact, not the build log — and include a control, something that should still be present, so a passing check cannot be a broken extraction.
- An oracle you have never seen fail is not an oracle. Break the invariant on purpose, watch the test go red, restore it. Cheap, and the only thing that distinguishes a real check from a comfortable one.
Guardrails
- Escalation ladder — two failures at a rung escalates exactly one rung (haiku→sonnet→opus→opus@max→fable), stated aloud. Under-routing is self-healing.
- Fable budget — ≤10% of turns;
vzt-agent statsshows your distribution against the target. Theopus@maxrung absorbs the planning that used to land on Fable. - External side effects are default-deny — a worker reaches only the
connections its brief declares against
.vzt/connections.json; holding a credential is not permission to use it. - Delegation cap — never delegate work finishable in a handful of tool
calls; prefer one sub-agent over several; once delegated, commit. Verify
external artifacts (run the oracle,
git diffthe worker's output); never spawn a sub-agent to double-check your own inline work. - Store releases are measured, not assumed — approval, serving and announcing are three separate events; ask the public storefront, never the console. See Mobile store releases.
- No frontier execution — plans always hand execution to cheaper tiers.
- Advisory, not authoritarian — directives are context injections; Claude overrides them only with a stated reason.
Manual overrides
| Input | Effect |
|---|---|
@fable / @opus / @sonnet / @haiku prefix | Force a tier for that prompt |
~ prefix | Bypass routing for that prompt |
/vzt-route <task> | Ask for an explicit routing decision |
/vzt-route stats | Tier distribution vs. targets |
CLI
vzt-agent install [--global] [--target <dir>] # install + wire settings.json
vzt-agent uninstall [--global] [--target <dir>] # clean removal
vzt-agent doctor [--global] # health check
vzt-agent sync [--check] [--only <.claude dir>] # refresh every install on this machine (~/.vzt/installs.json)
vzt-agent stats # routing distribution + Fable budget, /vzt-ship and ui taste-cache falsification
vzt-agent matrix # print the routing matrix
vzt-agent ship-check <SPEC.md> # gate a /vzt-ship spec — disjoint scopes, an oracle per unit
vzt-agent ship-start <SPEC.md> # open the run ledger for a gated spec
vzt-agent ship-note <SPEC.md> '<json>' # append one ledger line
vzt-agent ship-status [--target <dir>] # reconstruct run state from disk (use after a compaction)
# Supervision layer (optional — parallel /vzt-ship runs in an agent multiplexer)
# --mux orca (default) | herdr | vscode (native VS Code terminals; see docs/VSCODE.md)
vzt-agent ship-watch <SPEC.md> [--mux orca|herdr|vscode] [--timeout-ms <n>] # kick once: dispatch → wait → verify → gate
vzt-agent ship-dispatch <SPEC.md> [--mux orca|herdr|vscode] [--execute] # one worktree+claude per unit
vzt-agent ship-supervise <SPEC.md> [--mux orca|herdr|vscode] # verify each oracle → shared ledger + mux card
Keeping installs current. Installs are copies, so every install records
itself in ~/.vzt/installs.json and vzt-agent sync refreshes each one whose
source content changed (the first run also adopts existing installs: ~/.claude
plus ~/github-projects/*/.claude, or VZT_PROJECTS_DIRS). It runs automatically
from two places. One is the protocol repo's git hooks: run npm run hooks:enable
once and every commit, merge or rebase on main in the primary checkout syncs in
the background (log in ~/.vzt/sync.log). The other is every SessionStart, which
fires a detached sync --only <that .claude>. Refreshed project .claude/ files
are left uncommitted for you. Set VZT_NO_AUTOSYNC=1 to opt out.
Get the bare vzt-agent command with npm install -g github:vonzelle-vzt/vzt-agent-protocol,
or prefix any of the above with npx github:vonzelle-vzt/vzt-agent-protocol
(from a clone: node cli/vzt-agent.js).
Maintenance gate
Before release, run:
npm run verify
That runs the main node:test suite, root and VS Code npm audits, the VS Code
TypeScript compile, and an npm dry-run package check. The root package uses an
explicit files allowlist so local .claude/, .codex/, .vzt/, and test
state do not leak into install tarballs.
The process is the moat — the five Fable layers
Model choice alone isn't the whole story — the working discipline riding on
top of it is. /vzt-fable-mode extracts the frontier tier's working process
into five gates any tier can run. Each gate is a checkpoint the work must
pass before moving on; skipping one turns the output into a guess, no matter
which model produced it. A cheaper model running these gates beats a frontier
model running none. The canonical long form lives in
skills/vzt-fable-mode/SKILL.md.
Gate 1 — Scope before you act
State the plan before touching anything: what the brief actually asks for, the smallest change that satisfies it, and what is explicitly out of scope. Then play devil's advocate against your own plan once — list the unknowns and assumptions it rests on, and for each one say how you'll resolve it (read the file, run the command, ask). A plan whose unknowns are named is a plan; one without them is a guess with steps.
Gate 2 — Evidence before reasoning
Never reason about code you haven't looked at this session. Confirm files, symbols, APIs, and flags exist — read/grep them — before building on them. What the model remembers from training or an earlier session is a hypothesis, not evidence: partial recognition does not mean current knowledge, and a prompt implying a file exists does not mean one does. Verify, then reason.
Gate 3 — Attack your own approach
Before executing, try once to break the plan: what input, state, or ordering makes it wrong? What's the strongest argument this is the trigger and not the cause? Name the evidence that would refute the approach and go check it. If the attack lands, fix the plan now — it is exponentially cheaper than fixing the shipped version.
Gate 4 — Verify before declaring done
Every change gets a machine-checkable oracle — a test, a command, a curl, a rendered page — decided before the change is made, not invented after. Run it and paste the actual output. "Should work," "looks correct," and a green typecheck are not verification; behavior observed end-to-end is. If the oracle can't be run, say so explicitly instead of implying it was.
Gate 5 — Report only what you verified
No claim in the report that wasn't checked. Anything unverified is marked unverified or dropped — a finding you can't walk through end-to-end is a guess. An honest partial report ("3 done, 1 blocked on X") beats a padded complete-sounding one every time. Failures are stated plainly, with the output that shows them.
Why gates, not effort
The gates are about process, not effort: raising the effort dial does not compensate for a skipped gate — a skipped gate at max effort is still a guess. They also scale down: on Haiku each gate is one line of output; the discipline is identical, the prose is shorter. Combined with the orchestrator doctrine (frontier designs and verifies, Sonnet/Haiku execute and report back), this is what delivers the ~2–10× lower cost on routine steps at equal quality. The Cost/Intelligence/Taste columns in the routing matrix quantify that trade-off tier by tier, so the routing decision is a number, not a vibe.
How fable-mode activates
- Always on at the Opus tier — Opus never runs bare. Every Opus surface
carries the five gates by default: the
vzt-architect,vzt-heavy-builderandvzt-revieweragents state them as their first rule, the Opus chair profile injects them at session start, and every[VZT-ROUTE]directive that targets Opus restates them. Opus stays Opus (no model change) — it just always works with Fable's process. Frontier discipline, cheaper model. - Automatic in fleet executors —
vzt-builderandvzt-mechaniccarry the gate summary as Rule 1, so anything the router delegates to them runs the gates with no user action.vzt-plannerandvzt-oracle(Fable) don't reference it — they're the source of the doctrine, not a consumer of it. - Manual on the chair —
/vzt-fable-mode <task>loads the full skill into the current turn, the "elevate Sonnet" move for hard inline work. The model may also auto-invoke it when a task obviously calls for the discipline, but the slash command is the guaranteed path. Skip it for routine one-liners — the gates would just add overhead. - No model pin, by design — unlike
/vzt-plan//vzt-fix(force Fable),/vzt-build(Sonnet), and/vzt-quick(Haiku), fable-mode runs on whatever model is already active. It changes how the current model works, not which model works: the router picks the tier and effort, fable-mode upgrades the discipline of whichever tier got picked. The two dials are independent.
Requirements
- Claude Code ≥ 2.1.170 (skill/agent
model:frontmatter incl.fablealias) - Node.js ≥ 18
- A plan with access to Fable 5.1 (falls back gracefully:
availableModelsrestrictions make blocked tiers inherit the session model)
Docs
- Chair profiles — Opus-first, Sonnet-first, Fable, Haiku
- Routing matrix + decision procedure
- DESIGN.md template — the visual taste cache
- Orca supervision layer — watch a /vzt-ship run in Orca
- CLAUDE.md snippet for manual installs
Release notes
1.20.0 — Claude Code 2.1.290–2.1.299, and installs that keep themselves current
Installs keep themselves current. On 2026-10-17 vzt-agent doctor found the
global ~/.claude install and all four project installs on this machine out of
date. The global one was still injecting the retired model lineup on every
session. The cause was that installs are copies and nothing re-ran install.
Now install registers itself in ~/.vzt/installs.json, and the new
vzt-agent sync (with --check) refreshes every install whose source content
hash changed. It runs from the repo's .githooks (npm run hooks:enable),
but only on main in the primary checkout, so unmerged ship-unit worktree code
never reaches real projects. It also runs as a detached self-heal from every
SessionStart. See CLI.
Doctrine updates from the October Claude Code releases:
- Per-subagent effort and model in the prompt (2.1.292). A dispatch prompt can
set effort for each subagent, and that overrides the session effort with no
frontmatter needed. The chair profiles, both routing docs and the worker brief
(new
EFFORT / MODELfield) now say to set it per worker: low-effort Haiku scouts plus one high-effort reasoner. - Subagent pacing. Every worker brief's CONSTRAINTS now opens with the engineering team's measured line: "Time matters here: do not spend time that can be avoided, and the earlier a correct result is obtained, the better."
- Haiku 5.5 has a 1M context window.
vzt-scoutreads whole trees instead of sampling. - Sonnet 5.5 cache reads cost $0.10/MTok, down from $0.20. This is noted in the cost comment. The input/output ratios (100/40/20/1) are unchanged.
- Fast mode is GA on the Claude API for Opus 5.5 (
"speed": "fast"), the API twin of/fast. onFailure: "block"(2.1.295) is deliberately not used. Every managed hook is advisory and fail-open. A new test fails ifinstallever writesonFailure.- UserPromptSubmit now runs before skill and slash-command expansion (2.1.299).
This doesn't affect the classifier: it already skips
/prompts and never blocks. - Idle compaction (2.1.299). A session over 200K tokens is compacted while idle,
right before the one-hour cache expires.
/vzt-shipnow treats any long idle (such as waiting onship-watch) as a compaction: re-runship-statusand re-read the SPEC. - Noted, with no protocol change: desktop nested sessions,
/btwtabs, cloud session handoff, Team network secrets, API credits,/usagewarnings, the web-search refill andclaude plugin install --marketplace.
1.19.0 — the 5.5 lineup: Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5
The doctrine named the previous generation (Fable 5, Opus 5, Sonnet 5,
Haiku 4.5) in every hook, agent, skill and doc, while a few surfaces already said
5.5. That made the lineup inconsistent. Fleet agents pin aliases (model: opus),
so they were already running the new models. Only the prose and the cost math
were behind.
- Names. Every shipped surface now names Fable 5.1 / Opus 5.5 / Sonnet 5.5 /
Haiku 5.5. The retired-model guard in
test/classifier.test.mjsnow fails on the old names too, and onhaiku-4-5. It matchesOpus 5but notOpus 5.5. - Cost ratios re-anchored to sticker pricing: Fable 5.1 $10/$50, Opus 5.5
$4/$20, Sonnet 5.5 $2/$10, Haiku 5.5 $0.10/$0.50 per MTok. The
TIERS.costmultipliers move from 10/5/3/1 to 100/40/20/1. Haiku 5.5 is 20× cheaper than Sonnet 5.5 (above 100K-token prompts it is $0.50/$2.50, so 20/8/4/1). Because of that gap, pushing recon and mechanical work down to Haiku matters more than it did. Opus 5.5 costs 40% of Fable's price, down from half, so theopus@maxplanning rung costs less than before. /faston Opus 5.5 is $8/$40 per MTok, 2× standard. Before, the session-start hook quoted Opus 5's $10/$50.- Effort defaults. On the API, Opus 5.5 and Haiku 5.5 default to
medium, while Sonnet 5.5 and Fable 5.1 default tohigh. Sonnet 5.5 recalibrated its levels. The routing docs now say to set effort explicitly instead of relying on the default. - The routing logic itself is unchanged: same signals, same rungs, same agents.
1.18.0 — the agent bus, and three silent ship-watch failures on Orca
Agent bus (VS Code). Claude Code and Codex can now pair in two split VS Code
terminals and message each other through a durable local bus
(vzt-agent bus <verb>, WAL SQLite at $VZT_BUS_DIR, default ~/.vzt/bus). Typed
messages, an explicit state machine, idempotency keys, 300 s redelivery, 30 s
leases, one task in flight per agent. Terminals only ever receive a fixed wake
line plus a separate Enter. Claude also gets mail via a Stop-hook block; Codex
never does, because a Stop-hook block can wedge a session
(openai/codex#20783). The
vzt-mux extension is 0.7.0 with VZT: Pair Claude + Codex, a vztBus
sidebar view and vztMux.busCommand. Pair refuses unless workspace trust is on
and the Codex hooks are trusted, then runs bus trust-claude (Claude Code
2.1.299 stops on its folder-trust dialog even with permissions bypassed). Live
gate node test/live/bus-roundtrip-live.mjs passed in 31 s on 2026-10-14;
re-run it after any claude/codex update. See VS Code agent bus
and docs/VSCODE.md.
ship-watch on Orca — three silent failures fixed the same day:
e2254e5pre-trusts each unit worktree in~/.claude.json(cli/claude-trust.mjs). Before, every unit pane sat on the trust dialog and was failed at the 90 s start check.ab479b2creates unit worktrees with--base-branch <primary HEAD sha>. Before, Orca based them on origin/main, so local unpushed commits (even the run's own SPEC.md) were invisible to units.693c5ce+5e6d444: the integration gate's bare worktree now gets the primary checkout'snode_modulessymlinked and runs the repo'sorca.yamlsetup.command. Before, gates that build failedERR_MODULE_NOT_FOUND, and vzt-orca-flow's live doctor failed "pipeline state NOT initialized" with every unit green.
/vzt-ship also gains the cross-spec ordering rule (specs that share files run
as a strict sequence).
1.17.0 — a worktree is not seeded, so "build against the interface" was fiction
🔴 Every git worktree add branched from the primary checkout's HEAD, on all
three backends, with no merge, rebase, or cherry-pick anywhere in cli/. So a
unit briefed "implement X against the interface the barrier wrote in
types.ts" opened a tree where types.ts did not exist — the barrier had
written it in a different worktree, on a different branch. The unit then
either failed its own oracle or breached scope re-creating the file itself, and
the only place the pieces ever met was the integration gate, at the very end,
after every unit's budget was already spent.
The fix is a real dependency graph, not a naming convention. Spec units gain an
optional dependsOn: ["<unit id>"]; ship-check now also rejects a dependsOn
cycle (named — "u1 → u2 → u1", not "there is a cycle"), an unknown unit id, a
self-reference, and naming the barrier (it is every unit's implicit
dependency already). New pure exports in cli/ship-lib.mjs: planWaves(spec)
(topological waves — a spec with no dependsOn anywhere still yields exactly
one wave, so every existing SPEC behaves identically), depsOf(u), and
pathInScope(p, scope).
Before a unit launches, seedFromDeps() applies each dependency's finished
work into the dependent's worktree — transitively, in topological order,
barrier first — via the same captureWorktreePatch the integration gate
already used (committed, uncommitted, and untracked), then commits the
result and records it as baseSha. Committing matters twice: it becomes the
scope-audit baseline, so a seeded file is never mistaken for this unit's own
write, and it keeps this unit's own captured patch free of its dependencies'
content. Seeding is idempotent (git apply --check --reverse detects an
already-applied seed), so a corrected, re-dispatched unit doesn't choke on its
own seed commit.
ship-watch now runs units in dependency waves, not spec.units.map(dispatch).
A wide spec used to start one claude process and one terminal per unit, all
at once; a new --max-concurrent <n> flag (also VZT_MAX_CONCURRENT, default
4, 0 restores the old unlimited behaviour) bounds how many run at a time.
A unit whose dependency did not PASS is recorded BLOCKED and never
dispatched — its worktree would be seeded from a broken or absent base, so
running it anyway would fail for a reason that has nothing to do with its own
brief.
Runtime SCOPE_BREACH moves onto the supervised path. verifyAndRecord
now audits git diff --name-only <baseSha> plus git ls-files --others --exclude-standard in the unit's own worktree before running the oracle.
This verdict used to exist only in the headless workflows/vzt-ship.js path —
on ship-watch a breach first surfaced as a failed git apply in the
integration gate, at the very end, attributed to whichever unit happened to
apply second. Seeded dependency files and gitignored build artifacts are
correctly not breaches.
A reused worktree that has fallen behind gets a --- BASE DRIFT --- block
appended to its prompt, naming the missing-commit count and the 5 most recent
subjects it's missing; a freshly created worktree emits nothing.
The VS Code Ship Run tree groups units under collapsible Wave N nodes
(units stay vztUnit, so Focus Terminal / Open Worktree Diff / Re-run Oracle
are unchanged), adds two states — waiting (grey, a dependency hasn't passed
yet, distinct from queued) and SCOPE_BREACH (red warning, deliberately a
different icon from FAIL's red error) — and rolls a wave up to its worst
member (SCOPE_BREACH > FAIL > blocked > working > queued > waiting
finished>PASS). A record from a pre-0.6.0 CLI has nowaveand still renders as a flat list. Extension0.6.0.
The headless path honours dependsOn too. A field one engine obeys and the
other silently drops is worse than no field — the spec would promise an ordering
that half the runs ignore. workflows/vzt-ship.js now dispatches wave by wave
and records BLOCKED for a unit whose dependency did not pass. Workflow scripts
cannot import, so it carries a hand copy of planWaves/depsOf, and a drift
guard runs both against the same fixtures and fails if they ever disagree. It
needs ordering only, not seeding: those agents share one checkout, so a unit
that runs after its dependency simply finds the files there.
"If I close my lid, do my agents keep running?" With --mux vscode, a ship
unit is a VS Code integrated terminal — a child of the extension host — so
closing or reloading the window kills every in-flight agent. That was already
true; what was new is that the tree now says so. Sentinels are files, so
.started survived on disk, .idle never arrived, and a unit that died
yesterday rendered as a cheerful spinner forever. The extension stamps
activatedAt into host.json, and any unit dispatched before this host booted
is now interrupted (orange debug-disconnect) with a tooltip saying why. A
recorded verdict still wins — PASS is durable, liveness is not. Separately,
ship-watch holds a caffeinate -i -w <pid> idle-sleep assertion for the length
of the run (macOS; opt out with VZT_NO_CAFFEINATE=1), so walking away does not
suspend a unit mid-turn. For a run that must outlive the editor entirely, use
--mux herdr — its panes belong to a daemon, not to a window.
183 tests.
1.16.0 — review a diff in VS Code, and prove the agent got it
A herdr pane has no cursor, so every terminal review tool makes you retype
path:line by hand to say which line you mean. VS Code's native diff editor and
Comments API do it for free, and one command ships the whole batch back as a
single agent.prompt. This is the differentiator; the tree view is table
stakes — every rival plugin is read-only.
What the surface refuses to guess is the interesting part. The plan said to open
"the agent's diff", and herdr cannot tell you what that is: measured on the
live five-workspace fleet, every agent reports cwd and foreground_cwd as
$HOME, because herdr's new_cwd is $HOME and the agent cds afterwards.
WorkspaceInfo.worktree was unset on all five. So the diff is this window's
repo — which VS Code knows exactly — and the agent is chosen explicitly.
Nothing inferred, nothing inferred wrong.
agent.prompt's target is a PANE ID and nothing else. Probed read-only withagent.get, which takes the same target string:w5G:p1resolves, while the workspace label, the workspace id and the terminal title each come backagent_not_found.pane_idtravels end to end and no display name is ever resolved — a review landing in a different agent than the one you picked is the one failure this surface must not have.- Comments are grouped by file and ordered by line, never by click order. An agent handed comments in click order has to reconstruct the diff and will interleave two files while editing. Each comment carries its anchored source line, so "this" has a referent without re-reading the file.
- A failed send keeps the comments. Losing a written review to a transport error is unforgivable, and a retry is one click away.
🔴 The review loop shipped broken, and every cheap probe passed. Measured on
live herdr 0.7.5 against a real Claude Code agent: a single line submits
(idle → working within 2s), while multi-line text lands in the composer as
[Pasted text #1 +13 lines] and sits there — still idle 30s later, after a
call that returned ok in 153ms. A review is always multi-line, so the surface
reported "sent 2 comments", discarded the threads, and parked the review in an
input box nobody was looking at. Fixed with an unconditional agent.send_keys
enter after the prompt: polling for a status change is slower and races a fast
agent, while a spare enter hits an empty composer and does nothing.
That bug is why this release also ships a live gate. npm run test:live
stages its own throwaway project, splits a pane, starts a real agent, and runs
two phases — a CONTROL that sends a bare agent.prompt with no enter and
must stay stuck, then the real client which must leave idle within ~2s and be
visible in the pane. The order is the point: a green FIXED phase alone is
equally what a daemon that submits everything would give you, or an assertion
reading the wrong field. It is deliberately not named *.test.mjs, so npm test
cannot pick it up — an automated suite that silently needs a staged agent goes
red for the wrong reason — and with no daemon it exits 2 / CANNOT RUN, never
0. The lesson generalises past herdr: a fake-daemon test asserts the bytes you
wrote, not that anything happened. The bytes were correct in the broken
version too.
Four traps found by making that gate stage its own agent, every one of them invisible from the daemon's side:
- A pane read is a rendering, so its text is hard-wrapped at the pane
width — a multi-word regex can straddle a newline and silently never match.
A trust-dialog matcher written from how the dialog looks never fired,
because the pane holds
…you created or\none you trust?. - Readiness must be positive ("the composer is on screen"), never an absence
("no dialog is on screen"). A fresh agent draws a startup screen before its
trust dialog, so an absence check passes in the gap. herdr reports
idlethroughout andagent.wait --until idlereturns happily: from the daemon's side nothing is running, and a modal is just a program drawing characters. pane.readover the socket needssourceand answersresult.read.text, both of which the CLI hides. Read a guessed field and you getundefined, which contains no marker — so the assertion passes while looking at nothing.- A freshly split pane is not yet at a shell prompt;
agent.startanswersagent_pane_busy, which reads as "in use" rather than "not ready yet".
The review body carries markers, never instructions. An earlier probe asked
the agent to reply ACKNOWLEDGED as a delivery signal, and the agent refused —
on the grounds that anyone able to file a review comment could otherwise steer
the session. It is right: formatReview output is attacker-influenced whenever
the review is not yours, and a gate must never depend on an agent choosing to
obey injected text.
Also in this release: the two fleet defects the 1.15.0 notes claimed were
pinned by tests, which did not exist — nothing under test/ referenced
FleetModel, pane_created or pane_updated, so both fixes shipped unguarded
while the suite stayed green at 113. Seven tests now run the real compiled model
against a stubbed vscode, rather than grepping source: model.ts carries a
long comment explaining each defect, so a source grep matches the explanation
and passes on broken code. Suite is 131 and the reconnect half is covered too —
the spin oracle runs in a child process on a hard deadline, because mutating the
backoff to 0 starves the event loop and hangs the suite instead of failing
it.
1.15.0 — a snapshot is a source, not the source
Adds a Herdr Fleet view to the vzt-mux extension: workspaces → tabs → agents,
badged working / blocked / idle / done, with "N working · N blocked" in the
status bar. Herdr stops being something you look at and becomes a daemon you
query.
The extension is a CLIENT and must stay one. herdr owns every agent process; a VS Code window reloads on every extension update and dies with the app, so an agent parented to the extension host dies with it.
Four things everyone assumes about the herdr API, measured against a live 0.7.5 rather than read:
- 89 methods, not 147.
agent.attachis CLI-only — there is no such socket method.- One request per connection. The server closes after answering, so a second
write is
EPIPE. Onlyevents.subscribeholds the socket open. pane_agent_status_changedcannot be subscribed globally; it requires apane_id. The globalpane.updatedcarries the wholePaneInfoincludingagent_status, and is the entire live-status mechanism.
Types are generated from herdr api schema --json on every compile, emitting
HERDR_PROTOCOL; the client refuses to run against a daemon reporting a
different number. That schema is not a JSON Schema — it is a container of
five sibling schemas with refs rooted at the container, whose sub-schemas repeat
27 shared $defs — so the generator hoists, localises refs and collapses the
repeats, asserting on every build that they are still structurally identical
rather than trusting it.
Two defects found by measurement:
pane_createdfires for a pane that ALREADY EXISTS withagent:null, and treating it likepane_updatedsilently drops a live agent from the tree until its next status change. Creation may no longer downgrade an agent already seen;pane_updatedstill may, or a finished agent lingers forever. The two are asserted to differ on purpose — closing the first defect by making both non-downgrading opens the second.- herdr's snapshot and its event stream disagree persistently. The stream
pushed
workingtwice whilesession.snapshotreportedidlefor 12s with no corrective event. The model follows the stream; the corrections are a re-seed on view visibility and an explicit Refresh, not a poll loop.
Nothing polls. The only timers under src/herdr/ are a 50ms redraw coalescer, a
request timeout and reconnect backoff, and the view opens no socket at all until
it first becomes visible.
1.14.0 — the visual lane: DESIGN.md is a taste cache
The protocol was blind to visual work. Eight agents, all tier-shaped; a grep for
design system|ui|theme|brand|visual|css|tailwind across the whole repo matched
nothing but two VS Code mux strings. So "restyle the dashboard", "fix the
spacing" and "the palette is off" scored zero signals and fell into the
default bucket — the same 48%-of-decisions hole the 1.9.1 audit found — where
they got done from whatever the model imagined the product looked like.
This release adds the lane, and the interesting part is what decides its tier: a file, not the prompt.
templates/DESIGN.md— the artifact. Follows theawesome-design-mdconvention (YAML frontmatter a machine can parse, markdown an agent can read), with three sections that convention lacks:## Token source of truth,## Compliance,## Known gaps. It also adds avariants:dimension the reference format has no room for — that frontmatter is flat because it describes marketing sites, and a flat file cannot express a scoped admin skin without silently flattening it into the global brand./vzt-ui,vzt-art-director(Opus, authors) andvzt-stylist(Sonnet, applies). An art director decides; a stylist executes the decision.- A two-factor gate in the classifier, structurally identical to HORIZON
except the second factor is a
statSyncrather than a second regex. A realDESIGN.mdroutes visual work DOWN to Sonnet; its absence keeps taste work on Opus. The routing control is therefore a git-diffable artifact.
The doctrine: a DESIGN.md is a taste cache. It moves visual judgement off
the model tier and onto disk — the same move /vzt-ship makes for long-horizon
plans, and for the same reason. Escalate the PROCESS, not the MODEL; one axis over.
Three details that are load-bearing rather than decorative:
- TASTE scores on Opus, SURFACE on Sonnet. The split is by who has to
decide: "make it feel premium" needs someone to invent an answer; "fix the
spacing" says what to change, not what it should become. Surface-on-Sonnet is
the safety property — a visual false positive can never buy a costlier tier.
Measured, not assumed: the existing
longTrivialregression case contains "adjust the spacing" and assertstier === 'sonnet'. Scoring SURFACE on Opus turns that test red on precisely the failure it exists to prevent. - A stub is not a cache. Under 400 bytes counts as absent. A placeholder would down-route every visual request in the repo forever while containing no taste to apply, and a cache that lies is worse than no cache.
## Complianceships a runnable oracle, baseline-and-ratchet. A design doc nothing enforces is decorative, and the audit that prompted this release found exactly that: repos carrying 300-line design systems and 1,197 raw Tailwind palette classes. The hex half of the check is filtered on purpose —var(--token, #hex)fallbacks are the correct defensive pattern and email templates genuinely cannot use CSS custom properties, so an unfiltered rule flags the best code in the repo and gets switched off within a week.
taste stays documentation. Deriving control flow from a column whose 10/9/8/4
values were assigned by feel would be a fake derivation, and it would let someone
tidying a docs table silently re-tier the whole lane. TASTE_TIER is a named
constant citing the column instead.
vzt-agent stats gains a visual (ui) line carrying the lane's own kill-switch,
matching the one /vzt-ship already ships with: at ≥20 ui decisions, a cache-hit
rate under 20% means nothing is reading a DESIGN.md, the lane is buying an Opus
turn per prompt, and it should be deleted rather than defended.
Also fixed: AGENT_TYPES omitted the two new agents, so ship-check would have
rejected any spec whose unit does UI work — the same drift that once hid
vzt-architect. And the README claimed "Seven agents" while listing eight.
1.13.0 — queue records belong to a window
The last blocker on --mux vscode, and it was two bugs wearing one coat.
~/.vzt/vscode-mux/queue/ is one directory, but every open VS Code window runs
its own extension host and every one of them polls it. Observed with 2 windows
and 3 hosts:
- Wrong-window routing — whichever host won the poll opened the terminal, possibly in a window you were not looking at, possibly running a different build. The likeliest explanation for identical runs behaving differently, and for a reload refreshing one host while an older one kept serving the queue.
- Duplicate processing — claiming was
readFileSyncthenunlinkSync, two steps, so two hosts could both read a record before either deleted it and both open a terminal for the same unit.
Now: the CLI stamps each record with workspaceRoot, and a host claims one only
if that root matches an open workspace folder (containment either way, so a
window opened on a subfolder or a parent still counts). The claim itself is an
atomic rename — exactly one host wins, the loser gets ENOENT — which still
matters when two windows legitimately have the same folder open.
Records without workspaceRoot come from an older CLI and are claimed by anyone:
a version mismatch degrades to the old behaviour, not to a dead queue. If no open
window owns the project, nobody claims it — the CLI now says exactly that instead
of blaming the extension, and deletes the record rather than leaving it for some
later, unrelated window to launch after the run has ended.
Proven with two competing stand-in hosts against one queue: the host owning an unrelated folder claimed nothing, the owning host claimed both units, 2/2 PASS and the integration gate merged. Extension 0.3.0 (the queue contract changed). 103 tests.
1.12.0 — Orca reaches parity, and a failed unit says why
Orca can finally skip permission prompts. worktree create --agent claude
uses Orca's built-in launcher, which accepts no agent-specific flags — so orca
was the one backend that could not pass --dangerously-skip-permissions, and an
unsupervised unit hung on its first prompt. Orca is still the implicit default,
so this was the untreated path most users would hit.
orca skills get orca-cli documents the fix and the trap. Dispatch is now the
two-step Orca prescribes for a custom argv — worktree create without
--agent, then terminal create --command '<full argv>' — and the agent handle
comes from terminal create, never the worktree: a bare worktree create leaves
a fallback shell as the first terminal, and waiting on that reports
tui-idle instantly and grades the unit before it starts.
Orca gained the two-phase wait. It exposes no agent status states, but
terminal read returns a monotonic latestCursor, and output is proof of life.
Written from the documented CLI contract and not exercised end-to-end (no
Orca runtime on the authoring machine), so it degrades on purpose: if a cursor
cannot be read the phase is skipped and behaviour falls back to the single-phase
wait that shipped before.
A failed unit now says why — on every backend. Orca can stream a running
agent's output (terminal read); herdr cannot, and the VS Code extension API
gives no read access to terminal contents at all. So a failing unit printed a
bare FAIL and the diagnosis had to be rebuilt by hand — about an hour, to
conclude "the agent never started". verifyAndRecord now prints the oracle
command, the oracle's own output, the worktree, and the agent's Claude Code
transcript path — or states that none exists, which is the diagnosis. Better
than the capability it replaces: a transcript outlives the terminal.
A stale VS Code extension host is now visible. activate() stamps
~/.vzt/vscode-mux/host.json with the version actually loaded; doctor compares
it to the installed manifest and says RELOAD THE WINDOW instead of green, and
ship-watch warns before spending units on a host that will ignore the fix.
The chair follows a mid-session /model switch — corrected. 1.11.0 preferred
settings.json; that was observed reading claude-fable-5[1m] while the session
was demonstrably on Opus 5. Both file sources go stale in opposite directions, so
the order is now payload → chair.json[sessionId] → settings.json. Every
decision logs modelSource and payloadHadModel, so a wrong chair can be
attributed instead of guessed at.
Not adopted, deliberately. Orca's orchestration (task DAGs, dispatch,
decision gates, coordinator loops) covers the same ground as /vzt-ship, but the
two optimize for different failures: /vzt-ship keeps coherence in a FILE that
survives compaction and lets no builder grade itself — completion is an
independent oracle — while Orca keeps coherence in the runtime and treats a
worker's own worker_done as completion, with stronger provenance than our
chair-written ledger. Neither subsumes the other.
Known open issue. ~/.vzt/vscode-mux/queue/ is global while extension hosts
are per window, so with several windows open the hosts race to drain it — the
terminal may open in a window you are not watching, running a different build.
Scoping the queue per window is the fix.
101 tests.
1.11.0 — the VS Code mux grows a lifecycle, and the ledger learns to close
An audit of the protocol against its own live data. Every item below was reproduced before it was fixed.
Ship runs now terminate. ship-watch wrote only 3 of the 7 ledger line
kinds its own reducer understands — no integration, no run_complete, no
aborted. reduceLedger therefore reported every completed run as active
forever, and the router hook re-injected a stale [VZT-SHIP] ACTIVE RUN
block into every prompt in that repo with no TTL to clear it. All terminal
lines are now written, including on the barrier-abort path.
A failing unit no longer reports as a pass. Two producers wrote
unit_result in different dialects: the supervised path PASS/FAIL, the
workflow path PASS/BLOCKED/ORACLE_FAIL/SCOPE_BREACH. FAIL matched
nothing, so a run containing a failed unit told the rehydrating chair "all
reported units passed" — a false green at exactly the moment, post-compaction,
when the chair has no other source of truth. Both dialects now go through one
exported FAILED_STATUSES set.
The integration gate stopped grading an empty tree. It ran against the
primary checkout, which by construction contains none of the unit work — every
unit lives in its own unmerged worktree. It now builds a temp worktree from
HEAD and applies each passed unit's full divergence (committed, uncommitted
and untracked — agents frequently do not commit) via a throwaway index, then
runs the check there. Because ship-check enforces disjoint file scopes, a
failed apply means a unit wrote outside its declared scope, and is reported as
MERGE_CONFLICT.
The VS Code backend got herdr's state model. One hook script now serves
three events — SessionStart→started, PermissionRequest→blocked,
Stop→idle — and waitIdle is two-phase: wait for life, then wait for idle.
Without a start signal a unit whose terminal never ran its command was
indistinguishable from one still working, so it burned the entire unit budget
before being graded against an empty worktree. Also fixed: sendText() into a
still-initialising shell is silently discarded (now gated on shell integration,
with a delay fallback), a .status rewrite was skipped on re-runs, and a throw
during launch silently lost the unit.
A Ship Run tree (activity bar → VZT Ship): per-unit live status, focus
its terminal, open its worktree diff while it is still being written, re-run
its recorded oracle. Extension 0.2.0.
Routing fixes, each found in the live log (2,037 real decisions, 56% of them low-confidence):
- The inspection family —
audit,analyze,investigate,inspect— scored nothing. The audit prompt that found this could not be routed by the classifier it was auditing. Nowopus:review. security audit|reviewwas an unconditional Fable escape hatch, bypassing theopus@maxrung entirely (the demotion is gated tokind === 'plan'). Routine security review is Opus; a security hole stays Fable.entire reporoutedopus:horizonwhilewhole reporoutedsonnet:build— the scope nouns had diverged between the two alternations.- Length and brevity nudges overrode evidence instead of amplifying it: a long
trivial prompt bought Opus on word count alone, and a sub-15-word prompt with
a tied
fable:debugsignal lost to recon phrasing. - The chair follows a mid-session
/modelswitch.chair.jsonwas stamped only at SessionStart, so a switch never propagated — and becausedirective()branches on the chair's rank, a stale seat actively suppressed down-delegation on the most expensive tier. vzt-agent statsde-duplicates double-routed prompts. A repo registering the classifier on top of the global one logged each prompt twice with divergent verdicts; the phantom rows pushed the Fable figure to 10.01% against a ≤10% target. Deduped: 9.9%. It also prints one decimal, so the gate stops contradicting itself at the boundary.
Install/guard fixes. install() now copies docs/ — the skills referenced
docs/VSCODE.md by path and nothing ever installed it. Re-running install
refreshes a managed hook whose command changed rather than leaving the stale
one wired. The retired-model guard globs the whole doctrine surface instead of a
hand-listed dozen (it was missing the two newest skills), and the
doctrine-reference guard now covers docs/ and orca/.
Known gap, stated rather than papered over: orca units still cannot skip
permission prompts — orca worktree create exposes no way to pass flags to the
agent it launches. Orca also remains the implicit default, and the CLI now says
so out loud when it falls through to it.
99 tests.
1.10.0 — Opus 5: the opus@max rung
Opus 5 launched, and the fleet had already moved — model: opus is an alias for
the latest Opus (claude --help: "an alias for the latest model"), so every agent
was running Opus 5 from launch day while the doctrine around it still described Opus
4.8. Nothing was broken; the guidance was just describing a model nobody was running —
and in one case describing behavior Opus 5 had reversed.
- New rung:
haiku → sonnet → opus → opus@max → fable. Routine planning — architecture, tech specs, roadmaps, migration plans, PRD breakdown — no longer reaches the frontier tier. Opus 5 is a step change on deep reasoning at half Fable's cost ($5/$25 vs $10/$50), so that band now runs on Opus atmaxeffort via the newvzt-architectagent and/vzt-designturn skill. Fable keeps what only Fable can do: planning with no prior art to reason from (novel, greenfield, from-scratch, or a one-way-door sharding / replication / consensus / multi-tenancy call) and impossible bugs. Fable budget ≤15% → ≤10%. - Implemented as a post-scoring demotion, not a signal rewrite. Every
SIGNALSweight is untouched: afable:planwin falls toopus:plan @ maxunlessFRONTIER_NOVELmatches. One block, reversible. Thefable:debugband is deliberately not gated by it — an impossible bug is frontier work regardless of how ordinary the system it lives in sounds. suggestEffortnow emitsmaxforopus+plan. That is the rung, and it is the only place the classifier suggests it — retiring the "never emits max" invariant 1.9.1 shipped.- Delegation cap — this reverses 1.9.x guidance, on purpose. Opus 4.8 under-reached
for sub-agents, so the chair profile pushed parallel fan-out hard. Opus 5 reaches for
them readily, and that guidance now compounds an existing bias into sub-agent sprawl.
The Opus chair and
vzt-heavy-buildernow carry a ceiling: never delegate work finishable in a handful of tool calls, prefer one sub-agent over several, keep spawn counts low, and once you delegate, commit — never re-derive a worker's findings. Parallel waves are reframed from a default motion to genuinely independent tracks only. - Verification, scoped precisely. Opus 5 self-checks unprompted, so instructions
telling it to verify buy nothing. The line matters and was drawn deliberately:
verifying an external artifact — the oracle's output, a worker's diff on disk,
observed behavior — is the whole point and is untouched, so fable-mode Gate 4,
MACHINE_CHECK, "reporting ≠ persistence", and/vzt-ship's independent verification stage all survive unchanged. What's cut is self-verification: don't spawn a sub-agent to double-check your own inline work, and don't pad turns with re-verification passes. - Scope and concision clause on the Opus chair and in
templates/worker-brief.md— Opus 5 expands scope and writes longer by default. Deliver the scope asked, no unrequested refactors or abstractions, lead with the outcome. /fastis now priced in the doctrine. On Opus 5 fast mode bills $10/$50 per MTok — Fable-tier price for Opus-tier intelligence. Still the right lever when wall-clock beats token cost; explicitly a bad default.- Effort guidance re-tuned. "Fable-low ≈ Opus-high" was an Opus 4.8-era equivalence
and is gone. Start
xhighfor coding/agentic andhighelsewhere, then sweep down — Opus 5 is unusually strong atlow/medium, so effort defaults inherited from earlier models over-spend. - Anti-staleness guard (the fix for the root cause). Two new tests assert that every
TIERSlabel's model name appears acrossROUTING-MATRIX/vzt-route/README/CHAIR-PROFILES, and that no retired version string survives outside this release-notes section. The next model launch now fails a command instead of quietly rotting a dozen files. Add a row to the test'sRETIREDtable when a model is superseded. - 79 tests green (was 72). 8 agents, 9 skills.
1.9.1 — routing gap-fill (audit follow-through)
xhighfor hard Opus builds. The per-prompt classifier now suggestseffort: xhighfor a high-confidence, multi-signal Opus build (a clearly-hard refactor + performance/concurrency + complexity task) — the current Claude Code default for hard coding/agentic, and what thevzt-heavy-builderit delegates to already runs at, so the inline suggestion matches the delegate. Single-signal Opus builds stayhigh; Opus review and horizon-supervision stayhigh; the classifier still never emitsmax./fastlever in the Opus chair profile. The Opus chair is explicitly wall-clock constrained, and/fast(Opus 4.8, ~2.5× output at premium tokens) is the direct lever — now named in the doctrine.--mux vscodedoctrine parity. The native VS Code backend shipped in 1.9.0 but the doctrine still read "two backends / orca|herdr"; threadedvscodethrough the Opus long-horizon profile and thevzt-route/vzt-shipskills.
1.9.0 — native --mux vscode + model-currency audit
- Native VS Code multiplexer — a third
--muxbackend alongsideorca/herdr.vzt-agent ship-watch <SPEC> --mux vscodeopens each ship unit as its own native VS Code integrated terminal in its owngit worktree, via a filesystem queue drained by the companion extension invscode/and aStop-event idle sentinel hook. No external multiplexer binary. Seedocs/VSCODE.md. - Model-currency audit — verified the lineup is current (Fable 5 / Opus 4.8 /
Sonnet 5 / Haiku 4.5;
model: fableconfirmed a valid Claude Code alias). Corrected theTIERScost multipliers to today's sticker ratios (Fable25×→10×, Opus15×→5×; Sonnet/Haiku unchanged), so the printed "× cost saving" figures are honest. Adoptedxhighfor the Opusvzt-heavy-builder— now the Claude Code default for hard coding/agentic work — while keeping the "no xhigh on routine work" guardrail.
1.8.0 — Herdr-supervised runs are the DEFAULT substrate for /vzt-ship
- The
/vzt-shipskill now drives supervised runs in a live agent multiplexer by default — whenvzt-agentis onPATHand a mux is live, runvzt-agent ship-watch <SPEC.md>so each unit is a realclaudeagent in a watchable/attachable worktree pane (Herdr viaVZT_MUX=herdr; omit--mux). The headless Workflow tool becomes the fallback (no mux live /vzt-agentoffPATH) — resumable but not watchable; the skill says which driver it used. This ships in the filesinstall()copies (skills/vzt-ship/SKILL.md+hooks/vzt-session-start.mjs), so every project pulls the default in on install — no per-repoCLAUDE.mdedit needed. ship-watchstill STOPS at the green integration gate; never auto-merges.- Test isolation fix: the
ship-dispatchdefault-mux test now clearsVZT_MUXfrom the child env before asserting the code default (orca), and separately asserts thatVZT_MUX=herdrmakes herdr the default with no--mux. Prevents a machine-wideexport VZT_MUX=herdrfrom turning the suite red. 70/70 tests.
1.7.0 — Herdr backend (agent-multiplexer supervision, --mux)
- The supervision layer is now multiplexer-agnostic via
--mux orca|herdr(defaultorca). Added Herdr (herdr.dev) — a terminal-native agent multiplexer (a binary, persistent over SSH/mobile) — as a second backend.ship-watch/ship-dispatch/ship-superviseall take--mux. - Refactored the Orca-specific code into a 5-method backend interface
(dispatch / waitIdle / resolve / stamp / plan). The Orca path is unchanged;
worktree-bootstrap.shand the primary-checkout ledger resolution are already mux-agnostic. - Herdr prerequisites (one-time):
brew install herdr,brew services start herdr,herdr integration install claude(so claude reports state →herdr agent wait). Verified live: worktree resolution, oracle-in-worktree, and workspace-label stamping (orcacard /herdr workspacelabel). 68/68 tests green.
1.6.0 — Orca supervision layer
- Orca accepted as a
/vzt-shipsupervision layer (not fan-out — that stays rejected; these units are pairwise-disjoint, not a race). The terminal-native protocol is unchanged; Orca just runsclaude, so routing/hooks/subagents/skills inherit as-is. Reserve Orca for parallel ship runs. - New CLI:
ship-watch(kick once — dispatch every unit as an Orcaclaudepane → wait for each → auto oracle + card + ledger → integration gate → stop at "ready to review + merge"; never auto-merges),ship-dispatch(the commands, dry-run or--execute),ship-supervise(verify each oracle → shared ledger- Orca card). Installed to
~/.orca/vzt/alongsideorca/worktree-bootstrap.sh.
- Orca card). Installed to
worktree-bootstrap.shsymlinksnode_modules/.env*from the primary checkout into each worktree — closing the "a worktree can't build" objection for the supervised case.- Ledger coherence fix:
ship-note/ship-statusnow resolveLEDGER.jsonlto the primary checkout (git worktree listfirst entry)..vzt/ship/is git-tracked, so worktrees used to fork their own ledger — worker writes were lost and branches conflicted. Byte-identical in a plain checkout. - See
orca/README.mdandskills/vzt-route/SKILL.md→ "Accepted — Orca as the ship SUPERVISION layer".
1.5.0 — long-horizon release
- Doctrine shift — "Escalate the PROCESS, not the MODEL." Scope language
("entire codebase", "from scratch", "greenfield", "end-to-end",
"multi-tenant") no longer routes to Fable. It now routes to Opus under a
new
HORIZONtask kind, gated two-factor: scope language alone stays a planning question on Fable; scope + a build verb becomesHORIZONand points at/vzt-ship. - New skill
/vzt-ship— spec-first long-horizon execution. SPEC (no code) → GATE (vzt-agent ship-check, a command not an opinion) → RUN (barrier → parallel units → independent oracle verification → bounded repair → integration gate, via the Workflow tool) → LAND (verify artifacts on disk, stop before commit/deploy). Survives compaction:SPEC.md+LEDGER.jsonlon disk, and the classifier hook re-injects a[VZT-SHIP]block on every prompt since compaction doesn't re-fireSessionStart. - New CLI commands:
ship-check,ship-start,ship-note,ship-status. - See Long-horizon work above.
1.3.0 — 2026-07-08
- Fable discipline is now always on at the Opus tier. The five fable-mode
gates (scope, evidence, attack, verify, report) are wired into every Opus
surface:
vzt-reviewernow carries them (previously only the builders did), the Opus chair profile injects them at session start, and Opus-targeted[VZT-ROUTE]directives restate them. Model routing is unchanged — Opus runs on Opus 4.8, but always with the frontier tier's working process. - New sync test asserting every Opus surface (both agents, the chair profile, the classifier directive) carries the gates.
- README now documents all five gates in full (The process is the moat —
the five Fable layers)
and leads the quick start with the zero-clone
npx github:install. - README cleanup: removed the third-party comparison section.
1.2.0 — 2026-07-08
- Added worker-brief delegation doctrine:
templates/worker-brief.mdis the canonical template (TASK/CONTEXT/FILES_IN_SCOPE/OPERATION/ACCEPTANCE/ MACHINE_CHECK/EXPECT/CONSTRAINTS/REPORT). - Collision boundary: FILES_IN_SCOPE in a brief is a hard write boundary —
vzt-builder,vzt-mechanic, andvzt-heavy-buildernow all STOP and report rather than expanding scope if the task needs a file outside it. - Machine-checkable acceptance:
vzt-planner's step-routing table now carries amachine_checkcommand per step, chosen at plan time, not invented by the worker after the fact. - Reporting ≠ persistence: the
SessionStarthook's Fable/Opus chair profiles andskills/vzt-route/SKILL.mdnow tell the orchestrator to verify worker artifacts on disk (git diff, re-run the check) before accepting a completion report. - Added a sync test asserting the template, the three worker agents, and the routing skill all carry the new contract.
1.1.0 — 2026-07-07
- Added the
vzt-fable-modeskill: the frontier tier's five working gates (scope, evidence, attack, verify, report) extracted into a portable process any tier can run — cited as a new rule 1 invzt-builder,vzt-heavy-builder, andvzt-mechanic. - Effort is now a routing dimension: the classifier computes a suggested
effort (
suggestEffort()) per prompt and surfaces it in every[VZT-ROUTE]directive (@ effort low|medium|high), alongside an effort note:xhighfor hard coding/agentic (where the heavy-builder runs), not for routine work. - Added Cost/Intelligence/Taste columns to
TIERSin the classifier hook, with matching columns indocs/ROUTING-MATRIX.mdandskills/vzt-route/SKILL.md— a sync test now enforces the cost values match across all three. - Added orchestrator doctrine (frontier designs and verifies; Sonnet/Haiku
execute and report back) to
vzt-planner,skills/vzt-route/SKILL.md, theSessionStarthook's Fable/Opus profiles, and the CLAUDE.md snippet.
License
MIT © VZT Tech Consulting