Autopilot · Architecture map

Autopilot, drawn as Kubernetes resources

I kept describing Autopilot in words, and the words kept drifting away from the code. So I drew it the way the cluster would see it, as Kubernetes resources, and checked every box against the code on October 7, 2026. This page is that drawing. It's a snapshot, so some of what it calls a draft may have moved since, and drawing it turned up six things the code says that the docs didn't.

Architecture October 7, 2026Snapshot checked against the code on Oct 7, 2026Docs Autopilot design
Snapshot · Oct 7, 2026 · core built, runtime drafted

One Deployment, three jobs, and a grant it never hands out.

Six questions, answered from the code

Is Clearance an MCP server?

Three MCP tools, the rest are arguments

yes

tools_list, session_open, call. Hangar tools such as repo.pr.open or run.spawn are names passed to call, so one gate sees everything. Built on stdio; the HTTP transport isn't built.

A policy engine?

21 CEL deny rules, fail closed

yes

R000 to R020, first match wins, and any evaluation error is R000 and a deny. Tiers T0 to T3, path and field scope, repo and API allowlists, budgets. A denial returns the rule id and a fix hint to the agent.

A contract issuer and validator?

Issues a session, keeps it, checks every call

partly

There is no signed token a run carries. The grant lives in Clearance's memory, keyed by session id. Knowing a session id gives nothing: R001 checks the caller's identity against the session's principal.

Does it create the AI workload?

By creating an AgentRun, not an Agent

yes

launch (top level) or run.spawn (a child) creates the XR. Its one Kubernetes permission is a Role on agentruns in autopilot-runs. Agent is the planned XRD form of the AgentDefinition (Phase C).

Does the workload talk back to Clearance?

It's the only thing it can talk to

yes

NetworkPolicy allows DNS, Clearance and the model proxy, nothing else. The pod has two projected tokens, one per audience, valid for an hour, and no other credential.

Can outside clients use it?

And many never get a run pod

yes

Claude Code or a Tower user opens a session and calls tools with its own loop, so there is no AgentRun at all. Task, scheduled, event and team agents get one. Service agents are ordinary apps.

The map

One request, through every resource it touches. Box borders show build state. Most of the cluster side was a draft on October 7, 2026: written and unit-tested, never applied. The amber numbers are the order things happen in.

One request, through every resource it touchesCallers open sessions on Clearance in autopilot-system. Clearance creates an AgentRun XR in autopilot-runs; function-agentrun and provider-kubernetes compose a namespace agent-r-<id> with a quota, two network policies, a service account and a Job. The run pod can reach only Clearance and the model proxy.WHO TALKS TO CLEARANCEA person in Toweror the Autopilot pluginClaude Codeany MCP client, no runHolmesGPTSA token, TokenReviewTrigger bridgeand the interval runnerAUTOPILOT-SYSTEMCLEARANCE · DEPLOYMENTMCP serverstdio · 3 toolsAuthenticateOAuth, TokenReviewPolicy engine21 CEL deny rulesSession storethe grant, memoryRun launcherAgentRun, fake k8sGatewayaudit, spend, callAirframe toolsairframe.* plannerRecord writerhash-chained JSONLmodel-proxyallowlist · budgetforwarder not builtSESSION_OPEN · CALL1AUTOPILOT-RUNSAgentRuncatalog.hangar.io XR3CROSSPLANE-SYSTEMfunction-agentrunone Pipeline step, testedprovider-kubernetes6 Objects, never secrets4AGENT-R-<ID> · ONE PER RUNNamespacerestricted PSSResourceQuotasecrets 0 · jobs 1NetworkPolicy ×2Clearance + proxy onlyServiceAccountno token automountJobbackoffLimit 0APPLIESRun podimage by digest · non-root · read-only root · 2 tokens56TOOLS, CLEARANCE TOKENMODELSLEGENDbuilt and testeddraft, never appliedproposalexists todayrequest path
Figure 1Everything here lives in the dev cluster: runs and agent writes exist only there, and the component is refused on an upper cluster. Box borders show build state; the amber numbers match the steps below. The pod holds no Secrets, no provider keys and no kubectl.
What Clearance reaches, and where every decision landsClearance calls TokenReview, a token interceptor, Argo CD, Backstage, Prometheus, Loki and app APIs through adapters. It loads AgentDefinitions from the autopilot repo and writes only PRs to app and gitops repos. Every decision lands in a hash-chained audit chain, with an object-locked copy, a Rekor anchor, OpenTelemetry spans and task_id stamps proposed around it.Clearancegateway · adaptersSERVICES CLEARANCE CALLSTokenReviewwho is this pod?Token interceptorrepo-scoped GitHub tokenArgo CDrole:clearance-lowerBackstageMCP actions, partly builtPrometheus · Lokimetrics, logs, app APIsModel backendsonly the proxy holds keys7GIT · REVIEWED, PULLED BY ARGO CDautopilot repoAgentDefinitions: the ceilinggitops-cluster-devXRD, Composition, RBAC: not applied<app> reposGround PRs, may auto-merge (T1)gitops-<app> · tenantsFlight PRs, propose only (T2)LOADS8FLIGHT RECORDER · ONE TASK_ID ON EVERY RECORDAudit chainJSONL · prev_hash · hashObject-lock bucketWORM copy, in MinIORekor anchoranchors count and headOTel spanstool and LLM spans, tokenstask_id stampscommit, PR label, appReadersplugin, Agent tab, record9LEGENDbuilt and testeddraft, never appliedproposalexists today
Figure 2The adapters were interfaces and fakes on Oct 7, 2026. Model backends sit outside the cluster and only the model proxy talks to them. In the recorder only the chain itself was built; the bucket was decided (D5) but not created.

