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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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).
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.
The agent calls tools at CLEARANCE_URL with its clearance-audience token.
Each call goes through authenticate, rules, audit, spend, adapter, audit.
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.
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.
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.
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.
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.
| Document | Who writes it | Where it lives | Who can see it | What's in it | Enforced by |
|---|---|---|---|---|---|
| AgentDefinition | A person, in a reviewed PR | agents/*.yaml in the autopilot repo; Clearance loads it | Anyone who can read the repo | Identity, tier ceiling, tools, repos, APIs, models, network, compute, all budgets and TTL, image digest, triggers | It is the ceiling |
| The caller's request | The caller: a person, a trigger, or a parent run | The claim argument of session_open or run.spawn | Clearance. The audit keeps only its hash | Optional lower values for any limit | R007 if anything widens |
| Session (the grant) | Clearance, by narrow() | Clearance memory only | Clearance. The caller gets one copy of the limits in the reply | Effective limits, used and reserved counters, deadline, breaker, parent and children | Clearance, on every call |
| AgentRun spec | Clearance (agentrun_manifest) | etcd, namespace autopilot-runs | Whoever RBAC lets read agentruns there | Agent, image digest, sandbox, compute, network, deadline, ids, 3 of the 6 budgets | Composition: deadline, network, compute, sandbox |
| Runtime env | function-agentrun | The pod spec | The agent process | HANGAR_* ids, deadline, limits JSON, two URLs, token paths | Nothing: information |
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.
"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."
repo.pr.open, xr.request, repo.pr.open_upper and the airframe.* tools.airframe.plan. The planner, inside Clearance, compiles it into an ordered ChangeSet with no network and no clock, so the same input gives the same plan.airframe.apply walks the plan as PRs (steps 7 and 8). Each step's tier decides the gate: Ground and tenants work is T1, Flight values are T2 propose-only.airframe.verify checks the result.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) | Repo | Gate | Files |
|---|---|---|---|
| tenants | gitops-cluster-dev-tenants | human merge | A PythonApplication XR plus two ApplicationEnvironment XRs (staging and prod, on the prod cluster) |
| repos-ready | gitops-cluster-dev-tenants | wait | Crossplane creates the app and gitops repos |
| app-repo | the parachute app repo | auto-merge | cicd.yaml patch, platform/envs/dev.yaml, test.yaml |
| tekton-resync | the parachute app repo | auto-merge | Glidepath regenerates .tekton/ |
| gitops | gitops-parachute | human merge | <cluster>/staging/values.yaml, <cluster>/prod/values.yaml |
| verify | none | verify | XRs 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.
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…"}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.
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.
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/.
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.
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.
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.
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.