Skip to main content

Agent Plane

You are in the deep end

This page is the Plane contract. The story is how it hangs together. Who owns the catalogue: operating model.

The Agent Plane is the catalogue and the classifier. There is one plane per trust domain — not one mega-agent for the enterprise.

Two boxes sit inside it:

  • Agent Data Plane — routes, prompts, manifests, workflows. The Front Door calls it to decide.
  • Agent Control Plane — the catalogue UI. Locally it has no database and no decide API.

Neither box pins a route. Neither box starts Runtime. The Agent Capability Plane sits beside them and shares the same store.

What it is​

The Data Plane answers: given this caller, this channel, and this utterance (or named route_id), which governed path owns the turn? Outcomes are route, clarify, or abstain. Only route is startable, and it is the Front Door that starts.

What it owns​

OwnsDoes not own
Route catalogue, eligible set, decideThe freeze, the run pin
Prompt packs, workflow pointersLangGraph execution
Control UI over the catalogueChannel auth, public ingress

What it must not do​

  • Pin route_id or start Runtime.
  • Expose a public URL. Only Front Door calls decide.
  • Collapse Control Plane and Data Plane into one process that both classifies and executes.

How to talk to it​

MethodPathWho
POST/v1/intent/decideFront Door only
GET/v1/intent/eligibleFront Door, Control Plane
GET/v1/catalog/routesFront Door, Control Plane, Runtime (pinned version)

Fixtures: decide-chat-request.json.


Design notes​

Business problem​

Every UI and partner API guesses the agent (if/else, a second chatbot, a hard-coded URL). Entitlements fragment. There is no single eval surface for misroutes, no shared clarify/abstain/safety, and no versioned catalogue. High-risk routes get side doors. Operators cannot show why a turn routed.

Key requirements​

IDRequirement (this box)
FR-1Only the two AFDs call decide. AR may GET the catalogue row at the pinned version. Plane is not a public URL.
FR-2Decide = active table ∩ required_claims ∩ channel. Explicit route_id still looks up, entitles, and records.
FR-3Outcomes: route, clarify, abstain. Only route starts AR (AFD does the start).
FR-5Chat/UI never sees route_id, confidence, or router_layer. Decision record is server-side.
FR-6 / FR-7Eligible chips are chat-visible only. Jobs skip Layer ② / ③, not eligible, safety, or the decision record.

Decide path: Layer ② < 50 ms. Decide must not wait on a 12-step loop or on audit commit. Catalogue unreadable: fail closed.

Architecture​

AFD calls the Data Plane to decide. The Data Plane classifies against the versioned catalogue. ACP writes the decision audit asynchronously. Neither box starts AR.

Editorial source

AFD calls classify, then freezes and starts. This plane does not call AR.

Key technology decisions​

ChoiceWhy
ACP ≠ Data PlaneControl/audit can lag. Classify must fail closed and stay on the hot path.
Plane does not pin or startPin sits on AFD. Start sits on AR. Decide SLO is classify, not work.
Versioned in-memory catalogueEligible set ∩ claims ∩ channel must be local. Origin QPS on every turn does not hold.
Cheap layers firstLayer ① bind, Layer ② retrieve (<50 ms), Layer ③ only if needed. Safety on every path.
Async decision auditDecide SLO does not include "audit committed." Exam trail may lag.
Layer ③ on a separate poolIf saturated: clarify / abstain, not a slower ACP.
Jobs still hit Plane ①Explicit route_id is not a skip of entitle + record.

Expected outcomes​

  • One ingress decision for a shared experience.
  • One versioned catalogue and one eval surface (golden set / CI).
  • Operators can show eligible set, router_layer, outcome, and route_table_version.
  • A down AR does not stop new turns. A down catalogue stops starts (correct).

This box owns / does not own​

BoxOwnsDoes not ownIf it dies
ACPPolicy, versions, decision audit (target)Pin, start, loop, AFD hot-path classifyAudit lags; in-flight runs continue
Agent Data PlaneEligible set, classify, route/workflow cataloguePin, start, loopClassify fails closed
Decision auditAsync router_layer, outcome, eligible_routes (target)Chat transcriptDecide still succeeds