The request, step by step

1

A caller opens a session with session_open(agent, claim).

A workload definition must be called by its own service account; a delegated one must be called by a person.

2

Clearance computes the grant.

The AgentDefinition's limits, narrowed by the caller's request and, for a child, by what the parent has left. Any widening is denied (R007). The reply carries the session id, task_id, deadline and effective limits.

3

For an in-cluster run, Clearance creates one AgentRun in autopilot-runs.

Afterwards it can only patch spec.frozen (the breaker) or delete it (run.close).

4

Crossplane calls function-agentrun, which renders six provider-kubernetes Objects.

provider-kubernetes applies them into agent-r-<id>. A bad request is Rejected and renders nothing.

5

The agent calls tools at CLEARANCE_URL with its clearance-audience token.

Each call goes through authenticate, rules, audit, spend, adapter, audit.

6

Model calls go to MODEL_PROXY_URL.

The proxy checks the model allowlist and model_tokens on the same session, then forwards with a key only it holds.

7

Allowed calls reach real services through adapters.

TokenReview, the interceptor, Argo CD (lower environments only), Backstage, Prometheus, Loki, app APIs. The adapters are interfaces and fakes today.

8

Writes leave only as PRs.

The interceptor mints a GitHub token scoped to one repo for one call. Ground PRs may auto-merge; Flight PRs are propose-only and a person merges them.

9

Every decision, allowed or denied, becomes one hash-chained record with the task_id.

∎

At expiresAt the function renders nothing and Crossplane deletes the namespace, with or without Clearance alive.

A tripped breaker cuts egress and holds the run 15 minutes for forensics, then it expires.

The contract, in practice

What gets called "the contract" is a chain of five documents. The AgentDefinition in git is the ceiling. Every later copy can only narrow it, and only the session inside Clearance is checked on each call. The copies in the AgentRun and the pod are for information, except the deadline, network mode, compute and sandbox, which the composition enforces on its own.

