fable5-opus5-orchestrator
Health Pass
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 24 GitHub stars
Code Pass
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Keep Claude Fable 5 in the chair all day without draining your usage limit — token-frugal multi-agent orchestration plugin for Claude Code: tier routing (Sonnet 5 does the work), Requirements Ledger, enforced guard hooks, automatic teammate reaping
Fable Orchestrator
Run Claude Fable 5 all day — without watching the usage meter.
Fable 5 is the best chair a Claude Code session can have — and the most expensive seat in the house. Let it type every token itself and the session ends rate-limited, waiting out the reset window.
This plugin makes the split mechanical. Fable 5 keeps the chair and spends tokens only on planning, arbitration, and final decisions. Everything else — implementation, research, briefs, review, bulk reading — goes to Sonnet 5. Opus 5 stays on the roster as the escalation lane (security reviews pinned there), and the close gets its fresh-eyes verification from Fable 5 itself — one bounded call per workflow.
The division of labor
┌─────────────────────────────────┐
│ FABLE 5 — chair │
│ plan · arbitrate · decide │
└────────────────┬────────────────┘
│
specs & ledger down │ briefs & verdicts up
│
┌─────────────────────────────┼─────────────────────────────┐
▼ ▼ ▼
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ SONNET 5 · max │ │ SONNET 5 · max │ │ SONNET 5 · max │
│ mechanical bulk │ │ implementation │ │ routine judgment │
│ grep·fetch·scan │ │ code · tests │ │ briefs · review │
│ format · read │ │ debug · refactor │ │ filtering │
└─────────────────────┘ └─────────────────────┘ └──────────┬──────────┘
│ uncertain /
│ high stakes
┌──────────▼──────────┐
│ the valve │
│ verify: FABLE 5 │
│ escalate: OPUS 5 │
│ (→ fable ceiling) │
└─────────────────────┘
Fable thinks. Sonnet does. Fable checks the close. Your limit pays for the thinking plus one verification per close:
┌─────────────────────────────────────────┬─────────────────┬─────────────────────┐
│ Work │ Runs on │ Fable limit pays │
├─────────────────────────────────────────┼─────────────────┼─────────────────────┤
│ Phase planning, arbitration, decisions │ Fable 5 (chair) │ yes │
│ Implementation, tests, refactors │ Sonnet 5 max │ nothing │
│ Source briefs, filtering, code review │ Sonnet 5 max │ nothing │
│ Bulk gathering (fetch, grep, scan) │ Sonnet 5 max │ nothing │
│ Fresh-eyes verification (the close) │ Fable 5 max │ one call per close │
│ Escalations (hard judgment) │ Opus → Fable │ mostly nothing │
│ Security / adversarial review │ Opus (not Fable)│ nothing │
└─────────────────────────────────────────┴─────────────────┴─────────────────────┘
Why Fable 5 × Sonnet 5 is the right couple
- Fable tokens are the heaviest draw on your limit. Every token of bulk work kept off the chair extends how long Fable stays in it.
- Sonnet 5 closed the gap. Near-Opus quality on coding and agentic work, with the full effort ladder (
low→xhigh→max). Atmaxit carries the routine judgment — briefs, filtering, standard review — that used to need an Opus call. - The valve is two-tier. Fresh-eyes verification of the close runs on Fable 5 — the strongest model at the single highest-stakes moment, once per close. Escalations climb sonnet → opus → fable, with security reviews kept off Fable, whose classifiers decline benign security work most readily. Any tier can still decline it — the profile's rule is to rerun the refused task unchanged on another tier and, if that tier declines too, stop and tell you, never to reword the request past a classifier. A worker that returns "uncertain" never bounces back to the chair.
What the plugin does
Three layers, all mechanical — no CLAUDE.md editing, no manual routing.
1 · The Fable profile
A SessionStart hook injects the Fable-in-chair profile (instructions/dynamic-workflow-fable.md) into every session — auto-detected per session start, nothing to configure:
┌──────────────────────────┬──────────────────────────────┐
│ Scarce resource │ your usage limit │
│ Bounded / medium work │ delegated │
│ Requirements Ledger │ file, before any delegation │
│ Verification │ fresh-eyes on every close │
│ Disk hand-off │ the default │
│ Spawn-guard threshold │ 1500 chars │
│ Task-list gate │ 3rd task needs the ledger │
└──────────────────────────┴──────────────────────────────┘
The profile routes subagents by tier name (sonnet, opus, fable), keeps bulk material on disk (./.workflow/scratch/ — the chair receives briefs and verdicts, never dumps), and runs every delegated agent at max effort — savings come from the tier, never from dialing effort down. Context-heavy follow-ups go to a fork (subagent_type: "fork"), which inherits the full conversation with no spec-writing tax.
When the Fable limit runs dry, move the chair to Opus (/model): the injector serves the matching OPUS profile (instructions/dynamic-workflow-opus.md) — same discipline, the fable tier rests, fresh-eyes verification and the escalation ceiling fall to a fresh Opus agent. Opus is a drop-in chair: it orchestrates exactly as Fable did, only Opus now sits in the seat.
Detection runs at each session start, in priority order: an explicit FABLE_ORCH_PROFILE=fable|opus pin, then the SessionStart payload's model, then the default model /model wrote to settings.json (so an Opus default is honored even when the harness omits the payload model on a resume/compact — the case that used to fall back to Fable), then the last model this session saw. A mid-session /model switch still only takes effect at the next session start (SessionStart is the only injection point) — but the settings fallback makes that next start reliable. To pin the chair regardless of detection, set FABLE_ORCH_PROFILE=opus while you ride out the Fable limit, fable (or unset) when it resets.
2 · A Requirements Ledger
Before serious delegation the chair writes every requirement, constraint, and edge case as one checkbox line in ./.workflow/LEDGER.md — or LEDGER-<topic>.md beside it when one project runs several. The hooks watch every LEDGER*.md in that directory, newest first; a name ending in -archive.md is retired and silences the close guard for good, so a live LEDGER-archive-migration.md still counts. Files survive context compaction; conversation context does not.
- [ ] 1. Every explicit requirement, one line each
- [ ] 2. Implicit expectations and constraints too
- [x] 3. Marked done only after verification confirms it
- [~] 4. deferred: user approved postponing this
3 · Guard hooks
Instructions are advice; hooks are mechanism. The failure points that get skipped under pressure are fenced:
Spawn guard (PreToolUse on Agent|Task|Workflow) — gates the spawn prompt, or the Workflow script:
spawn (Agent / Task / Workflow)
│
├─ text ≤ threshold (default 1500) .............. PASS (short spawns are never taxed)
│
└─ text > threshold
│
├─ subagent_type == "fork" ................. PASS (forks already see the ledger)
│
├─ ACTIVE .workflow/LEDGER.md found ........ PASS (cite its items per agent)
│ (cwd → repo root / $HOME)
│
└─ no ledger — or only a stale one ......... DENY → "write the ledger first;
small single-phase → do directly"
A ledger is stale when every item is closed AND it was last touched before this session started — last week's finished ledger doesn't disarm the gates for a new task. Open items, or any touch during this session, keep it active.
Task guard (PreToolUse on TaskCreate) — the solo path the spawn guard can't see. A session that never spawns agents never meets the spawn guard; it just quietly implements a six-phase plan solo on the most expensive model (measured in the wild). The tracker tasks it creates for itself are the tell:
TaskCreate (tracker task)
│
├─ ACTIVE .workflow/LEDGER.md found .............. PASS
├─ fewer than 3 ledgerless tasks this session .... PASS (small task lists are fine)
│
└─ 3rd ledgerless task ........................... DENY once → "multi-phase work:
write the ledger, delegate the
phases to workers" — then quiet
Close guard (Stop) — chair only; a named teammate's close is never held on the chair's ledger (FABLE_ORCH_TEAMMATE_STOP=1 restores the old behaviour):
turn ends
│
├─ no ledger on the search path (cwd → repo root / $HOME) ... pass
├─ every item "- [x]" or "- [~] deferred" ................... pass
├─ ledger untouched by this session ......................... pass
├─ this session already got its reminder .................... pass
│
└─ open items, touched here, first time ..................... BLOCK once,
listing the items: finish them, defer with user approval, or
acknowledge in one line and move on — one reminder per session.
Archive paused ledgers (LEDGER-<topic>-archive.md) to silence for
good; LEDGER_GUARD_STOP_MODE=every-turn restores per-turn blocking.
A fourth hook (SessionEnd) cleans up after the session: its temp files and its tmux teammates. The agent-teams backend parks teammates in tmux panes and never reaps them (measured in the wild: 63 orphaned agents holding ~5 GB; later, 9 panes parked for 11-30 hours) — on current Claude Code those panes sit inside your own default tmux server, on older versions in dedicated claude-swarm-* servers. The hook kills the session's own teammates wherever they live: the legacy claude-swarm-<pid> server whole (matched via the hook's nearest-claude ancestor or the @session-<id> pane tag), and on shared servers only the PANES carrying this session's --parent-session-id — a non-swarm server itself is never killed. Swarm servers idle 48h+ are swept too. Finished teammates don't wait for a SessionEnd that may be days away: a rate-limited sweep piggybacked on the Stop hook samples every teammate pane's CPU and kills panes idling below ~1% CPU for FABLE_ORCH_TEAMMATE_IDLE_H hours (default 1). A parked teammate still burns a mailbox-polling heartbeat, so idleness is a sustained low RATE, not a frozen clock — working siblings re-baseline and survive. The injected profile adds the front line: the chair dismisses a teammate (shutdown_request) the moment its final report is accepted.
Watching the team live
Teammates are real claude processes in tmux panes — you can watch every agent think, call tools, and type in real time. Current Claude Code opens the panes inside your own default tmux server: if you launched claude from inside tmux, the team appears as extra panes right in your window (prefix q jumps between panes, prefix z zooms one to full screen, prefix w shows a session/window tree).
# who is on the field, by name
ps -axo pid=,command= | grep -- --agent-id
# every pane, mapped: session, pane id, pid, what it runs
tmux list-panes -a -F '#{session_name} #{pane_id} #{pane_pid} #{pane_current_command}'
# older Claude Code parked teams in dedicated servers — attach read-only
ls /tmp/tmux-$UID | grep claude-swarm
tmux -S /tmp/tmux-$UID/claude-swarm-<pid> attach -r
Watch, don't type: a teammate's pane is its working terminal, and stray input interferes with it — talk to agents through the lead session instead. The reaper keeps this view honest: dismissed and idle teammates disappear from the list instead of stacking up.
Install
/plugin marketplace add Rylaa/fable5-orchestrator
/plugin install orchestrator@fable-orchestrator
Requires python3 on PATH; macOS and Linux only — the hooks also shell out to tmux for teammate reaping, and Windows is not supported. No configuration needed.
Manual install (without the plugin system)
- Copy
scripts/ledger_guard_spawn.py,scripts/ledger_guard_stop.py, andscripts/cleanup_session_cache.pyto~/.claude/hooks/. - Merge this into
~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "^(Agent|Task|Workflow|TaskCreate)$",
"hooks": [
{ "type": "command", "command": "python3 ~/.claude/hooks/ledger_guard_spawn.py", "timeout": 10 }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "python3 ~/.claude/hooks/ledger_guard_stop.py", "timeout": 10 }
]
}
],
"SessionEnd": [
{
"hooks": [
{ "type": "command", "command": "python3 ~/.claude/hooks/cleanup_session_cache.py", "timeout": 20 }
]
}
]
}
}
- Append
instructions/dynamic-workflow-fable.mdto~/.claude/CLAUDE.md. - Without the SessionStart injector there is no per-session
startedmarker, so the stop guard can't tell another session's ledger from yours (every open ledger costs one reminder per session instead of zero) and the spawn/task gates can't ignore stale fully-closed ledgers.
Don't run the plugin AND the manual install side by side — you'd get every guard twice.
Configuration
Set these in ~/.claude/settings.json under "env".
┌───────────────────────────────┬────────────────────┬────────────────────────────────────────────┐
│ Env var │ Default │ Meaning │
├───────────────────────────────┼────────────────────┼────────────────────────────────────────────┤
│ LEDGER_GUARD_THRESHOLD │ 1500 │ spawn-guard gate (chars) │
│ FABLE_ORCH_PROFILE │ auto │ pin the chair profile: auto | fable | opus │
│ FABLE_ORCH_TEAMMATE_STOP │ (off) │ 1 lets the close guard hold teammates too │
│ LEDGER_GUARD_TASKS │ 3 │ 3rd ledgerless tracker task denied; 0 off │
│ LEDGER_GUARD_STOP_MODE │ once-per-session │ every-turn restores per-turn blocking │
│ FABLE_ORCH_METRICS │ (on) │ 0 disables local metrics logging │
│ FABLE_ORCH_SWARM_CLEANUP │ (on) │ 0 disables all teammate reaping │
│ FABLE_ORCH_SWARM_MAX_IDLE_H │ 48 │ sweep swarms idle ≥ N hours; 0 disables │
│ FABLE_ORCH_TEAMMATE_IDLE_H │ 1 │ kill teammate panes idle ≥ N hours; 0 off │
│ FABLE_ORCH_TEAMMATE_IDLE_RATE │ 0.01 │ cpu-sec/sec under which a pane is idle │
└───────────────────────────────┴────────────────────┴────────────────────────────────────────────┘
The session marker. The SessionStart injector writes a per-session temp file whose immutable started timestamp survives resume/clear/compact re-injections — the stop guard compares ledger mtimes against it to decide ownership, and the SessionEnd reaper anchors its cleanup to it. The SessionEnd hook removes the session's temp files and sweeps any older than 96 hours.
Metrics. Every hook appends one event line to ~/.claude/fable-orch/metrics.jsonl (events only — never prompt content): injections per model, spawn/task denies and passes, stop blocks and suppressions, reaps. python3 scripts/stats.py prints the summary, so the next "how is this performing?" question is answered with data. Disable with FABLE_ORCH_METRICS=0.
Tests
python3 -m pytest tests/ -q
The hooks are plain stdin/stdout JSON filters; the tests run them end-to-end as subprocesses — the spawn threshold and its env override, the fork exemption, Workflow script gating, the task-list gate (counting, one deny per session, session isolation), the upward ledger search and its repo-root/worktree/$HOME boundaries, stop-guard session scoping and ownership, metrics emission and opt-out, injection, cache cleanup, and teammate reaping (against a fake tmux/ps on PATH).
Honest limitations
- Hooks check ledger existence and checkbox state, not fidelity — a shallow ledger passes; writing a faithful one stays a judgment task.
- Freshness is only half-checked: a fully-closed ledger from a previous session re-arms the gates, but a stale ledger with OPEN items still satisfies them (it looks like active work).
- Marking
- [x]without actually verifying is possible; mechanizing further would invite ritual compliance. - Chair detection knows two chairs: Fable (primary) and Opus (limit fallback). Any other session model gets the Fable profile, and the 1500-char spawn gate applies everywhere. A mid-session
/modelswitch only re-profiles at the next session start —FABLE_ORCH_PROFILE=opuspins it immediately for the next fires. - Enforcement is only as strong as the host's hook pipeline — on at least one experimental spawn backend we observed an async
Agentlaunch proceed despite the guard's deny; verify once on your setup. - Pane idleness is a CPU-rate heuristic: a teammate parked below ~1% CPU for the idle window is reaped, so one blocked for hours inside a single quiet external wait (a long remote build) can be killed mid-wait — raise
FABLE_ORCH_TEAMMATE_IDLE_H, lowerFABLE_ORCH_TEAMMATE_IDLE_RATE, or disable withFABLE_ORCH_TEAMMATE_IDLE_H=0for such workloads. A reaped teammate can no longer be resumed with SendMessage. - The task guard counts tracker tasks, not work size: a solo multi-phase session that never creates tasks still slips through, and the deny is a single nudge per session, not a wall.
- The orchestration discipline itself is prompt-level; the hooks fence exactly three failure points — the ones that get skipped the most.
Why these guards
Details die entering the workflow (task→plan translation, fenced by the spawn guard) and leaving it (closing with silently unaddressed items, fenced by the close guard). The third measured failure is the workflow never starting at all: the chair quietly implementing a multi-phase plan solo on the most expensive model (fenced by the task guard). Everything between is judgment — and judgment belongs to the model, not to a regex.
License
MIT
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found