Session lifecycle#
A session (kind AgentSession) is one run of an agent. The operator reconciles it into a sandbox, a session token and a NetworkPolicy, watches it run, and tears it down at the end. This page follows one session from kubectl apply to its usage report, in the order the code runs (internal/controller/agentsession_controller.go, session_governance.go, podspec.go, bootstrap.go, outcome.go, usage_report.go).
There are two ways to get a sandbox:
- Direct mode. The trust tier has no
sandboxTemplateName. The operator creates an upstreamSandboxwith a pod spec it builds itself, and mounts a token it minted. - Claim mode (warm pool). The trust tier names a
SandboxTemplate. The operator creates aSandboxClaimwith no environment; agent-sandbox adopts a pre-started pod, and the pod fetches its session from the identity broker (late binding).
Tiers that require attestation (tier-2, confidential VM) follow the same path, but the operator never sees the token; see attestation-gated secrets.
Phases#
stateDiagram-v2
[*] --> Pending: created (event Accepted)
Pending --> Pending: QuotaExceeded<br/>(requeue 5 s)
Pending --> Provisioning: admitted
Pending --> Failed: AgentNotFound, TierNotFound,<br/>SovereigntyViolation, ScopeEscalation
Provisioning --> Provisioning: InputPending, AssertionPending,<br/>WaitingForSnapshot (poll 15 s)
Provisioning --> Running: sandbox Ready<br/>(claim mode: pod bootstrapped)
Provisioning --> Failed: InputInvalid, PrincipalUnverified,<br/>AttestationFailed, TemplateNotBootstrapCapable
Running --> Suspended: spec.suspend, idle, revocation suspend
Suspended --> Running: resumed or new activity
Running --> Completed: sandbox Finished or TTL reached
Suspended --> Completed: TTL reached
Pending --> Failed: revocation terminate
Provisioning --> Failed: revocation terminate
Running --> Failed: revocation terminate
Completed --> [*]
Failed --> [*]
Completed and Failed are terminal. When a session ends, status.outcome records a result: Succeeded, Failed, Expired, Revoked or Cancelled.
Sequence (direct mode)#
sequenceDiagram
autonumber
actor Dev as Developer or portal
participant K as Kubernetes API
participant Op as Operator
participant IB as Identity broker
participant AS as agent-sandbox
participant Pod as Sandbox pod
participant GW as Gateway
Dev->>K: create AgentSession (agentRef, user, scopes, ttl)
K-->>Op: watch event
Op->>K: add finalizer maqpna.com/cleanup, phase Pending
Op->>K: read Agent, TrustTier, SovereigntyPolicy default
Op->>Op: principal, sovereignty, scopes subset, TTL clamp
Op->>Op: revocations and concurrency quota
Op->>IB: POST /v1/token (Bearer MAQPNA_BROKER_TOKEN)
IB-->>Op: token, expiresAt, spiffeId
Op->>K: Secret session-maqpna-token, input Secret
Op->>K: Sandbox (runtimeClassName from tier)
Op->>K: NetworkPolicy session-maqpna
AS->>Pod: start pod in gVisor or Kata
AS-->>K: Sandbox Ready
Op->>K: phase Running, readyAt, readyIn
Pod->>GW: tool and model calls with the token
GW->>K: annotate maqpna.com/last-activity
Note over Op,IB: token re-minted at 80% of its lifetime
Pod-->>AS: agent exits, Sandbox Finished
Op->>K: outcome from exit code, phase Completed, teardown
Op->>GW: POST /v1/usage/sandbox (readyAt, finishedAt)
GW-->>Op: 2xx, outcome.usageReport Recorded
Step by step#
- Accept. On first sight the operator sets phase
Pending, emits theAcceptedevent and adds the finalizermaqpna.com/cleanup. - Resolve the agent and tier. A missing
Agentfails the session withAgentNotFound; a missingTrustTierwithTierNotFound. - Bind the principal. The operator records
status.principal{user, groups, verifiedBy}once, from the installed admission mode (--principal-binding:off,requester,trustedCreators) or, withspec.userAssertionRef, by verifying the user's ID token (verifiedBy: idToken). A missing assertion Secret keeps the sessionProvisioning(AssertionPending); a mismatch fails it withPrincipalUnverified. - Check sovereignty. The agent's image, model endpoint, tool URLs and tier jurisdiction are checked against the
defaultSovereigntyPolicy. Inenforcemode a violation fails the session (SovereigntyViolation); inauditmode it only sets a condition and a Warning event. - Check scopes.
spec.scopesmust be a subset of the agent's effective scopes, elseScopeEscalation.models:<route>is added when the agent has a model endpoint. - Clamp the TTL.
ttl = min(spec.ttl or --default-session-ttl (1h), tier.maxSessionTTL, agent.maxSessionTTL);expiresAt = startedAt + ttl. A reduction setsTTLClamped. - Expire if due. When
now ≥ expiresAt, the sandbox, claim and Secrets are deleted, the result isExpiredand the phaseCompleted. - Govern. For an attested session still pending, the operator asks the attestation service for the release outcome (
AttestationFailedon failure). It applies the strictest matchingAgentRevocation:terminatefails the session (Revoked),suspendsetsspec.suspend=true,blockmints nothing and re-checks within 30 seconds. A session that would exceedmaxConcurrentSessionsstaysPendingwithQuotaExceeded; the oldest queued session is admitted first. - Validate input.
spec.input(task up to 64 KiB, or a ConfigMap reference, plusenv) is checked. A missing ConfigMap meansInputPending; bad input fails withInputInvalid; in claim mode a task over 4 KiB fails withTaskTooLargeForClaim. - Resolve a fork. With
spec.fork, the operator waits for the snapshot (WaitingForSnapshot) and checks agent, user and scopes match. - Get credentials.
- Direct, non-attested:
POST {--broker-url}/v1/tokenand the Secret<session>-maqpna-token; re-minted at 80% of the token lifetime (default token TTL 15 minutes). - Claim mode: nothing is minted;
TokenReadyreasonBootstrap. - Attested:
POST {--attest-url}/v1/releaseswith a 32-byte nonce;TokenReadyreasonAttestationGated.
- Direct, non-attested:
- Create the sandbox. The input Secret, the workspace PVC (if any), then the
Sandbox(orSandboxClaimagainstmaqpna-<tier>), owner-referenced, withshutdownTime = expiresAtandshutdownPolicy: Deleteas an upstream backstop. Then the NetworkPolicy<session>-maqpna. - Set the phase. Upstream
Finished=True→Completed; suspended or idle →Suspended; ready →Running(first time:readyAt,readyInand the metricmaqpna_session_ready_seconds{tier,mode}); otherwiseProvisioning, polled every 15 seconds. - Requeue at the earliest of expiry, token refresh, idle deadline or the provisioning poll.
- Idle and suspend. The gateway stamps
maqpna.com/last-activityon every tool or model call (at most once a minute per session). Afteragent.idleTimeout(default 15 minutes) without activity, the operator suspends the sandbox (operatingMode: Suspendedin v1beta1) until new activity arrives. - End. The result is set once, by precedence: revocation (
Revoked) > expiry (Expired) > agent-reported result (maqpna.com/result, written by the gateway when the agent callsPOST /v1/sessions/self/result) > exit code of theagentcontainer > the upstream Finished reason. - Finalise. Deleting the session runs the finalizer: the result becomes
Cancelled(reasonDeleted) if nothing else ended it, usage is reported, the sandbox, claim, Secrets and NetworkPolicy are deleted, then the finalizer is removed. - Report usage. With
--usage-report-urlset (the chart points it at the gateway's/v1/usage/sandbox), the operator posts{namespace, session, agent, user, tier, readyAt, finishedAt}once per session, authenticated with a projected ServiceAccount token. The gateway checks it with a TokenReview, meters the sandbox time and appends a ledger record with decisionusage. Failures retry every 30 seconds; after one hour the report is markedLost(eventUsageReportFailed). See usage metering.
Claim mode: late binding#
sequenceDiagram
autonumber
participant Op as Operator
participant AS as agent-sandbox
participant Pod as Warm pod
participant IB as Identity broker (port 8083)
participant K as Kubernetes API
Op->>K: status.bootstrap.env (no token)
Op->>K: SandboxClaim (warmPoolRef maqpna-tier, no env)
Pod->>IB: POST /v1/bootstrap?wait=20 (projected SA token, aud maqpna-bootstrap)
AS->>K: adopt warm Sandbox for the claim, label pod claim-uid
IB->>K: TokenReview, pod, Sandbox, SandboxClaim, AgentSession
IB->>K: patch status.bootstrap.pod, podUID, bootstrappedAt
IB-->>Pod: env, token, expiresAt, spiffeId
Op->>K: phase Running once bootstrap.podUID is set
- The operator writes the session environment to
status.bootstrap.envand creates a claim withoutspec.env, because agent-sandbox only adopts warm pods for claims without one. - The warm pod (its SDK, or
maqpna-attest-agent --mode=bootstrapon attested tiers) long-polls the broker's bootstrap port with its projected ServiceAccount token. - After adoption the broker follows pod → Sandbox → SandboxClaim →
AgentSession, binds the first pod (409 already_bootstrappedfor any other) and returns the environment and a fresh token. Attested sessions get the release ID and nonce instead of a token. - The session is
Runningonce bootstrapped;readyAtis the bootstrap time. Repeated bootstrap calls from the bound pod refresh the token.
What you see#
kubectl get agentsessions shows the result and start latency as extra columns (OUTCOME, READY-IN), and maqpna session list prints:
NAME AGENT USER PHASE OUTCOME READY-IN AGE
fix-4790 coder bob@acme.eu Completed Succeeded 2.43s 118m
fix-4821 coder alice@acme.eu Running - 1.71s 3m
Values are illustrative. Events on the session follow the steps: Accepted, TokenMinted, SandboxCreated, Running, TokenRefreshed, Suspending, Resumed, Expired, Completed, and the warnings ScopeEscalation, SovereigntyViolation, Revoked, QuotaExceeded. Read them with maqpna events or maqpna session describe.
Failure modes#
| Situation | Behaviour |
|---|---|
| Identity broker unreachable | MintFailed event; nothing is created; the reconcile retries |
| Operator down | Running sessions keep running; tokens are not re-minted, so they expire at their TTL |
| Attestation fails | The release is burned; the session fails with AttestationFailed and its sandbox is deleted |
| Usage report endpoint down | Retries every 30 s for one hour, then outcome.usageReport: Lost |
| Warm pool empty | The claim waits for a sandbox; alert MaqpnaWarmPoolEmpty |