Three-tier routing that cuts LLM costs 93–95% without quality loss. Route mechanical work to free local, reasoning to cheap API, identity-critical calls to the heavy model.
Most AI cost waste comes from one mistake: sending everything to expensive models. This system classifies every call by complexity, dispatches it to the cheapest model that can handle it, and applies output discipline to compress what each tier sends and stores.
Three tiers: local/free → cheap API → premium. Classify by purpose or flags, dispatch to cheapest competent tier, escalate on failure only. Never fan out in parallel.
Quality floor: no compression ships if output degrades. Permanent memory: long-term memory survives every optimization pass. Both override every other law.
Router alone: 67–90% cost reduction (realistic mix: 30% free / 65% cheap / 5% premium). Output doctrine adds another 3–10%. Both parts required — neither works alone.
Each tier has a class, a model, token budgets, and a target distribution within a rolling 50-call quota window (±10% tolerance). Hard-floor purposes skip quota logic entirely — they always land on T3.
The classifier checks purpose first, then flags, then prompt heuristics. Pass an explicit purpose for faster, more accurate routing. Hard-floor purposes (red pills below) route directly to T3 regardless of quota or cost pressure.
Mechanical, deterministic. No reasoning needed.
Flags: chat · light · cheap · mechanical
Heuristic: prompt <40 chars + greeting pattern → T1 automatically.
Inference needed. Not identity-critical.
Flags: deepseek · cheap_reasoning · long_context
Identity-critical, architectural, high-stakes. Cannot demote.
Flags: precision · opus · important · default if no match
Import once. Call ask() with a prompt and an optional purpose. The router classifies, checks quota, dispatches to the cheapest competent tier, logs the call to memory/tier-usage.jsonl, and returns a result object.
Three call patterns. Include purpose whenever you know it — routing is faster and more accurate than heuristic-only classification.
const { ask } = require('./lib/tiered-ask.cjs');
// ── SIMPLE CALL — router picks tier automatically
const r = await ask({ prompt: 'Summarize this paragraph: ...' });
console.log(r.text, r.tier, r.model);
// ── WITH PURPOSE — faster, more accurate routing
const r2 = await ask({
purpose: 'summarize',
prompt: 'Apollo program: 1961-1972, 12 men on moon.',
system: 'Return 2 sentences max.'
});
// ── HARD-FLOOR — always hits T3 regardless of quota
const r3 = await ask({
purpose: 'architectural_decision',
prompt: 'Design a load-balancer for 10M RPS. List tradeoffs.'
});
// ── FORCE A TIER via flags
const r4 = await ask({ flags: ['cheap'], prompt: 'Extract fields from this JSON...' });
| Field | Type | What it contains |
|---|---|---|
text | string | Model response, trimmed |
tier | 1 | 2 | 3 | Tier that handled the call |
model | string | Exact model name used |
latency_ms | number | Wall-clock time from call to response |
fallback | boolean | True if a cascade fallback occurred |
usage | object | Token counts: prompt_tokens + completion_tokens |
verification_flags | array | T2 hallucination guard findings (file paths, SM IDs) |
Check that all three tiers are reachable before your first real call.
node lib/tiered-ask.cjs ping
See tier distribution, average latency, fallback count, and per-model breakdown across the last N calls. Run this regularly to catch routing drift.
node scripts/tier-usage-report.cjs node scripts/tier-usage-report.cjs 200 # last 200 calls
Run the v2 setup prompt in Claude Code (or any agent with bash + file write). Takes ~5–10 minutes on broadband. The only manual step: paste your API keys when asked.
node --version ≥ 18. Install Ollama if missing (winget / curl / brew). Probe http://localhost:11434/api/tags — run ollama serve if not responding. Pick T1 model by free RAM: >24 GB → qwen3:32b · 12–24 GB → qwen3:14b · <12 GB → llama3.2:3b. Pull the chosen model via ollama pull <model>.
.env and append .env to .gitignore.
Copy this template. Fill in your API keys and set TIER1_MODEL to the model chosen in Phase A.
OLLAMA_BASE_URL=http://localhost:11434
TIER1_MODEL=qwen3:32b
DEEPSEEK_API_KEY=sk-...
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
TIER2_MODEL=deepseek-v4-pro
TIER2_FALLBACK_MODEL=deepseek-v4-flash
ANTHROPIC_API_KEY=sk-ant-...
TIER3_MODEL=claude-opus-4-7
QUOTA_WINDOW_SIZE=50
QUOTA_TARGET_CHAT=0.30
QUOTA_TARGET_CHEAP=0.40
QUOTA_TARGET_PRECISION=0.30
QUOTA_TOLERANCE=0.10
lib/soft-failure.cjs — append-only error log · lib/ollama-client.cjs — T1 caller + health probe · lib/deepseek-client.cjs — T2 caller with auto-scaled timeout + retry · lib/anthropic-client.cjs — T3 caller · lib/deepseek-verify.cjs — hallucination guard (file paths, SM IDs) · lib/tiered-ask.cjs — main router (classify → quota → dispatch → log) · scripts/tier-usage-report.cjs — distribution + latency report. All CommonJS (.cjs). Each client loads .env via a 4-line loader that skips # lines and does not overwrite existing process.env.
Run all six checks. Each should produce the expected output shown in the comment.
# 1 — All tiers reachable
node lib/tiered-ask.cjs ping
# → { tier1: { ok: true }, tier2: { ok: true }, tier3: { ok: true } }
# 2 — T1 routing (short greeting → tier 1)
node -e "require('./lib/tiered-ask.cjs').ask({ prompt: 'hi' }).then(r => console.log(r.tier))"
# → 1
# 3 — T2 routing (known purpose → tier 2)
node -e "require('./lib/tiered-ask.cjs').ask({ purpose: 'summarize', prompt: 'Apollo 1961-1972.' }).then(r => console.log(r.tier))"
# → 2
# 4 — T3 routing (complex prompt → tier 3)
node -e "require('./lib/tiered-ask.cjs').ask({ prompt: 'Design load-balancer for 10M RPS.' }).then(r => console.log(r.tier))"
# → 3
# 5 — Hard floor (always tier 3, no demotion)
node -e "require('./lib/tiered-ask.cjs').ask({ purpose: 'identity_audit', prompt: 'who are you?' }).then(r => console.log(r.tier))"
# → 3
# 6 — Distribution report
node scripts/tier-usage-report.cjs
lib/tiered-ask.cjs and scripts/tier-usage-report.cjs. All six verification checks must show expected output before marking setup complete.
Six invariants that override operational convenience. Violating any one breaks the system's trust guarantees or data integrity. No exception without explicit user confirmation and a logged rationale.
These apply in all modes — setup, production, maintenance, compression passes — with no exceptions.
.env once. Never logged, re-displayed, or emitted in any output after initial capture.memory/tier-usage.jsonl and memory/soft-failures.jsonl are append-only. Rotation requires a backup first. Silent deletion is data loss.HARD_FLOOR set in tiered-ask.cjs is not changed without explicit user confirmation. It protects identity-critical calls from cost pressure.Output is the most expensive surface — every emitted token is billed. These habits close the four leaks that burn ~30% of output tokens in unoptimized prompts: narrated reasoning, restated instructions, forward-carried context, and confirmatory tool calls.
Trigger: every output, every tier, every call.
Trigger: any list, comparison, sequence, status, or number.
Trigger: before any LLM call or tool call.
Trigger: any content >200 tokens, or content only some steps need.
Trigger: file edits, state changes, memory updates, task completions.
Memory is leverage when curated, cost when hoarded. Each tier has a distinct lifespan, eviction policy, and promotion rule. The worst mistake: letting working-memory content graduate automatically and turning long-term memory into a junk drawer.
"Long-term memory survives every compression. Compression that breaks this is not compression — it is amnesia."
—20x Token Reduction DoctrineThe two invariants (quality floor, permanent memory) override all 20 laws. These clusters cover the laws that cause the most silent failures when violated. Each law is ≤15 words — scan before any session.
The router solves the tier problem: which model handles this call? The doctrine solves the token problem: what does every tier send, store, and emit? Neither works without the other. The router without doctrine ships bloated prompts to cheap models and undoes half its own savings. The doctrine without the router applies compression pressure on premium calls that should have been routed away entirely.
The two invariants — quality floor and permanent memory — are not constraints on the system. They are the reason the system is trustworthy enough to run autonomously. Every compression that violates them is not compression. It is either a cheaper answer or a forgotten self.
Route cheap. Compress outputs. Keep memory clean. Strip until removing more degrades. Stop one step earlier.