ATP Consistency & Semantics

The Agent Transaction Protocol gives you at-least-once delivery, exactly-once effect. This page makes that promise precise: states, transitions, retries, receipt timing, and partition behavior.

One-line summary: retries are safe because (intent_id, idempotency_key) is UNIQUE in the transactions table. A receipt is only emitted after the row transitions to settled and is signed by the site key. There is no "maybe paid" state.

1. Intent state machine

created
Intent received, signature valid, scope OK.
authorized
User / agent consent recorded. Nonce burned.
executing
Side-effects in flight (payment, fulfillment).
executed
Side-effects acknowledged. Awaiting settlement signal.
settled
Terminal success. Receipt signed and persisted.
compensated
Terminal failure with rollback. Compensation receipt signed.
expired
Terminal failure, no side-effects committed.

Transitions are monotonic. Once a row leaves a state it does not return. There is no executing → authorized edge. Recovery from a crash always re-enters at the same or a later state.

2. Delivery guarantee

The wire protocol is at-least-once: agents may retry any step until they observe a terminal state. The application layer turns this into exactly-once effect using the idempotency key:

-- transactions table (canonical shape)
CREATE TABLE transactions (
  intent_id        TEXT NOT NULL,
  idempotency_key  TEXT NOT NULL,
  state            TEXT NOT NULL,
  body             JSON NOT NULL,
  receipt          JSON,
  created_at       TIMESTAMP NOT NULL DEFAULT NOW(),
  updated_at       TIMESTAMP NOT NULL DEFAULT NOW(),
  UNIQUE (intent_id, idempotency_key)
);

A retry with the same (intent_id, idempotency_key) returns the existing row verbatim — same body, same receipt, same signature — without invoking the side-effecting handler again.

3. Idempotency key rules

4. Receipt timing

Receipts are issued only on entry to settled or compensated. They are never issued speculatively.

StateReceipt visible?Signed?
created → executingNo
executedNo (pending)
settledYesEd25519 over JCS canonicalization of the receipt body
compensatedYes (compensation receipt)Ed25519, separate document referencing the original intent_id
expiredNo (none was promised)

Receipts are verifiable by any third party at POST /api/atp/receipts/verify with no shared secret.

5. Retry policy (recommended)

6. Network-partition behavior

Failure modes during a partition:

7. Duplicate-intent detection

Distinct from duplicate requests: a duplicate intent is when the agent generates two independent intent_ids for what the user meant as one action (the classic "double-click checkout" failure). WAB does not eliminate this — it's a UX problem — but it limits the blast radius:

8. Ordering

Intents are independent. ATP does not impose a total order between two intents from the same agent or user. If your business logic requires "A before B", encode that as a precondition inside the intent body (e.g. "execute only if intent X is settled"). The server evaluates the precondition at authorize time.

9. Concrete examples

Successful retry after timeout

POST /api/atp/execute
Idempotency-Key: 9b1c...
Body: { intent_id: "int_abc", ... }

→ (timeout, agent retries the same request)

POST /api/atp/execute
Idempotency-Key: 9b1c...                ← same key
Body: { intent_id: "int_abc", ... }     ← same body

→ 200 OK
{ "state": "settled", "receipt": { ...signed... } }
(same row, no double-execution)

Nonce reuse (rejected)

POST /api/atp/authorize
Body: { intent_id: "int_abc", nonce: "n1", signature: "..." }
→ 200 authorized

POST /api/atp/authorize
Body: { intent_id: "int_abc", nonce: "n1", signature: "..." }   ← same nonce
→ 409 { "error": "nonce_consumed", "first_consumed_at": "..." }

10. Document history

Related: ATP overview · Security · Threat model · Benchmarks