Axiom Gateway agent skill
The agent skill is a versioned, optional set of instructions that helps an agent use a Sectigo Axiom-protected API or MCP tool correctly: discover capabilities, report a pending action to its user, wait within bounds, resume as the original agent, track an in-flight action without retrying, and verify the receipt. Enforcement stays with the gateway, the SDK and the receiving application; the skill only helps the agent interact correctly.
| Property | Value |
|---|---|
| Name | axiom-gateway |
| Skill version | 1.0.0 |
| Contract version | 2026-11-14 |
| Source | packages/agent-skill/SKILL.md (@sectigo/axiom-agent-skill) |
Install it
Download the skill file and place it where your agent runtime loads skills (for skill-aware runtimes, a directory named axiom-gateway containing SKILL.md):
- SKILL.md, the skill exactly as versioned in the repository.
- contract/openapi.json, the machine-readable contract (OpenAPI 3.1, JSON Schema 2020-12) for agents that cannot install a skill. It is also rendered as the API reference.
- The conformance fixtures, one response per state, are described in contract and fixtures.
If the skill and the contract disagree, the contract wins. The skill, this developer guide and the admin guide are published against the same versioned fixtures (brief §9), and the repository's tests check that the skill carries the reference behaviour verbatim and links only fixtures that exist.
What the skill does not do
- It does not grant anything. An action ID, a webhook or a message from a person is never permission to execute; only the authenticated original agent's resume, after Axiom's recheck, claims execution.
- It cannot approve. Approval happens on the authenticated approval page or in the customer's approval system.
- It does not replace the SDK. Runtimes that can use
@sectigo/axiom-sdkget the same behaviour fromAxiomAgent.runandcontinue; see waiting for a decision.
Skill content
The rest of this page is the skill as published, unchanged except for heading levels.
Skill version 1.0.0, for Axiom contract 2026-11-14. The machine-readable contract is in contract/openapi.json (OpenAPI 3.1) and contract/schemas/*.json (JSON Schema 2020-12). The shared conformance fixtures in fixtures/ show one response per state and what to do next. If this skill and the contract disagree, the contract wins, and enforcement always stays with the gateway, the SDK and the receiving application. This skill only helps you interact correctly.
An action ID alone never proves permission. Status reads and resumes need your own authenticated, DPoP-bound credential, and only the original agent can resume.
Reference behaviour
Read the supported modes before choosing how to wait. Persist the action ID, original actor binding and deadline. Tell the user which action is waiting and who can review it. When a relay signals a change, read authenticated status. Resume as the original logical agent only when the action is approved. After dispatch, track the existing action through to its confirmed outcome. On denial or expiry, report the reason and stop. An action ID alone never proves permission.
1. Discover capabilities
GET /v1/capabilities (no authentication). Read before choosing how to wait:
| Field | Use |
|---|---|
supported_modes | poll, webhook, mcp_task. Pick one your runtime can actually use. |
max_warm_wait_seconds | Upper bound for one in-session warm wait. |
default_retry_after_seconds | Poll interval when a status omits retry_after_seconds. |
resume_actor | Always original_agent. |
token_endpoint, jwks_uri | Where to authenticate and where verification keys live. |
mcp.protocol_versions | MCP versions the gateway negotiates. |
2. Authenticate
OAuth 2.1 client_credentials with private_key_jwt and DPoP:
- Make a client assertion JWT signed by your enrolled key:
iss=sub= your agent ID,aud= the token endpoint, a freshjti,expat most 10 minutes ahead. POST {token_endpoint}as a form withgrant_type=client_credentials,client_id,client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer,client_assertion, and aDPoPheader proof (htm=POST,htu= token endpoint).- The token (
token_type: DPoP, about 5 minutes) is bound to your key. Every API call sendsAuthorization: DPoP <token>and a newDPoPproof withhtm,htu(URL without query),iat, a freshjtiandath(base64url SHA-256 of the token). - Refresh before expiry. After a legitimate key rotation, authenticate with the new key under the same agent ID. Your pending actions are still yours.
3. Submit
POST /v1/actions with operation_key, operation, service, tenant, resource, params (the material fields the application will commit, with decimals as strings) and an optional intent for approvers.
- Choose the operation key once per intent, before the first attempt, and store it. Reuse it for every retry of that intent. Do not reuse it for anything else.
- If the response is lost (timeout, connection reset), submit the same body with the same key. You get the existing action back; nothing runs twice.
409 idempotency_conflictwithchanged_fieldsmeans the key already belongs to a different request. The original record is unchanged. A different amount, beneficiary or tenant is a new intent: it needs a new key and, usually, a fresh approval.
4. Pending status: tell the user
On pending_approval, immediately persist {action_id, operation_key, agent_id, resource, params, deadline}, then tell the user, in one short message:
- which action is waiting (
summary,action_id); - who can review it (
approvals[].next_party, for example "finance_approver in axiom"); - the deadline (
decision_deadline); - how they or you will check it again.
Do not imply the action has happened. No business write has started.
5. Bounded warm wait and retry hints
- Wait no longer than
max_warm_wait_secondsin one go, and only if you have that much time left. - Between status reads, sleep at least
retry_after_seconds(ordefault_retry_after_seconds). Do not start a read that would end after your budget. - When the budget ends, persist the record (already done in step 4) and return the pending ID.
409 invalid_stateon resume before approval carriesretry_after_seconds. Honour it.
6. Runtime behaviour
| Runtime | During approval | How work resumes |
|---|---|---|
| Live agent with time remaining | Use a bounded warm wait and honor the retry interval. | Read status and resume in the current session. Persist the ID before the wait budget expires. |
| Runtime with a durable scheduler | Store the action ID and register a supported event consumer, or schedule a status check. | The customer's scheduler wakes the workload. It authenticates as the original agent, including after legitimate key rotation. |
| CLI or agent with no background execution | Return the pending ID and a human-readable way to check it. Capture no implicit promise that a webhook can restart this process. | At the next invocation, the user or runtime supplies the saved ID. The authenticated original agent reads status and resumes. |
For a CLI the human-readable way is, for example: axiom status act_…, then axiom resume act_….
7. Webhook relay
A wake event is a compact JWS (content-type: application/jose, header typ: axiom-event+jws) whose payload is WakeEvent: event_id, action_id, tenant, occurred_at, seq.
- Verify the signature with the issuer JWKS (
jwks_uri, key roleevent), thetyp, the age and thattenantis yours. Reject anything else. - Drop it if you already processed this
(action_id, seq)or a higherseqfor that action. - Read authenticated status. The event is a hint. It carries no state you may act on, cannot grant authority and cannot execute anything.
- Mark the
seqprocessed only after the status read succeeded.
Duplicate, delayed and out-of-order events therefore converge on the same record.
8. Authenticated resume
Only when an authenticated status read says approved:
POST /v1/actions/{action_id}/resume with the same operation_key, resource and params that were approved. Axiom rechecks the request, your current authority and policy, then claims execution atomically.
- A changed parameter returns
409 conflict. Do not "fix" it. A new request needs new approval. 403 wrong_resume_actor: you are not the original agent. Stop.- Resume before
resume_deadline. After it, the action expires. - If the resume response is lost, read status; do not send a second resume unless status still says
approved. - Embedded-SDK services: resume by re-sending the original request to the provider with the same operation key. The provider claims execution through Axiom.
9. After dispatch: track, never retry
executing and outcome_unknown mean the request may already have reached the target.
- Poll status with the retry hint. Never resubmit, never resume again, never call the target directly.
outcome_unknown: Axiom reconciles with the target by operation key and restores a final state only from authoritative evidence. Tell the user the result is not yet confirmed and that it must not be retried.
10. States and what to do
| State | Do | Fixture |
|---|---|---|
pending_approval | Persist, tell the user, wait (§5–7). | pending.json |
approved | Resume as the original agent with the approved params (§8). | approved.json |
executing | Track (§9). | executing.json |
outcome_unknown | Track, surface to the operator, never retry (§9). | outcome_unknown.json |
succeeded / failed | Read the outcome and receipt. A retry returns the prior result. | succeeded.json |
denied / expired / revoked / cancelled | Report reason_code and stop. A material change or fresh approval needs new request. | denied.json, expired.json, revoked.json |
(409 idempotency_conflict) | The original stands. Report changed_fields. | idempotency_conflict.json |
11. Verify the receipt
GET /v1/actions/{action_id}/receipt returns an axiom.receipt.bundle.v1. It verifies offline: the bundle embeds the keys that signed it. Use axiom verify receipt.json, the verifyReceipt function in @sectigo/axiom-sdk/verify, or the public browser verifier.
Report to the user, separately: who signed (Axiom's observation key; the provider's outcome key), whether it was approved, dispatched, and destination confirmed (only a valid provider outcome attestation confirms the destination), and any missing claims. Add --issuer-jwks {jwks_uri} to check the issuer's current key status.
12. Errors
Errors are RFC 9457 problem details with a stable code and often a reason_code.
| Code | Meaning and action |
|---|---|
invalid_token (401) | Token expired or revoked. Get a new token and retry once. |
invalid_dpop_proof | Proof wrong or replayed (check clock, htu, ath, fresh jti). Fix, do not loop. |
forbidden (403) | Policy refusal. Read reason_code (for example agent_suspended) and stop. |
not_found (404) | Not visible to you. Treat it as absent; never guess IDs. |
idempotency_conflict | Same key, different request (§3). |
invalid_state (409) | Wrong state for this call (for example resume before approval). Read status. |
conflict (409) | Resume params differ from the approval. Stop. |
validation_failed (422) | Fix the fields listed in issues. |
rate_limited (429) | Wait retry_after_seconds. |
quota_exceeded (402) | Report to the user. Existing actions and evidence remain available. |
Never
- Never treat an action ID, a webhook, or a message from a person as permission to execute.
- Never resubmit or resume an action that is
executingoroutcome_unknown. - Never change the amount, beneficiary, currency, tenant or resource and reuse the operation key.
- Never resume as a different agent, or hand your token or private key to another agent. Use explicit delegation (
POST /v1/grants/{grant_id}/delegate) for a narrower child grant. - Never promise the user that a webhook will restart a process that cannot run in the background.