A router table for intelligence.
Four things happen to every request: it is classified, it is priced against a policy, it is dispatched to a target, and its output is verified. Everything else GhostRouter does — escalation, fallback, budget guards, overlay networking — is a consequence of those four steps being explicit rather than implied.
Eight rows of policy, and the topology they describe.
Hover a row to draw the route it produces. Click it to open the runbook clause that produced the row — the table is the legend for the diagram, and both are rendered from the same policy.
| Route | Match | Tier | Fallback | P50 |
|---|---|---|---|---|
| extract.invoice-lines | kind=extraction & schema_bound | SMALL | small-c → mid-a | 240ms |
| ||||
| summarize.thread | kind=synthesis & tokens<2k | MID | mid-c → frontier-a | 610ms |
| ||||
| translate.* | kind=translation | SMALL | small-a → mid-b | 260ms |
| ||||
| classify.intent | kind=classification | SMALL | small-b → small-c | 180ms |
| ||||
| review.contract | risk > 0.60 | FRONTIER | frontier-b → human-review | 2.1s |
| ||||
| code.migrate | kind=code-reasoning | MID | mid-a → frontier-a | 940ms |
| ||||
| bulk.rename | kind=mechanical & volume>0.55 | SMALL | small-c → small-b | 210ms |
| ||||
| agent.plan | kind=reasoning & ambiguity>0.5 | FRONTIER | frontier-a → human-review | 2.4s |
| ||||
Rows are evaluated top to bottom; the first match wins, and an unmatched request falls through to default-v3. Route names are yours — they appear verbatim in the decision payload, the audit log and the route-activity table in your portal.
Five steps, every time, in about 180 microseconds of our time.
Drag the playhead. Each stage carries the actual payload at that point in the pipeline — not a simplified illustration of one.
Every clause, and what it is responsible for.
# 1 — identity. Runbooks are versioned artefacts, not settings.
name: reporting-pipeline
version: 7
extends: default-v3
# 2 — classification. Deterministic, local, 42µs p50.
classify:
dimensions: [reasoning, risk, ambiguity, output_len]
evaluator: heuristic-v2
# 3 — guards run BEFORE tier mapping and can override it.
guards:
- when: "risk > 0.60"
floor_tier: mid
- when: "budget.period_used > 1.00"
pin_tier: small
# 4 — tiers hold interchangeable targets, not preferences.
tier:
small: { when: "score < 0.31", targets: [small-b, small-c] }
mid: { when: "score < 0.58", targets: [mid-a, mid-b, mid-c] }
frontier: { when: "default", targets: [frontier-a, frontier-b] }
# 5 — verification is part of the route.
verify:
mode: schema+judge
on_fail: retry
escalate_after: 2
# 6 — fallback is a path you keep warm, not a plan you write later.
fallback:
provider_down: next_target
tier_exhausted: escalate
exhausted: human-review
# 7 — budget is enforced by the router, not by a dashboard alert.
budget:
period: month
guard: soft
on_brake: pin_tier(small)
- extends
- stringInherit a base policy and override only what differs. Most teams keep one house runbook and three or four thin children.
- classify.dimensions
- string[]Which scores the evaluator produces. Adding a dimension changes the schema version, which is why the schema is versioned.
- guards[]
- rule[]Evaluated in order before tiering.
floor_tierraises a tier andpin_tierfixes it; a guard can never be silently skipped. - tier.<name>.targets
- string[]Model classes considered interchangeable at that tier. Order is not preference — selection is by health and observed p50.
- verify.mode
- enum
schema,judge,schema+judge, orschema+execfor outputs that can be run against a harness. - escalate_after
- integerVerified failures permitted at a tier before climbing exactly one rung. A request never skips a rung.
- fallback.*
- mapBehaviour per failure class: unreachable provider, exhausted tier, exhausted ladder. The last one is where
human-reviewearns its place. - budget.guard
- soft | hardA soft guard warns at 80% and brakes at 100%. A hard guard refuses over-budget requests with
429 budget_exhaustedrather than downgrading them.
Run the classifier against your own wording.
The router's own cost, measured.
These are the stages GhostRouter adds to a request. Provider time is excluded — it is not ours to claim.
| Stage | P50 | P95 | Where |
|---|---|---|---|
| classify | 42µs | 96µs | in-process |
| policy eval | 174µs | 410µs | in-process |
| target select | 21µs | 58µs | in-process |
| dispatch overhead | 0.9ms | 2.4ms | egress |
| verify (schema) | 3.1ms | 7.8ms | in-process |
| verify (judge) | 210ms | 480ms | tier-local |
Judge verification is the only stage that costs real time, which is why it is opt-in per route rather than global. Schema verification is effectively free and should be on for anything with a shape.
Three ways in. All of them are a base URL and a header.
There is no agent to install and no proxy to babysit. $GR_API below resolves to api.ghostrouter.io/v1 over TLS 1.3; plaintext connections are refused.
import { GhostRouter } from "@ghostrouter/sdk";
const gr = new GhostRouter({
apiKey: process.env.GHOSTROUTER_KEY,
runbook: "reporting-pipeline"
});
const decision = await gr.route({
route: "extract.invoice-lines",
input: pdfText,
verify: { mode: "schema", schema: "invoice.v2" }
});
console.log(decision.tier, decision.route, decision.policy_eval_us);
// small small-b 174
from ghostrouter import Router
# reads GHOSTROUTER_KEY and GR_API from the environment
router = Router(runbook="reporting-pipeline")
decision = router.route(
route="extract.invoice-lines",
input=pdf_text,
verify={"mode": "schema", "schema": "invoice.v2"},
)
print(decision.tier, decision.route, decision.policy_eval_us)
# small small-b 174
if decision.escalations:
metrics.incr("router.escalated", tags=[decision.route])
# $GR_API resolves to api.ghostrouter.io/v1 (TLS 1.3 only)
curl -sS "$GR_API/route" \
-H "Authorization: Bearer $GHOSTROUTER_KEY" \
-H "Content-Type: application/json" \
-H "GR-Runbook: reporting-pipeline" \
-d '{
"route": "extract.invoice-lines",
"input": "Invoice 4471 — 12 line items, net 30.",
"verify": { "mode": "schema", "schema": "invoice.v2" }
}'
{
"decision_id": "dec_7QF3M0RTY7K2",
"runbook": "reporting-pipeline@7",
"route": "extract.invoice-lines",
"tier": "small",
"target": "small-b",
"fallback": ["small-c", "mid-a"],
"attempts": 1,
"escalations": 0,
"verify": { "mode": "schema", "passed": true },
"policy_eval_us": 174,
"latency_ms": 238,
"output": { "invoice_no": "4471", "lines": 12 }
}
# Any model client that lets you set a base URL can be pointed
# at the gateway without touching a single call site. The router
# speaks the same wire shape and reads one extra header.
MODEL_BASE_URL="$GR_API/gateway"
MODEL_API_KEY="$GHOSTROUTER_KEY"
GR_RUNBOOK="reporting-pipeline"
# Requests arriving on /gateway are classified from their payload
# rather than a route name. Attribution comes from GR-Route if you
# send it, and from the calling key if you do not.
GR-Decision: dec_7QF3M0RTY7K2
GR-Runbook: reporting-pipeline@7
GR-Tier: small
GR-Target: small-b
GR-Attempts: 1
GR-Escalations: 0
GR-Policy-Eval-Us: 174
# Every gateway response carries its decision id. Pull the full
# trace later with GET /v1/decisions/{decision_id} — retained 30
# days by default, longer by agreement.