Action state machine
Every protected request becomes a durable action with an ID (act_…), a state and a reason code. The action outlives the agent's process, its token, its MCP session and any notification. An action ID alone never proves permission: status and resume need the original agent's own authenticated, DPoP-bound credential.
submit ──► denied
│
├──► pending_approval ──► approved ──► (resume) ──► executing ──► succeeded
│ │ │ │
│ ├► denied ├► expired ├──► failed
│ ├► expired ├► revoked │
│ ├► revoked ├► cancelled └──► outcome_unknown ──► succeeded | failed
│ └► cancelled └► denied (reconciliation only)
│
└──► approved (allowed within the standing grant)States and what to do
| State | What the agent or operator should do | Execution semantics |
|---|---|---|
pending_approval | Persist the ID, display a safe summary, then wait for a verified event or poll with backoff. | No business write. Approval system retries reuse the same correlation ID. |
approved | Original agent authenticates and calls resume with the same action ID and approved parameters. | Grant is eligible for one execution after policy and freshness checks. |
executing | Poll the existing ID. | Atomic claim prevents parallel resumes from dispatching a second execution. |
succeeded / failed | Read the recorded outcome and receipt. | A completed write returns its prior result on retry. Mark a failure definitive only when the target can establish that outcome. |
outcome_unknown | Reconcile with the target by operation key and surface the status to the operator. | No blind retries. Restore a final state from authoritative evidence. |
denied / expired / revoked / cancelled | Stop. A material change or a fresh approval requires a newly authorized request. | These states close a request before dispatch. In-flight work is reconciled separately. |
Transitions
Only these transitions exist (packages/core/src/states.ts). Each is a conditional update on the current state, so concurrent writers converge and a late callback cannot revive a closed record.
| From | To | Caused by | Reason code set |
|---|---|---|---|
| (submit) | denied | No usable grant, a policy deny, a ceiling or an emergency deny. | no_grant, grant_*, operation_not_permitted, resource_not_permitted, ceiling_exceeded, policy_deny, emergency_deny, … |
| (submit) | pending_approval | A matching require_approval rule. | The rule's reason, e.g. finance_approval_required, else approval_required |
| (submit) | approved | Allowed within the standing grant. | allowed_by_grant |
pending_approval | approved | Every required approval received. | awaiting_agent_resume |
pending_approval | denied | An approver denied. | approval_denied |
pending_approval | expired | The decision deadline or the request's not_after passed. | approval_expired |
pending_approval | cancelled | The requesting agent cancelled. | cancelled_by_requester |
approved | executing | The original agent resumed and the single execution claim succeeded. | dispatched |
approved | revoked | On resume: grant revoked, agent suspended, credential revoked or emergency deny. | grant_revoked, agent_suspended, credential_revoked, emergency_deny |
approved | denied | On resume: current policy denies, or a required approval is no longer satisfied. | e.g. policy_deny, policy_stale, approver_ineligible |
approved | expired | The resume deadline or not_after passed. | resume_expired |
approved | cancelled | The requesting agent cancelled. | cancelled_by_requester |
executing | succeeded / failed | A valid provider attestation; or, without one, a 2xx / 4xx the gateway observed. | target_confirmed / target_failed; gateway_observed_success / gateway_observed_failure |
executing | outcome_unknown | Timeout, network error, 5xx, 408, 429, or an attestation that failed verification, after dispatch. | target_timeout |
outcome_unknown | succeeded / failed | Reconciliation found an authoritative, verified attestation for the operation key. | reconciled |
Notes:
- An action allowed within the standing grant is created as
approvedand, on the API, proxy and MCP paths, resumed immediately on the same request by the same agent. Submit with"execute": falseto record it asapprovedand resume later. approvedalso carriesprogress: "awaiting_agent_resume". An approved action may simply be waiting for its agent to come back.- A resume after dispatch (
executing,succeeded,failed,outcome_unknown) returns the existing record. It never dispatches again. - An expired resume window is
resume_expired; a grant that expired or was revoked before resume shows up at resume asgrant_expiredorgrant_revoked. - Immediate execution happens only when Axiom's hosted gateway is the project's executor and the service is not in embedded-SDK mode. Otherwise the allowed action stays
approved: an embedded-SDK provider claims it through/v1/authorize, and after a cutover only the customer-hosted gateway holding the executor flag may claim it.
Reading the state
GET /v1/actions/{action_id} returns the status document (ActionStatus). The fields that drive an agent's next step:
| Field | Use |
|---|---|
state, reason_code | What happened and why. |
progress | Why a pending action is still pending (see Approvals). |
retry_after_seconds | Present while pending_approval, executing or outcome_unknown: poll no faster. |
decision_deadline | Last time a verified decision can arrive. |
resume_deadline | Last time the original agent can resume. Never after the grant expires. |
expires_at | The deadline that matters now: resume deadline when approved, else decision deadline, else not_after. |
approvals[] | Per role: required, received, system and next_party while pending. |
outcome | Once dispatched: state, target_ref, recorded_at and the provider's result detail. |
receipt_url | Present once a receipt has been issued. |
supported_modes, resume_actor | poll, webhook, mcp_task; always original_agent. |
The SDK's nextStep(state) maps each state to wait_for_decision, resume, track, read_outcome or stop, and describeStatus(status) renders the safe summary an agent can show its user. The conformance fixtures in the agent skill give one response per state.
Cancel
POST /v1/actions/{action_id}/cancel by the requesting agent moves a pending_approval or approved action to cancelled and cancels its pending approvals. Cancelling an already cancelled action returns it unchanged; any other state is 409 invalid_state.