Skip to main content

Route

You are in the deep end

A route is the product. The story is how it hangs together. How a request binds one: request lifecycle.

A route is the agent. Everything else on this section — capability, prompts, schema, retrieval, memory, corpus — is a row that hangs off one. Authoring an agent means writing a route row and the rows it points at.

Routes live in dataplane.routes on the Agent Data Plane. The primary key is (route_id, route_version), so a route id is a family of immutable versions, not a single mutable record.

What the row holds​

FieldWhat it doesRequired
route_idStable product identifierAlways
route_versionImmutable catalogue cutAlways
intent_labelThe label decide returns on outcome=routeAlways
statusdraft, published, active, or retiredAlways. Defaults to published
activeMarks the one live version per route_idAlways. Defaults to false
autonomy_mode0–3. Who picks the next stepAlways. Defaults to 0
prompt_idThe prompt pack for LLM stagesEvery active route
tool_manifest + tool_manifest_versionThe pinned manifest of capabilitiesMode 1 and any mode 2/3 route with tools
workflow_idThe fixed stage listMode 2 and mode 3
max_loop_stepsCALL/DONE budgetMode 1 (defaults to 8 at Runtime when unset)
policy_profileRisk bar that Layer ② confirmation readsAlways
model_profileModel tier. FK to dataplane.model_profilesAlways
activation_targetWhere Runtime runs — the /v1/runs URLAlways in practice — Front Door dials it
agent_client_idWorkload identity pinned on the runOptional
required_claimsUser entitlement filter. Defaults to []. Not agent scopes — claims and scopesOptional
channelsChannel filter. Defaults to ["web"]Optional
chat_visibleWhether the route appears in the chat eligible setDefaults to true
descriptionDisplay text for the Control PlaneOptional

There is no manifest_id column. The manifest pointer is the pair tool_manifest + tool_manifest_version.

Two companion tables are keyed by the same (route_id, route_version) and foreign-keyed back to the route: dataplane.retrieval (see retrieval and corpus) and dataplane.memory_profiles (see memory).

Fields the catalogue stores but nothing reads​

Do not plan behaviour on these.

FieldReality
keywordsStored on the row. Layer ② does not score it. The classifier indexes labelled seed utterances, not this column
output_schema_idA pointer only. Runtime never loads it. Structured output comes from the capability output_schema — see schema
eval_suite_idCatalogue metadata
fallbackStored. No decide or Runtime path reads it
model_profileEnforced as a foreign key and passed on the start contract, but Runtime ignores it. The model is global — see LLM

What each autonomy mode requires​

CataloguePinLint enforces these at publish time, so a bad combination fails before a caller can hit it.

ModeMust haveMust not have
0 Single inferenceprompt_idtool_manifest, workflow_id, retrieval.mode=tool
1 Autonomousprompt_id, tool_manifestworkflow_id
2 Deterministicprompt_id, workflow_id—
3 Guidedprompt_id, workflow_id, tool_manifest—

Every active route needs a prompt_id, in every mode. There is no domain-HTTP-only product.

Versions, status, and the pin​

A route_id can have any number of versions. Exactly one may be active — a partial unique index enforces it, and a check constraint ties the two fields together: active = true if and only if status = 'active'.

StatusMeaning
draftWork in progress. Not hydratable
publishedAn immutable released cut. Not necessarily the live one
activeThe version new requests classify against. One per route_id
retiredTaken out of service

Only active rows participate in routing — decide and the eligible list both read activeRoutes().

Pinning is what stops a mid-flight catalogue change from rewriting a running request. Once decide returns a route_id and route_version, Front Door fetches that exact row, writes a freeze, and starts Runtime with both. Runtime refuses to start without the version, copies the pair onto a durable run pin, and hydrates against that snapshot. Publishing a new active version afterwards does not affect a run already in flight.

Authoring a route​

There is no write API. The Data Plane exposes reads plus decide; the only POST is /v1/intent/decide. Routes are authored as SQL under agent-fabric-scripts/stack/route/<route_id>/ and loaded by the seed script.

EndpointReturns
GET /v1/catalog/routesActive routes. ?include=all for every version
GET /v1/catalog/routes/{routeId}One route. ?route_version= for an exact pin, omit for the active row
GET /v1/catalog/routes/{routeId}/versionsEvery version of one route
GET /v1/intent/eligible?channel=Chat-eligible active routes

The response nests the resolved manifest, retrieval, and memory_profile alongside the route fields, so one GET is enough to see the whole product. The Control Plane browses the same endpoints.

How decide picks a route​

POST /v1/intent/decide is Data Plane only, and only the afd workload may call it. It prunes the eligible set first, then walks three layers.

LayerIdWhat it does
① RulesrulesA named route_id (jobs, or a chat chip) or a slash command from dataplane.intent_rules. First match wins, and a named route_id never falls through
② ClassifierretrieveIn-process nearest-neighbour over labelled utterances, on a 50 ms budget. Over budget means abstain, and ③ is skipped
③ LLM fallbackllmOff by default. The layer is a stub that returns nothing

Eligible pruning differs by ingress: jobs and a named route_id see every active entitled route including hidden ones, while chat sees only chat_visible routes matching the channel and the caller's claims.

OutcomeMeaningStarts a run
routeBinds route_id, route_version, intent_label, confidenceYes
clarifyReturns a prompt and candidatesNo
abstainNothing matched, or the classifier ran out of budgetNo

Only route pins. See request lifecycle for what happens after.

Seed examples​

Every stack seed uses route_version = '2026.08.1' with active = true.

route_idModeWhat it attaches
overdraft_fee_qa0Prompt pack, plus deterministic_prefetch over fee-schedule and product-disclosure
ticket_draft_reply0Prompt pack. chat_visible=false — it is a child of ticket_triage
fee_explain1Manifest, max_loop_steps=6
billing_assistant1Manifest with account_fee_lookup and lookup_order_by_order_id
duplicate_charge_review2Manifest plus workflow
kyc_onboarding2Manifest plus a workflow with branch and human_gate. chat_visible=false
ticket_triage3Manifest plus a workflow with a kind=agent child into ticket_draft_reply

Layer ① command seeds exist too: /shopassist binds shopassist_case, /overdraft binds overdraft_fee_qa.

What each mode hydrates: autonomy modes.