2. Two boxes​

LayerCountWhy
Agent PlaneOne per trust domainSame eligible set, entitlements, eval, audit
Agent Control Plane (ACP)One inside that planeControl + decision record
Agent Data PlaneOne inside that planeRoute/workflow catalogue + classify

Three routers (do not collapse)​

RouterQuestionWho calls it
Agent Data PlaneWhich workflow / manifest / app?AFD (classify). AR (row at pin, not classify)
Agent planner (inside AR)Which tool / step next?AR
Model routerWhich LLM endpoint?AR

Data this plane owns​

RecordWho writes itLives whereJob
DecisionACPAudit store (target)Why this turn routed, clarified, or abstained
Route/workflow cataloguePlatform / domain teamsAgent Data PlaneVersioned rows. Includes agent_client_id per row.

Frozen route/session is on the AFD. Run pin is on AR. Capability versions are on the Agent Capability Plane (same Agent Plane band).


3. Classify​

The Agent Data Plane classifies. Agent Front Door calls it, then freezes and starts. AR runs. 202 comes from AR, not from the data plane.

Decide = active table ∩ required_claims ∩ channel. Outcomes: route, clarify, abstain. Only route starts AR.

Classification runs only over eligible routes. Cheap layers first, safety on every path. The jobs API skips Layer ② / ③. It does not skip eligible, safety, or the decision record.

Cheap layers first; safety on every path. Layer ① bind, Layer ② retrieve, Layer ③ only if needed. Jobs skip Layer ② and ③ but still entitle and record.

LayerWho sees itContents
ACP decisionAFDoutcome, and on route only: route_id, intent_label, route_table_version, confidence
Decision recordACP / ops / evalFull trace: router_layer, eligible_routes, safety_flags, latency_ms

Chat/UI never sees route_id, run_id, confidence, or router_layer (FR-5).


4. Context​

ActorRelationship
Chat AFDMessage + claims + session_id. May send route_id on hint tap / clarify pick (Layer ①).
API AFDExplicit route_id + claims. Never receives clarify.
ARGET catalogue row pointers at pinned route_table_version after start. Does not classify.
Platform / domain teamsAuthor versioned catalogue rows (agent_client_id, activation_target, manifest pointer).
Agent evalsGolden set and adversarial cases against this catalogue. CI gate.
Channels / browsersNo relationship. Not a public URL.

5. Container / component architecture​

ComponentResponsibility
DecideHot path. In-memory catalogue ∩ claims ∩ channel, then layers, then safety.
EligibleChat-visible ∩ claims ∩ channel. Event-only rows omitted. Used for chips, not start.
Catalogue row APIPointers at a pinned version: activation_target, manifest, policy, model, memory, agent_client_id.
Layer ①Bind: hint tap, clarify pick, jobs route_id, topic bind.
Layer ②Retrieve. Budget < 50 ms. Local v1: keyword retrieve.
Layer ③Separate pool. Rare. Saturate → clarify / abstain. Local v1: deferred.
SafetyEvery path, including jobs.
Audit publisherAsync. Full trace. Not on the decide clock. Local v1: not wired.

Hot path. Whole eligible set in replica memory. Unreadable: fail closed. AFD is the only decide caller.


6. APIs​

Module or internal RPC. Agent Front Door is the only decide caller. Do not pin. Do not start AR. Do not mint correlation_id.

MethodPathWhen / response
POST/v1/intent/decideEvery new decide. Chat: message + claims + session_id. Jobs: route_id + claims. Returns route, clarify, or abstain
GET/v1/intent/eligibleUI hints. Chat-visible ∩ claims ∩ channel
GET/v1/catalog/routes/{route_id}After outcome=route only. Row pointers at route_table_version. AR reads this after start

Chat with a frozen route/session and a continuation does not call decide.