Five documents, each narrower than the lastThe AgentDefinition in git is the ceiling. The caller's request narrows it, Clearance's session holds the result and enforces it, and the AgentRun spec and runtime env carry shrinking copies for information.AgentDefinitiongit · ttlMinutes 10 · modelTokens 120000 · the ceilingThe caller's requestttl_minutes 5 · model_tokens 60000Session (the grant)enforced on every callAgentRun spec3 of 6 budgets · deadline enforcedRuntime envinformation onlyEach copy can only narrow the one above it. Only the session is checked on each call.
Figure 3The flight-briefer example from the code: the definition allows 10 minutes and 120000 model tokens, the request asks for 5 and 60000, and that is what the session enforces.
DocumentWho writes itWhere it livesWho can see itWhat's in itEnforced by
AgentDefinitionA person, in a reviewed PRagents/*.yaml in the autopilot repo; Clearance loads itAnyone who can read the repoIdentity, tier ceiling, tools, repos, APIs, models, network, compute, all budgets and TTL, image digest, triggersIt is the ceiling
The caller's requestThe caller: a person, a trigger, or a parent runThe claim argument of session_open or run.spawnClearance. The audit keeps only its hashOptional lower values for any limitR007 if anything widens
Session (the grant)Clearance, by narrow()Clearance memory onlyClearance. The caller gets one copy of the limits in the replyEffective limits, used and reserved counters, deadline, breaker, parent and childrenClearance, on every call
AgentRun specClearance (agentrun_manifest)etcd, namespace autopilot-runsWhoever RBAC lets read agentruns thereAgent, image digest, sandbox, compute, network, deadline, ids, 3 of the 6 budgetsComposition: deadline, network, compute, sandbox
Runtime envfunction-agentrunThe pod specThe agent processHANGAR_* ids, deadline, limits JSON, two URLs, token pathsNothing: information
Does it hold the budget? Yes

The session holds six limits: the TTL plus five counters (tool_calls, github_calls, open_prs, model_tokens, children), and spends them as calls happen. A child run reserves its budget out of the parent's remainder when it starts, and its real usage rolls up when it closes, so a team can never spend more than its root was granted. A 200-sequence property test covers this.

Five denied calls, a tripwire, or a token overrun trips the breaker. The counters exist only in Clearance's memory: a restart ends every session, which is the safe direction, and the runs still stop on time.

The acceptance test: the parachute sentence

"Provision a new python application named parachute. Give it a dev and test ground environment, a staging and prod flight environment, and configure it with one 100% weight canary step and a URL=http://myendpoint.io env var."

apiVersion: airframe/v1
kind: AppSpec
app: parachute
stack: python
environments:
  ground: [dev, test]
  flight:
    - { name: staging }
    - { name: prod }
config:
  rollout:
    strategy: canary
    steps: [{ setWeight: 100 }]
  env:
    - { name: URL, value: "http://myendpoint.io" }
Plan step (real output)RepoGateFiles
tenantsgitops-cluster-dev-tenantshuman mergeA PythonApplication XR plus two ApplicationEnvironment XRs (staging and prod, on the prod cluster)
repos-readygitops-cluster-dev-tenantswaitCrossplane creates the app and gitops repos
app-repothe parachute app repoauto-mergecicd.yaml patch, platform/envs/dev.yaml, test.yaml
tekton-resyncthe parachute app repoauto-mergeGlidepath regenerates .tekton/
gitopsgitops-parachutehuman merge<cluster>/staging/values.yaml, <cluster>/prod/values.yaml
verifynoneverifyXRs Ready and CicdOnboarded, the dev namespace's Application Synced

The planner raises two warnings on its own: the environment name test collides with the pipeline stage name, and a single 100% step promotes at once with no ramp. The app-repo row is where the plan broke on October 7, 2026; see the first two gaps below.

The flight recorder

Clearance writes one record per decision, allowed or denied, chained to the one before. Arguments are kept only as a hash, so a secret that slips into a call never lands in the log. The chain proves nobody edited, deleted or reordered records. It can't prove nobody cut off the tail, which is why the checkpoint gets anchored in Rekor and copied to an object-locked bucket.

A denial the agent can fix:

{"seq": 43, "ts": "2026-10-07T13:56:20Z",
 "task_id": "t-3f9a1c2b7d10",
 "session": "s-3f9a1c2b7d10", "parent": null,
 "principal": "user:james",
 "agent": "flight-briefer",
 "tool": "app.api.get", "tier": 0,
 "decision": "deny", "rule": "R017",
 "args_sha256": "51aa…",
 "result": "that application API is not on this agent's allowlist",
 "prev_hash": "d17a…", "hash": "0b3f…"}

Found while drawing this

Six things the code said that the docs didn't. Each was checked against the autopilot repo on October 7, 2026, not taken from the docs. I haven't rechecked them for this page, so treat each as found on that day.

The parachute plan is denied by Clearance's own baseline.

Found Oct 7, 2026

Blocks M3 exit. The planner's app-repo step patches cicd.yaml. BASELINE_DENY_PATHS puts cicd.yaml off-limits to every agent, and test_dispatch.py asserts that exact denial (R015). Run end to end, the sentence stops at step 3. This needs a decision: a field-level allow for /deploy/environments using the AF-9a machinery, or hand that step to a person through Tower's existing cicd.yaml PR flow.

The planner is stale against the environments overhaul.

Found Oct 7, 2026

1 test failing. 234 of 235 tests pass. The failure: the planner emits deploy.lowerEnvironments, upperEnvironments and promotionOrder, and Glidepath's schema now accepts only deploy.environments (ADR-0019). It also writes platform/envs/*.yaml, while apps now use glidepath/envs/.

A run pod can't pass R001 yet.

Found Oct 7, 2026

Design gap. R001 compares the caller with session.principal, which is the person or the definition's service account. A run pod authenticates as system:serviceaccount:agent-r-<id>:run. Nothing maps one to the other. The unbuilt auth adapter has to, for example by finding the AgentRun that owns that namespace and taking its sessionId.

The recorder can't reconstruct what a run was allowed to do.

Found Oct 7, 2026

Audit gap. The session.open record stores the caller's request only as a hash and doesn't store the effective limits, and sessions live only in memory. After a restart the grant is gone. The limits hold no secrets, so recording them in that record costs nothing.

The drafts still used the old API group.

Found Oct 7, 2026

Drift. manifests.py and the AgentRun XRD said catalog.idp.io; the Tier 2 rename had moved everything to catalog.hangar.io, and design.md's Role already used the new group.

The counts in the docs were out of date.

Found Oct 7, 2026

Docs. The docs said 19 CEL rules and 255 tests. The code had 21 rules (R000 to R020, with R020 for field scope added by AF-9a) and 235 tests.

Sources: the autopilot repo's src/clearance/ (dispatch, session, limits, policy, audit, modelproxy, manifests, server, airframe_plan), airframe-drafts/ (XRD, composition, function-agentrun, RBAC) and agents/, plus the Hangar docs: the Autopilot design, roadmap, glossary and decisions. Example ids and timestamps are illustrative; field names and shapes are the code's.