Hangar

Docs · Airframe

AgentRun: Autopilot’s sandboxed agent runs

AgentRun (catalog.hangar.io/v1alpha1, namespaced) is one bounded, disposable run of an Autopilot agent. Clearance, Autopilot’s tool gateway, creates it through the Kubernetes API in autopilot-runs; nothing else is meant to. It is the one Airframe kind that is not requested from git, because a run lives for minutes and no person reviews it (Autopilot design, “Durable writes are commits; ephemeral runs are direct requests”).

Status: installed on hangar-dev. The copy-test passed on 2026-10-28 (results), and the XRD and Composition ship through airframe-service-catalog on clusters rendered with components.autopilot. Nothing creates runs yet: Clearance (AP-A2) is not deployed.

What a run gets

Its own namespace agent-<name>, pod security restricted, default-deny NetworkPolicy, a quota with room for one Sandbox and no Secrets, Services, Jobs or PVCs
One pod an agent-sandbox Sandbox running the agent image (by digest) as non-root, read-only root filesystem, no capabilities, service account run with no token mounted
Egress by mode none (nothing), clearance (DNS + Clearance), clearance+model (+ the model proxy, port 8081: Clearance’s own pod, which charges model tokens to the session; the composition sets model_proxy_selector to Clearance’s label), allowlist (+ listed CIDRs)
Identity projected tokens for the clearance and model-proxy audiences only, never a key
The runtime contract HANGAR_RUN_ID, _TASK_ID, _SESSION_ID, _AGENT, _EXPIRES_AT, _LIMITS (spec.limits: the budgets and, since Autopilot A-7, tools, the session’s grant the runtime narrows its tool offer to), _INPUT, _OUTPUT_DIR, _WORKSPACE, CLEARANCE_URL, MODEL_PROXY_URL; /workspace, /run/output, tokens under /var/run/hangar

Guardrails

  • A hard deadline nobody has to enforce. spec.expiresAt becomes the Sandbox’s shutdownTime with shutdownPolicy: Delete: the agent-sandbox controller deletes the pod at that instant. The pod’s activeDeadlineSeconds is a kubelet-enforced backstop. Past the deadline the composition renders nothing and Crossplane removes the namespace. Clearance, Crossplane and the function can all be down and the pod still ends on time.
  • The deadline can’t move. The XRD makes every spec field immutable except frozen, which can go from false to true only (Clearance’s breaker). Nobody, Clearance included, can extend a run, widen its network or raise its limits.
  • Fail closed. No airframe.autopilotReady on this cluster’s registry record, a hardened sandbox without a runtime class (the cluster names one only through function-agentrun’s HANGAR_RUNTIME_CLASSES, which Apron sets from cluster.yaml autopilot.hardenedRuntime: gvisor together with the RuntimeClass itself), an unknown compute class, a TTL over the cap (240 minutes), an allowlist of /0, loopback or link-local, or an input over 4 KiB: Rejected, nothing created, never downgraded. The upper cluster can’t carry the flag (the registry chart refuses it).
  • No PR-sweep label. The run namespace never carries hangar.io/ephemeral-env; the PR-namespace TTL sweep would delete it. A test asserts it, and the cluster’s admission policy refuses it.
  • Final phases are final. Expired, Drained and Rejected never render again, so a finished run can’t restart.
  • A narrow caller. Clearance’s permission is one namespaced Role on agentruns in autopilot-runs. It passes no manifest: the schema constrains every field and the composition decides what is created.
  • A confined provider. provider-kubernetes’s grant for these kinds is confined by a ValidatingAdmissionPolicy to agent-r-* run namespaces, with no pod-security label changes and only serviceAccountName: run (cluster repo 55-autopilot/agentrun-grants/, decision D15).

Lifecycle

Provisioning (objects created, Sandbox not Ready) → Running (Sandbox Ready) → Succeeded / Failed (kept for a 5-minute grace period) → Drained. Frozen cuts egress and keeps the pod for forensics until freezeHoldMinutes, then Expired. The phase is status.phase and the RunReady condition’s reason (closed set in xrds/agentrun.meta.yaml). Read RunReady, not Ready: with nothing composed (Expired, Drained, Rejected) Crossplane’s Ready is True.

A finished run’s XR stays until something deletes it (Hangar decision D20). Clearance deletes it once it has recorded the run’s evidence. Whatever Clearance doesn’t delete, the reaper backstop does: every 10 minutes it deletes each AgentRun whose deadline, min(spec.expiresAt, creation + 240 minutes), passed more than 60 minutes ago, read from those immutable fields, never from status. Deleting the XR removes everything the run composed through owner references, including Objects Crossplane lost track of.

Installing it on a cluster

Everything below is in the cluster repo (apron renders it with components.autopilot, control-plane clusters only):

Path What
55-autopilot/agent-sandbox/ the agent-sandbox controller v1.0.8 (the image by tag: the chart labels with it), leader election off
55-autopilot/agentrun-grants/ provider-kubernetes’s ClusterRole, its confinement policy, the Object-origin policy, Clearance’s Role
10-crds-operators/crossplane/provider-kubernetes-runtime.yaml a stable service account name for provider-kubernetes
10-crds-operators/crossplane/functions.yaml, function-agentrun-runtime.yaml the Function, with HANGAR_CLUSTER_NAME
20-service-catalog/airframe-service-catalog/ xrds/agentrun.yaml and compositions/agentrun/composition.yaml in the include list
55-autopilot/agentrun-reaper/ the reaper backstop: a CronJob that deletes AgentRuns 60 minutes past their deadline

Verify a live run: tools/airframe-verify agentrun autopilot-runs/<name>. Egress is proven by Autopilot’s tools/netpol_canary.py, which renders the same policies with this function’s compose().

The copy-test, step by step with expected output: agentrun-copy-test.md.

Function internals, the expiry clocks and the shared-grant caveat: functions/function-agentrun/README.md.