Decide request: chat vs jobs (JSON)
{
"ingress": "chat",
"channel": "web",
"session_id": "sess-88",
"message": "Why was I charged $42?",
"route_id": null,
"claims": { "sub": "jane", "emts": { "accounts:read": true } }
}
{
"ingress": "jobs",
"channel": "api",
"route_id": "contract_review",
"claims": { "sub": "svc-legal-jobs", "emts": { "legal:msa_review": true } }
}
Decide response: route vs clarify (JSON)
{
"outcome": "route",
"intent_label": "fee_explain",
"route_id": "fee_explain",
"route_table_version": "2026.08.1",
"confidence": 0.91,
"eligible_routes": ["fee_explain", "account_history", "policy_qa"]
}
{
"outcome": "clarify",
"clarify_prompt": "Did you want a fee explanation or recent transactions?",
"candidates": [
{ "route_id": "fee_explain", "label": "Explain a fee" },
{ "route_id": "account_history", "label": "Show recent transactions" }
],
"eligible_routes": ["fee_explain", "account_history", "policy_qa"]
}

AFD channel APIs: Agent Front Door §6.


7. Deployment architecture​

ConcernStance
IngressNone from channels. AFD and AR only, private network / mesh mTLS.
ScaleDecide RPS + Layer ② latency. Three fleets: Chat AFD, API AFD, Agent Plane.
Catalogue rolloutVersion bump, not turn rate. In-flight pins keep old versions via AFD.
FailoverLose decide replicas: classify fails closed. Lose audit workers: decide still succeeds.
IsolationDo not share an autoscaler with AFD or AR. Do not put Layer ③ on the Layer ② pool.

Working order of saturation at hundreds of turns/s: (1) UI SSE on Chat AFD, (2) Layer ② CPU for free-text chat, (3) start-ack to a hot AR, (4) freeze KV, (5) Layer ③ — only if on the hot path. Do not.


8. Data architecture​

Route / workflow catalogue​

FieldWhy
route_idStable name.
route_table_versionPin unit.
required_claimsEntitlement. Not per-user rows in this table.
channels / chat_visibleEligible vs chips vs event-only.
activation_targetWhich AR. Omit = shared runtime.
agent_client_idWhich robot.
tool_manifest + versionPointer. No inlined tools[].
autonomy_modeWho decides the next step: 0–3.

Consistency: Catalogue reads at a named version (pinned) are repeatable. AR and AFD must not re-read active after pin.

Local seed: route packs under agent-fabric-scripts/stack/route/. See author an agent.


9. Event / messaging design​

Decide is synchronous RPC. Audit is the messaging path (target).

TopicPartition keyProducerConsumerSemantics
agent.decision.recorded (working name)session_idACP audit publisherAudit store, evalAt-least-once. Decide already returned
Catalogue version publishedroute_table_versionControl pipelineDecide replicasFan-out snapshot

This plane does not consume {runs_topic} and does not produce job starts.


10. Sequence diagrams​

Happy path — chat classify → route​

Chat AFD posts decide. The Data Plane intersects the catalogue with claims and channel, returns route, and writes the decision record asynchronously.

Editorial source

Jobs entitle (no Layer ② / ③)​

API AFD sends explicit route_id + claims. Data Plane looks up, entitles, and records. Missing claims → fail closed. No clarify.


11. Failure and resilience​

FailureBehaviour
Decide replicas downClassify fails closed. AFD returns 503. No guessed route.
Catalogue snapshot missingFail closed.
One AR downPlane still accepts turns. AFD start-ack fails for that target only.
Audit lag / audit store downDecide still succeeds. Exam trail lags.
Layer ③ saturatedclarify / abstain. Do not enqueue behind Layer ②.
Shared downUnaffected. Routing still works.

Anti-patterns: public Agent Plane URL; pin or start on ACP; re-read active after pin; scaling Layer ② by adding Layer ③ replicas; event-only rows on chat chips.


12. Scaling​

Scale Chat AFD, API AFD, and Agent Plane as three fleets. Decide must not wait on a 12-step loop. Numbers: Enterprise Agent Fabric §3.

AxisGrows withScale signal
ACPNew decide RPS (Layer ①–②)Decide RPS + Layer ② latency
Agent Data PlaneCatalogue reads at pinned versionVersion bump, not turn rate
Decision auditDecide RPS (async)Queue depth. Lag is OK

AFD and AR scale: Agent Front Door §12 · Agent Runtime §12.


Agent Runtime · Agent Capability Plane · Agent Front Door · Autonomy