Immutable. This exact content is served forever at /api/v1/blob/990e0a993b6e9e24.
---
name: coaltipple
description: >-
Model/effort router, ALWAYS-ON via its conductor hook: every prompt is routed, and routing returns exactly one of THREE outcomes — SELF (do it yourself at a pinned tier/effort; the routine result — routing that lands on self is still routing), delegate-DOWN (a large cheap mechanical task goes to a cheaper tier to save tokens), or escalate-UP (a task beyond the current model's competence goes to a stronger tier for quality). Sensitive/security work is NEVER delegated down, whatever its size. Invoke manually when weighing where a task should run. Claude Code only -- the platform's spawn tool must accept a worker model parameter (Antigravity etc. cannot actuate this); support reviewed monthly.
---
# CoalTipple — routing contract
**THE FRAME — ALWAYS-ON, THREE OUTCOMES.** This contract applies to EVERY prompt (the conductor injects a routing forcer each turn; there is no trigger condition to meet): every task is ROUTED, and routing returns exactly one of three outcomes — **SELF** (do it yourself, tier pinned + effort tuned — the most common result), **delegate-DOWN**, or **escalate-UP** — UNLESS a structural off-state is already declared: the platform gate below, `enableRouting:false`, `mode:'off'`, a matched `disableRouting` domain, or a ranking that genuinely cannot be built (F8) — exactly five states, never a sixth you invent. Those are DECLARATIONS the pipeline reaches or the config/platform sets, not a decision you make per task. **A declared off-state stands down the WHOLE contract, not only the 3-outcome decision — no grade, no Asks, no P1–P17, no Step 2 HARD GATE, no P7 verify-obligation: do the task as a plain agent.** **"The skill doesn't apply" is never a decision YOU make because a task looks small or out of scope:** a task too small to delegate still runs the FULL pipeline — Step 1's grade AND Step 2's sensitive HARD GATE (the gate is grade-INDEPENDENT, checked whatever the grade says, never grade-then-gate) — before landing on SELF. Skipping the pipeline because a task looks small is exactly how a small-LOOKING task on a sensitive path slips past the never-down gate.
> [!CAUTION]
> **PLATFORM GATE -- actuates on Claude Code ONLY.** Routing spawns a worker at a CHOSEN model + effort, which needs a spawn tool that accepts a worker **model** parameter. **Works: Claude Code** (`Agent`/`Task` takes `model`). **BROKEN: Antigravity** -- `invoke_subagent`/`define_subagent` expose no model param + no effort knob (low/mid/high are model *names*), so a worker silently runs the parent's model. On any platform without an agent-selectable worker model, **do NOT pretend to delegate-down or escalate-up** -- the spawn cannot change tier; routing is a silent no-op. Not on Claude Code -> routing is OFF, one of THE FRAME's five declared structural off-states, never a per-task decision — the skill still degrades safely. (Reviewed monthly.)
> **Degrades safe on any CC version:** an unknown model classifies as a strong tier (never cheap), a failed spawn falls to the next available, and the platform resolves each alias to its current best model at spawn-time — a CC update never breaks routing.
You are **main** (depth-0): you decide routing + spawn everything yourself. **Workers are leaves BY POLICY — give a worker a bounded task-contract (its done-criteria make it RETURN, never open-endedly spawn its own workers); a worker that fails RETURNS its result for main to re-route.** This holds whether or not the platform caps nesting — robustness rides on bounded task-contracts + the spawn-fail-fall (Step 3), never on the platform forbidding a worker to spawn.
> This contract is English so any model can read it; user-facing output follows the user's language (translate prose, technical terms verbatim — see **Always**).
## The ledgers — the countable rails (TRANSCRIBE these; never re-count from prose)
Six lists own the COUNT + MEMBERSHIP of the asks, the absolute prohibitions, the fail routes, the on-disk files, the config keys, and when each reference loads. The Steps carry the execution mechanics; every "how many / which ones" question is answered by these lists verbatim.
### Asks — exactly 3, numbered (+ ONE standing offer)
- **ASK 1 — FABLE (real money), conditional (Step 2).** Fires when the RESOLVED worker is a fable-family model (`isFableModel` — a substring match catching the alias `fable` AND a pinned concrete id like `claude-fable-5`) AND `fableConsent` is not already `true`. Options: **once** (this spawn only) · **always** (persist `fableConsent: true` to the project config) · **no** (do not climb into fable — F7 caps the route at the rung below). One worker, one ask, no cost math. Never fires when routing lands on SELF or a non-fable tier.
- **ASK 2 — FAN-OUT (more than one worker in one move — the ULTRACODE rung, or any parallel delegation; Step 2).** Before spawning SEVERAL workers at once, state the plan (how many × tier × effort, capped at `maxConcurrentSubagents`) and get a yes. A single worker is NOT this ask.
- **ASK 3 — MEMORY ANCHOR (at most once per session; Memory anchor section).** Fires only when ALL hold: about to delegate context-DEPENDENT work · no anchor exists (`contextFiles` empty AND no platform `CLAUDE.md`/`AGENTS.md` the worker would inherit) · `memoryOffer` is `auto` · not yet offered this session. Options: **Create** / **Choose** / **Skip** (Skip persists `memoryOffer: 'off'` — never re-asks).
- **OFFER — SELF ERROR-REPORT (an offer, not a consent gate):** when CoalTipple itself misbehaves → STOP, summarize, OFFER to file an issue (P16; Self error-report section).
- **NOT asks (add nothing to the count):** a SINGLE non-fable worker spawn — standing consent is your `mode` config, and its transparency is the visible `[model·effort]` spawn label (Step 3.2), not a question · the qualityBar climb (automatic, bounded by `maxTotalAttempts`) · the self-update ask/reminder (the conductor's SessionStart channel, governed by `updateMode` — never fired by this file).
### Absolute prohibitions — P1–P17 (the complete set)
A "never" inside a Step and not listed here is that step's sequencing mechanics, not an additional absolute.
- **P1 — Sensitive work is NEVER delegated DOWN** — whatever the grade, under every `mode`, and under a quota/limit-hit too (wait for reset / stay on a capable main / hand back — never fall cheaper; a vetted built-in beats hand-rolling). Sensitivity is a property of the PATH + the task's MEANING, never of the edit type — a "comments-only" / "just a typo" edit on an auth/crypto/payment path is still sensitive. The gate forbids DOWN only: SELF stays allowed (Step 2 HARD GATE).
- **P2 — Never read "no keyword flag fired" as "not sensitive"** — the flags are English-only; the gate fires on MEANING in ANY language, and YOU are the sensitive-gate authority on a non-English task (Step 2).
- **P3 — A `modelTiers` pin never lowers the sensitive floor** — a KNOWN-weaker alias pinned into a high tier does not satisfy the gate (a pin proves availability, never capability); only an UNKNOWN, post-cutoff pin is trusted there (Step 2).
- **P4 — Never spawn a fable-family worker without consent (ASK 1)**; on "no", never climb into fable for this task, block the RESOLVED model name (not merely the literal `fable`), and never hardcode `opus` as the cap — the cap is the rung below fable, READ from the ranking (Step 2 · F7).
- **P5 — Never route on a broken ranking, and never enumerate the live model list** — the ranking is the alias floor + pins; genuinely unbuildable → routing OFF, do it yourself (Step 0 · F8).
- **P6 — Workers are LEAVES: never spawn an open-ended worker** — a bounded task-contract's done-criteria makes it RETURN, and a worker never spawns its own workers (Step 3).
- **P7 — Never eyeball a verify — RUN the objective check, and never let a worker grade its own output** (Step 3.4; binds a SELF result too — Step 2).
- **P8 — Never let a cheap main QA an escalated expert's work** — the strong worker owns the hard task AND its integration (Step 3.5).
- **P9 — Never delegate the final user-facing deliverable (translation / summary / research prose) to a cheaper tier** while `preserveVoiceForUserFacing` is true — bulk MECHANICAL work (renames, formatting, codegen) stays delegable (Step 2).
- **P10 — Never escalate/climb on high-by-SIZE-alone mechanical bulk** (size is the EFFORT knob, difficulty is the TIER knob) — EXCEPT a whole-repo audit / bug-scan / security-review, which is capability-driven breadth: never delegate THAT down-to-cheap, and if YOU are the floor tier never SELF it — escalate UP (Step 1).
- **P11 — fast-mode is never a routing rung** — attach it only on an explicit human latency request, and only if `fastModeOnLatencyRequest` (Step 2).
- **P12 — Never fan out several workers near a budget/quota edge for marginal, context-light mechanical work** the main can do inline — a worker that dies on the limit returns NOTHING (Step 2 budget gate · F9).
- **P13 — With a stakes signal present or CoalBoard convening this turn, never escalate independently and never double-prompt** — CB leads, CT becomes its tier-lever; no CoalBoard installed / not fired this turn → this rule is INERT (Step 2).
- **P14 — On a platform whose spawn tool takes no worker-model parameter, never pretend to route** (the platform gate above).
- **P15 — A side-effecting step (bash mutation, external call, commit) is never delegated and never retried** — retry = doing it twice (`references/damage-control.md`).
- **P16 — A self error-report is OFFERED, never auto-submitted** — and never includes unapproved code or paths (Self error-report section).
- **P17 — Never clobber an existing memory-anchor file** — load + append/crystallize only (`references/damage-control.md`, write-policy).
### Fail routes — F1–F11 (the complete set)
A recovery move inside a Step or a reference and not listed here is that step's mechanics, not an additional route.
- **F1** — a spawn ERRORS instantly (model unavailable / disabled / out of quota / gone) → add that model to `blocked` → `resolveWorker(ranking, desiredTier, {blocked, floorTier})` → spawn the returned worker → repeat on each fresh error (Step 3.3).
- **F2** — `resolveWorker` returns `null` (everything blocked down to the floor) → STOP: a SENSITIVE task hands back or waits for reset — never below its floor (P1); a non-sensitive task → do it yourself if you can clear the bar, else hand back honestly (Step 3.3 · damage-control).
- **F3** — verify lands BELOW the bar but near it, attempts remain → climb ONE rung, cheapest lever first (EFFORT → VERSION → TIER; never re-try the same model) (Step 3.4; binds a SELF result too — Step 2).
- **F4** — verify FAR below the bar, or out of `maxTotalAttempts` → jump straight to the top tier; at the top and still failing → hand back to the user (Step 3.4; binds a SELF result too — Step 2).
- **F5** — the failure mode is un-verifiable (crypto timing, numerical precision, security — plausible-but-wrong PASSES your check) → JUMP to the reliable tier, never climb through plausible-wrong rungs (Step 2).
- **F6** — a worker silent past `subagentTimeoutSeconds` → treat it as failed and re-route (damage-control).
- **F7** — fable consent-declined ("no") → do NOT climb into fable: STAY at the current tier (a climb case), or land on the rung below fable (a grade-5 `reasoning` start — the same `resolveWorker` fall, blocking the RESOLVED name); the cap holds even if `qualityBar` is unmet. An UNAVAILABLE fable is not a decline — it just falls (F1), no ask (Step 2).
- **F8** — ranking missing / corrupt / incomplete → rebuild on the spot per `references/lock.md`; genuinely unbuildable → routing OFF, work normally (Step 0).
- **F9** — near a session/quota limit BEFORE spawning → collapse to inline-self (the proactive budget gate; `resolveWorker` is the reactive fall once a spawn is blocked) (Step 2).
- **F10** — a delegation dies mid-run → the worker's proposal is in `proposed/`, finished subtasks in `state.json` → resume the remainder, never redo done work (damage-control).
- **F11** — you KNOW from the start the task exceeds your ceiling → escalate immediately, don't burn an attempt (Step 3.4).
### Files — the on-disk set (a near-twin pair, declared once)
A dot-FILE is CONFIG, a dot-DIR is STATE — the pair exists at both levels; do not read one as a typo of the other. This is the complete set a ROUTE touches. The conductor's own update-check stamp (`~/.claude/coal/coaltipple/update-check`) belongs to the SessionStart self-update channel, not to this contract.
- **Config (read on every route):** global `~/.claude/.coaltipple.json` (unchanged) overlaid by the FIRST-FOUND project config — `<gitroot>/.claude/coal/coaltipple.json` → `.agents/coal/coaltipple.json` → `.gemini/coal/coaltipple.json` → LEGACY `<gitroot>/.claude/.coaltipple.json` — every bare "`.coaltipple.json`" in this file means this MERGED pair (merge rules in **Always**). **`<gitroot>` = the nearest ancestor holding `.git`; no `.git` anywhere above → `<gitroot>` is just the starting directory itself** (`findGitRoot`, `config-load.mjs`).
- **State:** `~/.claude/coal/coaltipple/` (global dir) holds `ranking.json` — READ at spawn-time only (a SELF route never reads it), WRITTEN only on a rare rebuild, atomically (F8) · `<gitroot>/.claude/.coaltipple/` (project dir) holds `proposed/` (the delegation sandbox workers write proposals into) + `state.json` (the subtask journal) — written only during a delegation (F10).
- **Writes driven by asks:** ASK 1's "always" persists `fableConsent: true` into the PROJECT config FILE (the resolved path per the Config bullet's read-order above — edit the key directly; `scripts/` ships only with the repo source, not the installed plugin, so `node scripts/configure.mjs --project --fableConsent true` is a convenience for a source checkout, never the only path) · ASK 3 may create/choose a memory-anchor file (the user's own file — append, never clobber, P17) and persists `contextFiles` / `memoryOffer`.
- **A SELF route writes NO CoalTipple artifact** — the edit lands in the user's files directly; `proposed/` is the DELEGATION recovery net, not self's (self recovery = your platform's normal undo / git).
### Config — the knob set (defaults; full reference = README Configure / `config-schema.mjs`) — all 24 config keys in 23 rows (`updateMode`·`updateCheckDays` share one).
| key | default | gates |
|---|---|---|
| `enableRouting` | true | master switch (false = routing + conductor silent) |
| `mode` | auto | allowed DIRECTIONS: `delegation` = down-only · `escalation` = up-only · `off`; P1 overrides every mode (Step 2) |
| `qualityBar` | 60 | the acceptance bar a result must clear (Steps 1 · 3) |
| `delegateMinLines` | 120 | the delegate-down floor (Step 2) |
| `maxTotalAttempts` | 2 | staircase budget before jump-to-top (F4) |
| `subagentTimeoutSeconds` | 150 | stall reap (F6) |
| `maxConcurrentSubagents` | 4 | fan-out cap (ASK 2) |
| `requireTaskContract` | true | the Step 3.1 contract |
| `qaOnMerge` | standard | Step 3.4 verify rigor (strict / standard / off) |
| `fastModeOnLatencyRequest` | true | P11 |
| `preserveVoiceForUserFacing` | true | P9 |
| `keywords` | the factory groups | grade floors + `sensitive` / `preserveVoice` flags (Step 1 lists the factory numbers) |
| `hotKeywords` | [] | legacy flat list — merges as a grade-4 sensitive group |
| `sensitivePaths` | auth · crypto · payment · billing · migration · secret · token · password · session · security | path fragments forcing grade ≥4 + sensitive; a config UNIONS with these, never replaces them |
| `excludePaths` | node_modules · .git · dist · vendor · build · .next · coverage | dirs never counted toward grading breadth |
| `disableRouting` | [] | per-domain routing OFF: `coding` / `text` / `math` / `research`, or `'all'` (Step 2) |
| `contextFiles` | [] | the memory anchor (ASK 3) |
| `memoryOffer` | auto | the anchor offer switch (ASK 3) |
| `fableConsent` | false = ask | ASK 1's standing consent (project-persisted) |
| `modelTiers` | {} | human pins over the alias floor (Step 0; P3) |
| `language` | auto | the user-facing output language (Always) |
| `updateMode` · `updateCheckDays` | ask · 14 | the conductor's self-update channel (SessionStart — not a rail of this file) |
| `gitRecoveryBoundary` | auto | git commits as an extra recovery boundary (damage-control) |
### References — when each loads — these two are the complete reference set.
| reference | when |
|---|---|
| `references/lock.md` | on-demand — ONLY to REBUILD a missing/corrupt/incomplete ranking (F8), or for pin/fall/safety depth; a normal route never opens it |
| `references/damage-control.md` | MANDATORY at its moments — a delegation failing mid-run (F2 detail · F6 · F10) · the memory-anchor flow (ASK 3) · a self error-report (the OFFER) |
## Grants & denials (CLASSIFY-BLOCK — declared)
| class | step it powers | grant | on denial |
|---|---|---|---|
| read | the merged config, the ranking, memory-anchor files, `state.json`/`proposed/` (F10 resume) | `Read` | an unreadable file is NOT the same as an absent one — never fall back to schema defaults silently: an unbuildable ranking → routing OFF (F8); an unreadable config → treat as `mode:'off'` (one of THE FRAME's five declared states, never a sixth); an unreadable journal → say the F10 resume net is not active this run, before delegating |
| write | ASK 1's `fableConsent` persist, ASK 3's memory-anchor file, the delegation journal (`proposed/`, `state.json`) | `Write`/`Edit` | report the write failed, never claim it applied — a failed "always" persist means you WILL be asked again next spawn, say why (never let a silent re-ask read as a config bug); a failed journal write means F10's resume net does not exist this run |
| spawn | delegate-DOWN / escalate-UP (Step 3) | `Agent`/`Task` (or `Bash` for a `claude -p` dispatch — its OWN grant) | **a refused GRANT is not F1's unavailable-MODEL fall** — say the delegation could not run and why, before landing on SELF; a silent SELF here is byte-identical to the routine SELF outcome and hides that routing never happened |
A denial reaches the WORKER as a visible message and propagates NO further — not to the dispatcher, not as a catchable condition. Every row above states a branch or an explicit death; a step that dies says so in the output. Never report a denied step as done, skipped, or clean. No network row — the routing pipeline fetches nothing (the self-update check is the conductor's SessionStart channel, not this contract — Asks ledger, NOT-asks bullet). Grant denials are a SEPARATE mechanism from the F1–F11 fail-routes and P1–P17 prohibitions above (permission-level, not a routing decision) — both ledgers' counts are unchanged.
## Step 0 — the Lock (a valid ranking gates any SPAWN)
Routing that will SPAWN needs a tier ranking — **the ranking is READ at spawn-time (Step 3), never per prompt: a SELF route reads nothing, and an idle session pays nothing.** It is **dead simple — no introspection, no model-list enumeration, no refresh cadence:**
- **The ranking IS the alias floor + your pins.** Tiers = the Claude aliases `haiku < sonnet < opus < fable` → `low / mid / heavy / reasoning` (**fable = the top rung above opus — a real-money spawn, consent-gated before use; ASK 1, Step 2**), overlaid with your `modelTiers` pins (merged config). The platform resolves each alias to its current best model AT SPAWN-TIME, so the floor never goes stale. When a route will spawn, read the GLOBAL shared ranking `~/.claude/coal/coaltipple/ranking.json` (a cheap file read). **You do NOT enumerate the live model list.**
- **Unknown model → `heavy`, never cheap.** Any model you do not recognize from the floor/pins is a STRONG tier (the safe over-provision). Routing keys off this tier STRUCTURE, not an exact list — a vendor 5→10-model shuffle never breaks it.
- **`modelTiers` pins are the one human override** for a model released after your cutoff (you cannot see it): the user names it in the merged config and it wins front-of-tier.
- **A failed spawn FALLS** (`resolveWorker`, Step 3 · F1): availability is knowable ONLY at spawn-time, never from a catalog.
- **If a ranking genuinely cannot be built → routing OFF: just work normally** (F8). The skill never *breaks* — it routes, or routing is off.
→ Rebuild recipe (only when ranking.json is missing / corrupt / incomplete — rare), the pin/fall mechanics, and why every degradation mode is safe: **read `references/lock.md`.** A normal route just reads the file; it never rebuilds.
## Step 1 — Grade the task (deterministic, not self-assessment)
Grade 1–5 by the rubric — **size + sensitive-path + keyword**, never "it feels easy" (a cheap model is overconfident):
| Grade | Signal |
|---|---|
| 1 | read / search / summarize — **the no-signal base: no file edited, no content produced (`sizeUnits`), no keyword match** |
| 2 | **editing even ONE file floors the grade here at minimum** · ≤1 file, <500 lines, not sensitive |
| 3 | 2–3 files / 500–1000 lines |
| 4 | >3 files / ≥1000 lines / **sensitive path (auth, crypto, payments, migrations) — forces ≥4 even at 1 file** |
| 5 | concurrency / mutex / race / crypto-logic / timing-attack / proof — **forces 5 regardless of size** |
**Every signal is a FLOOR and the grade is the HIGHEST floor fired — deterministic, no tiebreak judgment** (the rubric composes size + sensitive-path + keyword floors with max, as `grade.mjs` does): a 1-file edit (floor 2) whose prompt matches a grade-4 group grades 4 — the per-grade floor conditions are the table above, not restated here.
**Size = content size (general-purpose):** code counts lines/files · text/translation/research/docs count words/chars (same thresholds). **Difficulty (keyword/sensitive) ALWAYS overrides size** — a tiny-but-hard task (1 line of crypto, a short legal-contract translation) = high grade.
**Keyword groups are config-tunable (`keywords`, merged config) — honor them.** The FACTORY groups and their grade floors (the shipped values; a config overrides PER GROUP — it may add words or RAISE a built-in group's grade, never lower it below the factory floor, and never strip a built-in `sensitive`/`preserveVoice` flag): **`concurrency` 5 · `crypto` 5 (sensitive) · `security` 4 (sensitive) · `coding` 4 (sensitive — its words are the migration / schema-change / payment / billing / rate-limit class, NOT "any code": a group fires on its WORDS/intent, never on the task's general domain, so an ordinary code edit matches nothing) · `audit` 4 · `math` 5 · `knowledge` 3 · `domain` 4 (sensitive) · `creative` 2 (preserveVoice)**; legacy `hotKeywords` merges as a grade-4 sensitive group. A prompt matching a group floors the grade to the group's number and inherits its flags; an omitted group keeps the built-in. The conductor's 0-token hint reflects only the built-ins, so YOUR read of the merged config is the authority. **Match a group by the task's INTENT in ANY language** — a bug-scan / audit / security-review matches `audit` by MEANING, not a literal English word (the English `words` are only examples + the conductor hint).
**Read WHY the grade is high — it sets the DIRECTION (the grader's `reasons` tell you which):**
- **High by DIFFICULTY** (sensitive path / hot keyword) → escalate-**UP**: you need a stronger tier for correctness.
- **High by SIZE alone** (`reasons` show only `size: N`, no sensitive/keyword — a 40-file rename, 48 CRUD handlers, bulk codegen/translation) → a high-**EFFORT** signal, NOT high-tier → **delegate-DOWN to a cheap tier + high effort.** The size threshold inflates the grade to stop you under-routing big *complex* work; once you see the work is MECHANICAL, route it DOWN. Never escalate-up / climb-the-staircase on bulk-mechanical work just because breadth bumped the grade (P10) — bulk is the EFFORT knob, difficulty is the TIER knob.
- **EXCEPTION — a whole-repo audit / bug-scan / security-review / code-review is high-by-DIFFICULTY, NOT size:** it spans many files (looks size-driven) but finding REAL issues needs CAPABILITY — a cheap tier returns a confident shallow all-clear. Route it UP, or keep it on a capable main; NEVER delegate-down-to-cheap, and if YOU are the floor (Haiku) NEVER self — escalate UP (P10). Bug-finding is the TIER knob, not the EFFORT knob.
If the conductor injected a hint, respect it.
**qualityBar (config 0–100) — the acceptable-quality bar:** grade picks the *starting* tier (Step 2); `qualityBar` is the line the result must clear, or routing climbs the ladder (Step 3). Default 60.
- **Plain meaning:** "accept work of quality ≥ qualityBar%". `90` = critical, must be exact · `60` = competent/serviceable (default) · `40` = cheap-and-cheerful.
- **0** = anything passes → stay at the starting tier, never climb. **100** = almost nothing passes below the top → climb to the top (except a task the lower tier already does perfectly — trivial → no climb). The extremes emerge from the climb mechanism; nothing is hardcoded.
## Step 2 — Route (decide)
**HARD GATE, checked before the grade or anything else counts — sensitive = NEVER delegate-DOWN, and this does NOT depend on your grade (the single most important safety rule):** if the task touches anything sensitive/security — crypto, timing-attack, constant-time, auth, authorization, payment, secret, token, password, session, security (the grader + conductor flag these, plus any `keywords` group you marked `sensitive`) — then **delegate-DOWN is FORBIDDEN, whatever number you graded it.** Do NOT gate this on grade ≥4/5: a weaker main UNDER-grades sensitive work and slips past a grade-gated rule. (Observed live: a main graded a hand-rolled constant-time compare *medium* and delegated it DOWN — "known pattern, save tokens"; the under-grade bypassed the safety rule and shipped hand-rolled crypto to a weaker model.) **The keyword is the gate, not your grade** (P1). Sensitivity keys on the PATH + the task's MEANING, never on the edit type — a "comments-only" / "just a typo" edit on an auth/crypto/payment path is still sensitive; DOWN is forbidden, SELF is fine. For sensitive work: escalate UP, keep it on main, or use a vetted built-in (`crypto.timingSafeEqual`) — never delegate-down, never hand-roll security on a cheaper tier (a plausible-but-wrong timing leak PASSES your tests). **"Keep it on main" presumes your OWN current tier already clears the sensitive SAFE FLOOR — `heavy`, fixed regardless of grade** (the fable bullet below: "the cap holds at its safe floor tier `heavy`, never below"). **Placing yourself on that floor is SELF-IDENTIFICATION, not the barred live-model enumeration (P5) — you already know your own name/family; match it against the SAME alias floor Step 0 defines** (`haiku < sonnet < opus < fable`). **If you genuinely cannot place yourself there, don't assume the floor clears — escalate instead**, the same fail-toward-safe posture Step 0 uses for an unrecognized WORKER model. This is **NOT the grade table's per-grade CEILING**, a different and higher number at grade 5 (`strongest`/fable; the two only coincide at grade 4, where both happen to be `heavy`). SELF is the allowed DESTINATION once you clear the FLOOR — it never requires reaching the CEILING. If your own tier sits below `heavy`, staying SELF is not on the table: escalate (spawn UP) at least to `heavy` — same as "forces escalation off the current cheap tier" below (Step 2, tier-ceiling section), the SAME rule seen from underneath, not a second rule. **Holds under a QUOTA / limit-hit too:** if the safe tier is quota-blocked, a sensitive task WAITS for reset, stays on a capable main, or hands back — it does NOT fall to a cheaper tier (the limit-hit fallback floors sensitive work at its safe-minimum — see Damage control). **A `modelTiers` pin cannot lower this floor (P3):** a KNOWN-weaker alias (haiku/sonnet) pinned into a high tier never satisfies the gate — skip it and take the next known-strong model in that tier (`resolveWorker` enforces this on the fall); only an UNKNOWN, unseeable pin (a model released after your training cutoff) is trusted there. (fable is a KNOWN top rung — it qualifies for a sensitive slot by capability, and never-down still holds.)
**Unknown path/meaning (you genuinely can't yet tell if it's sensitive) → ASSUME sensitive until confirmed otherwise:** inspect the file's imports/callers (both directions) or ask the user; a HARD GATE with an unresolved default fails toward the safe side, never toward speed.
**The keyword flags are ENGLISH-ONLY, so on a NON-ENGLISH task YOU are the sensitive-gate authority (P2):** a Thai/CJK/Arabic/Cyrillic prompt — OR a Latin-script one (Spanish/French/German/Portuguese/Indonesian) — meaning "constant-time compare" / "auth bypass" matches NO English literal, so no flag fires and the grade reads trivial; a grade-gated reading would let the never-down backstop vanish. Do NOT rely on the flag for a non-English prompt: **apply the never-down gate by the task's MEANING / intent.** The conductor's resident routing contract reminds you to grade sensitivity by MEANING in ANY language, and it adds a per-turn nudge ONLY when it detects a non-Latin SCRIPT (Thai/CJK/…) — a Latin-script non-English prompt trips neither the keyword flags nor that script nudge, so YOUR read of the intent is the real gate. The gate fires on MEANING; the keyword is only its English fast-path.
**`mode` (config, default `auto`) sets the DIRECTIONS you may route — honor it (the sensitive HARD GATE overrides it):**
- `auto` (default) = route BOTH directions per grade.
- `delegation` = budget mode — delegate-DOWN allowed, escalate-UP SUPPRESSED (miss the bar → do it yourself / hand back, don't climb).
- `escalation` = quality mode — escalate-UP allowed, delegate-DOWN SUPPRESSED (never push work down to save tokens).
- `off` = routing OFF — do it yourself (the conductor also short-circuits its forcer on `mode:"off"`).
- **Override:** a SENSITIVE task obeys the hard gate under ANY mode; escalate-UP for sensitive work is always permitted even in `delegation` mode (safety beats the budget knob).
**Per-domain `disableRouting` (config) — routing OFF for a matched domain:** if the graded task's domain (`coding` / `text` / `math` / `research`) is in `disableRouting`, OR the list includes `'all'`, routing is OFF for THIS task — do it yourself. Map the task to its domain by nature (code→`coding`; translation/docs/prose→`text`; proofs→`math`; citation/claim→`research`). (The conductor only short-circuits on `'all'`; per-domain matching needs the graded domain, so it is YOUR check.)
**Double-hook stand-down (CoalBoard may fire the same turn — P13):** a stakes signal (security/crypto/migration/money) present, or CoalBoard already convening, means do NOT escalate independently — defer to CB: it leads, you become its tier-lever (its lenses run at the tier you'd have picked), one consent, never double-prompt. At the top tier you can't climb further anyway, so yield there too — CB's go-wide is the only escalation left. **No CoalBoard installed / fired this turn → this rule is INERT: the sensitive hard-gate above governs alone (escalate UP as usual — CB is an optional sibling, never assumed).**
Two knobs: **TIER** (coarse, burns Opus quota) × **EFFORT** (fine, cheap, low→max). **Escalate cheapest-first, EFFORT → VERSION → TIER:** raise EFFORT (same model, think/iterate harder), then VERSION (a stronger model of the SAME tier, e.g. Opus 4.6→4.8 — cheaper + closer than a tier jump), then TIER. Exhaust each lever before the next; never skip to a higher TIER while a stronger same-tier version is untried (Opus quota is scarce; once hit it is hard-blocked).
| Grade | Move | Tier ceiling (EFFORT is set by OUTPUT SIZE — the rubric below — never by the grade) |
|---|---|---|
| 1–2 | **delegate-down** *(only if large — see floor)* else do it yourself | low |
| 3 | do it yourself or delegate | mid — exhaust EFFORT, then VERSION, before raising tier |
| 4 | escalate-up *(if difficulty-driven)* / delegate-down *(if size-driven + mechanical; but if YOU are the floor this is SELF, no "down" exists)* | heavy · sensitive → never down |
| 5 | escalate-up / do it yourself | strongest |
- **The tier column = the CEILING, not the START — climb the staircase to it:** escalate ONE rung at a time and verify each (Step 3); a lower rung often clears the bar (cheap tiers are more capable than they look). Reach the ceiling only when a lower rung FAILS verify; **jump straight to the top** on far-below / out-of-attempts (`maxTotalAttempts`) OR when the failure mode is subtle / un-verifiable — crypto (timing leaks), numerical precision, security — where a cheaper tier's plausible-but-wrong output would PASS your check (F5). **Rule: climb when verify is your safety net; JUMP to the reliable tier when verify can't catch the failure.** Don't pre-jump to the top just because the grade is high (that over-provisions TIER). (A sensitive/hard grade still forces escalation OFF the current cheap tier — climb at least one rung; delegating sensitive DOWN is already forbidden above.)
- **Fable = the top rung (reasoning), and it costs real money → ASK 1 before you spawn it:** whenever the RESOLVED worker is a fable-family model — `isFableModel`, a SUBSTRING match catching the alias `fable` AND a pinned concrete id like `claude-fable-5` (a climb/jump to the top, or a grade-5 `reasoning` start) — and `fableConsent` (config) is not already `true`, ASK the user ONCE in their language before spawning — **once** (this spawn only) · **always** (persist `fableConsent: true` into the project config FILE — `node scripts/configure.mjs --project --fableConsent true` is a convenience when working from the repo source, `scripts/` is not part of the installed plugin) · **no**. On **no**, do NOT climb into fable — **stay at the current tier**: the rung below fable (the top NON-fable tier — opus/`heavy` today, but READ it from the ranking, never hardcode `opus`, since a future model landing between opus and fable would become the new cap). Declining the money CAPS the climb here even if `qualityBar` is unmet — run the worker at that tier and accept its result, don't climb further (F7). That rung is exactly where a blocked fable re-resolves (`resolveWorker`, the SAME fall an unavailable fable takes), so a grade-5 `reasoning` START with no rung-below drops there too: block the RESOLVED model (the exact name that tripped the ask — a pinned `claude-fable-5`, NOT merely the literal `fable`, which would re-resolve + re-serve the pin) and keep declining while `isFableModel` stays true. No cost math, no money-gate — one worker, one ask. Sensitive work keeps its never-down floor (the cap holds at its safe floor tier `heavy`, never below). A blocked/unavailable fable spawn falls the same way with no ask.
- **EFFORT tracks OUTPUT-SIZE/iteration, NOT difficulty — the two knobs are independent:** TIER is the correctness lever (difficulty → which model); EFFORT is the size lever (how much to produce/iterate). A hard task with SMALL output = strong TIER + LOW effort: a 10-line constant-time compare gets a strong tier (heavy/opus) for *safety* but LOW effort — it's 10 lines (don't jump a tiny crypto fn to the top fable rung; that over-provisions TIER). Reserve max for genuinely LARGE / deep work (a formal proof, a big refactor). Burning max on a tiny hard fn is the over-provisioning trap — the TIER already bought correctness; effort only sizes the work.
- **Effort rubric (by OUTPUT SIZE):** `low` = a lookup / ≤30 lines / 1-2 sentences · `medium` = a function + tests / a short proof / a paragraph · `high` = a multi-case proof / a multi-file change / a long document · `max` = a formal proof all-cases / a big refactor / an exhaustive sweep.
- **Effort is the ALWAYS-ON lever (it pays even when TIER cannot move):** at the top tier, or with only one tier available (cheaper tiers off, or a single-model user), you cannot escalate TIER but STILL optimize by scaling EFFORT per task (low for easy, high for hard). Most routers only pick a tier (worthless with one tier); CoalTipple's effort lever delivers savings at ANY fixed tier. A self-executing top main IS still routing: it pins the tier and tunes effort. **"Pins the tier" on SELF means a CEILING on any worker you might spawn from here, never an instruction to self-downgrade — you are already whatever model you are and cannot change that; the pin only bounds a future spawn.**
- **Delegate-down FLOOR (most important):** spawning a worker has a high fixed overhead (~tens of thousands of tokens — measured). **Delegate down only for a task big enough that doing it yourself would cost far more than the overhead** (≥ `delegateMinLines`, default 120). Small-to-medium → **do them yourself** (offloading loses tokens). `delegateMinLines` is a CODE-line proxy — cheap-per-line work (translation, formatting, plain text) burns far fewer tokens/line, so it needs proportionally MORE than its raw line count before delegating pays. Judge real effort vs overhead, not the line count.
- **Budget gate before you fan out (the proactive limit-hit guard) · spawning is the biggest spend you control:** a worker pays off only when the task clears the floor AND there is clear BUDGET HEADROOM. Near a session/quota limit, or right after a limit-hit, the cheapest correct route is often NO worker: collapse to inline-self (F9). Above all, do NOT fan out SEVERAL workers at once for marginal-value, context-light, mechanical work (a multi-file grep/compare, a small audit, a consistency sweep) the main can do inline (P12) — N spawns × ~tens-of-thousands near a budget edge IS the limit-hit, and a worker that dies on the limit returns NOTHING (partial findings unrecoverable). `resolveWorker` is the REACTIVE fall once a spawn is blocked; THIS is the proactive gate before you spawn at all.
- **If YOU are the floor tier (cheapest — e.g. main is Haiku):** no tier exists below you, so delegate-DOWN is off the table. A big size-driven mechanical task (the one the grade table routes "delegate-down" for a higher main) has NO down-target → it collapses to SELF: do it yourself and CALL it self (a "delegate-down to myself" label is the self-contradiction that then slips the work UP to a costlier tier). Cheap/mechanical work you can do → **do it yourself.** Work beyond your competence (hard / sensitive / too-big-context) → **escalate UP** (don't keep a hard task on the floor just because "it isn't sensitive" or you hold the context). At the floor: self-for-cheap, up-for-hard.
- **Context floor (the worker starts FRESH) — distinguish two kinds of "big context":**
- **Accumulated CONVERSATION context** (files read, decisions, the user's intent, the back-and-forth) — NON-transferable; a fresh worker can't see it. Work whose correctness depends on it → **keep on main** (compressing it into a contract is lossy + expensive). This is the real context-floor.
- **On-disk / re-readable INPUT** (a codebase, a large document, logs) — a worker CAN read it itself from a path in the contract. A large on-disk input is NOT a reason to keep work on main — it's a reason to pick a worker with a big-enough context VARIANT (Step 0): choose TIER by difficulty, VARIANT by input size, independently. (Example: "refactor a 180k-token codebase" → the 180k is on-disk → delegatable → a hard refactor on a heavy tier + a 256k variant = an Opus-256k worker. Exception: if you ALREADY hold the input in a large-context main, keeping it there is free.)
- **ULTRACODE** (max effort + multi-agent fan-out) = the top rung, for genuinely *broad + independent* work only; workers run a cheap tier, you orchestrate (cap concurrent at `maxConcurrentSubagents`, default 4 — they share one rate limit). **Spawning more than one worker in one move is ASK 2** — state the plan (how many × tier × effort) and get a yes first. **fast-mode is not a routing choice (P11)** — attach it only on an explicit human latency request, and only if `fastModeOnLatencyRequest` (default true); never as a routing rung.
- **Voice:** when `preserveVoiceForUserFacing` (default true), never delegate the final user-facing deliverable to a cheaper model (P9) — INCLUDING a translation, summary, or research write-up (the prose IS the deliverable), even when bulky (reviewing it to protect voice + terminology costs about as much as redoing it). Bulk MECHANICAL work (renames, formatting, codegen) is not "deliverable prose" and stays delegate-able.
- **A SELF result faces the same bar (P7 binds you too), UNCONDITIONALLY — `qaOnMerge` does not gate it** (`qaOnMerge` only tunes a DELEGATED worker's merge rigor, per its own config help text; `off` skips a worker's merge-verify but never skips your own): verify your own output with the same domain-appropriate check as a worker's (Step 3.4), sized to the change — a code edit runs the build/tests, a text/comment edit re-reads the diff for completeness + correctness; eyeballing your own work is still not verifying. Below `qualityBar` → the staircase applies to YOU: raise effort, then escalate (F3). SELF work edits the user's files in place — no `.proposed/` sandbox (Files ledger).
## Step 3 — If you delegate
1. **Task contract** (required when `requireTaskContract`, default true): give the worker goal + constraints + interface + done-criteria. A thin brief makes a cheap worker build the wrong thing with confidence. Keep it compact.
2. **Spawn** at the chosen tier/effort — **lead the spawn's description/label with the chosen model + effort so the human sees what is running** (e.g. `[haiku·low] scaffold 48 handlers`, `[opus·max] formal proof`, `[sonnet·high] refactor auth`; CC renders the description as the task chip). Require a **compact diff/summary**, not whole files. **The worker MUST be BOUNDED — never spawn an open-ended worker that can loop (P6):** the done-criteria (item 1) makes it terminate (a "keep improving" brief runs forever). `subagentTimeoutSeconds` (default 150) catches a STALL (F6); the done-criteria prevents the LOOP.
3. **Spawn-fail-fall — the never-fail availability loop (drives `resolveWorker`; F1–F2):** a spawn can ERROR instantly (0-token) because the model is **unavailable / disabled / out of quota / gone** ("X is currently unavailable" — proven live with `fable`). Availability is knowable ONLY here, at spawn-time. On error: **(a)** add that model to a `blocked` set; **(b)** call `resolveWorker(ranking, desiredTier, {blocked, floorTier})` (classify.mjs — walks `desiredTier`→`floorTier`, skipping `blocked`); **(c)** spawn the returned worker; **(d)** repeat (a)–(c) on each fresh unavailable error; **(e)** if `resolveWorker` returns `null` (everything blocked to the floor), STOP — for a SENSITIVE task the `floorTier` is its safe-minimum, so `null` means hand back / wait for reset and **NEVER breach never-down** by falling cheaper (F2). **Guarantee: routing reaches a working model OR hands back cleanly — never stuck on an unavailable model, never falls sensitive work below its floor.** (This is the DOWN-for-availability move; the quality climb below is the UP-for-quality move — opposite directions, don't conflate.) Mechanics + the limit-hit/quota fallback detail: **`references/damage-control.md`.**
4. **Verify against qualityBar:** rigor follows `qaOnMerge` (strict / standard / off; default standard) — `off` skips merge-verify (trust mode), `strict` = exhaustive. Output may "look right but be wrong" — **never trust it blindly, never let the worker grade itself (P7).** Measure against external criteria = the contract done-criteria + a **domain-appropriate objective check** (code: build/test/lint/type-check · text/translation: every segment present + consistent + format preserved · research: every claim has a real, authoritative source, verified with the web/doc tools *you have yourself* · math: check the steps / substitute back · **a comment-only edit INSIDE a code file is BOTH: re-read the diff for correctness/completeness AND confirm the file still parses/lints**). **RUN the check — actually execute the tests/build/parse; do NOT eyeball the code. A cheaper main especially defaults to eyeballing: it catches obvious misses but mis-judges subtle bugs (live: low and mid mains both read a buggy `1.5h` as 5400 by eye; only running it revealed the true 18000). Eyeballing is not verifying.** Clears the bar → done.
- **Below the bar → climb the staircase (F3):** near the bar + attempts remain → **escalate one rung** (the next lever in EFFORT/VERSION/TIER order — a stronger same-tier VERSION first, then the next TIER; don't retry the same model, don't skip a tier while a stronger same-tier version is untried). **Far below (the next rung won't clear it), or out of `maxTotalAttempts` (default 2) → jump straight to the top tier (F4)** (don't die by a thousand cuts). At the top and still failing → hand back to the user.
- **competence-introspection:** if you *know* from the start the task exceeds your ceiling → escalate immediately, don't waste an attempt (F11).
5. **Never let a cheap main QA an expert's work (P8)** — if you escalated *up*, let the strong worker own both the hard task and the integration.
## Memory anchor · Damage control · Self error-report
These run only on a specific, infrequent path — keep the rules out of the per-route hot path:
- **Memory anchor** (a fresh worker gets project context beyond the bare contract): when you are about to delegate **context-dependent** work, find the anchor — `contextFiles` if set, else the platform's auto-loaded `CLAUDE.md`/`AGENTS.md`. If none exists AND `memoryOffer` is `auto` AND you have not offered this session (ASK 3) → read **`references/damage-control.md` (Memory anchor)** for the lazy-offer + write-policy (append, never clobber — P17). Changed anytime via `/coaltipple memory`.
- **Damage control** (bound the blast radius of a mid-run death — a silent stall, a limit-hit/unavailable fallback, the `.proposed/` sandbox + `state.json` journal, worktree-isolation, the never-delegate/never-retry-a-side-effect rule P15): the load-bearing safety invariant stays in the gates above (sensitive never falls below its floor on a limit-hit — HARD GATE + Step 3.3). The mechanics fire only when a delegation is actually failing (F6 · F10) → **`references/damage-control.md`.**
- **Self error-report** (CoalTipple misbehaves — a contradictory instruction, a loop/dead-end, a clearly-wrong route, the Lock refusing a valid ranking, a config key off-spec): STOP, summarize what went wrong, OFFER to file at `github.com/TheColliery/CoalTipple/issues` (never auto-submit — P16). Detail: **`references/damage-control.md`.**
## Always
Honor the MERGED config (every value is tunable): global `~/.claude/.coaltipple.json` (your defaults) overlaid by the first-found project config (`<gitroot>/.claude/coal/coaltipple.json` → `.agents/coal/coaltipple.json` → `.gemini/coal/coaltipple.json` → LEGACY `<gitroot>/.claude/.coaltipple.json`; per-key override; a project file is OPTIONAL — absent = global + schema defaults) — **EXCEPT `mode`/`updateMode`/`fableConsent`, a cloned project file may only QUIETEN these, never escalate:** `mode`/`updateMode` can't climb past your GLOBAL choice, or past the schema default (`auto`/`ask`) when you set no global at all — a cloned repo cannot silently turn ON auto-routing or auto-updates. `fableConsent` is narrower: a bare project `true` with NO matching global setting is your own "always-this-project" consent (`configure.mjs --project --fableConsent true`) — honor it; only an EXPLICIT global `false` blocks a project `true` (never let a project override a global you deliberately set to false). **Consent-gate token spend per the Asks ledger** — never spawn fable unconsented (ASK 1), never fan out several workers silently (ASK 2). **Respond in the user's language** (auto-detect, or the `language` config): translate the prose, but **never translate technical terms** — commands, paths, identifiers, tier/model names, severity labels, config keys stay verbatim. `/coaltipple off` turns routing off; `/coaltipple stats` shows approximate savings (an estimate — there is no cost API); `/coaltipple memory [on|off|set <file>]` sets up or disables the memory anchor; `/coaltipple update` checks for a newer version.