| name | coaltipple |
| description | Model/effort router — delegate a task you CAN do but that is large+cheap DOWN to a cheaper tier to save tokens (delegate-down), and hand a task beyond your competence UP to a stronger tier for quality (escalate-up). Use when a task has a large cheap subtask worth offloading, or when it exceeds the current model's competence. 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
[!WARNING]
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 -> this skill does not apply. (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).
Step 0 — A valid ranking must exist first (the Lock)
Routing needs a tier ranking. 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 → low / mid / heavy (reasoning = opus at max effort), overlaid with .coaltipple.json modelTiers pins. The platform resolves each alias to its current best model AT SPAWN-TIME, so the floor never goes stale. Read the GLOBAL shared ranking ~/.claude/.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 .coaltipple.json and it wins front-of-tier.
- A failed spawn FALLS (
resolveWorker, Step 3): availability is knowable ONLY at spawn-time, never from a catalog.
- If a ranking genuinely cannot be built → routing OFF: just work normally. 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 / format |
| 2 | ≤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 |
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 (.coaltipple.json keywords) — honor them: built-in groups (concurrency/crypto/security/coding, math, knowledge, domain, creative, audit) each carry a grade floor + optional sensitive (never-down) and preserveVoice flags. Fold in BOTH the built-ins AND the user's overrides: a prompt matching a group's words floors the grade to its grade 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 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 — 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. 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, before grade or anything else — 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 .coaltipple.json 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. 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). 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: 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 (e.g. an episodic fable) is trusted there.
The keyword flags are ENGLISH-ONLY, so on a NON-ENGLISH task YOU are the sensitive-gate authority: 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): 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 · Effort |
|---|
| 1–2 | delegate-down (only if large — see floor) else do it yourself | low · low–med |
| 3 | do it yourself or delegate | mid · med→max 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 · effort by SIZE · sensitive → never down |
| 5 | escalate-up / do it yourself | strongest · effort by SIZE (small = low, deep/large = max) |
- 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. 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.)
- 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 the reasoning tier for safety but LOW effort — it's 10 lines. 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.
- 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. 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 — 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). fast-mode is not a routing choice — 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 — 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.
Step 3 — If you delegate
- 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.
- 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: the done-criteria (item 1) makes it terminate (a "keep improving" brief runs forever). subagentTimeoutSeconds (default 150) catches a STALL; the done-criteria prevents the LOOP.
- Spawn-fail-fall — the never-fail availability loop (drives
resolveWorker): 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. 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.
- 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. 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). 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: 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 (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.
- Never let a cheap main QA an expert's work — 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 → read references/damage-control.md (Memory anchor) for the lazy-offer + write-policy. 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-retry-a-side-effect rule): 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 → 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). Detail: references/damage-control.md.
Always
Honor the MERGED config (every value is tunable): global ~/.claude/.coaltipple.json (your defaults) overlaid by project .claude/.coaltipple.json (per-key override; a project file is OPTIONAL — absent = global + schema defaults). Consent-gate token spend — never fan out costly work silently. 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.