Agent Action Capsule · explain
07 / 11

Bilateral attestation and settlement

Two parties who do not trust each other each seal only what their own side observed, and a verifier who trusts neither derives from the two records whether they agree.

Two sealed views of one payment, compared by a verifier Left column: three leg records. Step 1: a terms leg sealed by the payee states an amount of 1000 (BTC, assetScale 11) and a Lightning payment hash. Step 2: a payer_observed leg, sealed by the payer key 2cbf..., cites the terms leg and records payment_ref 6aaa35ab..., amount 1000, routing_fee 0, status settled. Step 3: a payee_observed leg, sealed by the payee key 7125..., cites the same terms leg and records the same payment_ref, received 995, receive_fee 5, status settled. Step 4: both observed legs flow into a verifier in the middle, which checks that the two keys differ, that the payment references are the same, and that the statuses are equal. Step 5: the verifier applies the amount rule 1000 = 995 + 5, which holds, and the derived state lands on agreed. Right column: six slots for the derived payment state of settlement records: terms_only, payer_stated, payee_stated, agreed, mismatch, unjoined. Step 6: a second conformance vector in which the payee reports receive_fee 0; 995 + 0 does not equal 1000, so the state lands on mismatch with differs: amount. The final picture shows both cases: receive_fee 5 gives agreed, receive_fee 0 gives mismatch. derived payment state (§9.2) terms_onlyno observed leg cites the terms payer_statedpayer leg only: a stated claim payee_statedpayee leg only: a stated claim agreedboth, joined, consistent mismatchboth, joined, amount/status/ref differ unjoinedboth present, cannot be joined terms leg · sealed by payee amount 1000 · BTC · scale 11 capsule_id 7fcbce84… payer_observed terms_ref 7fcbce84… payment_ref 6aaa35ab… amount 1000 · routing_fee 0 status settled kid 2cbf… payee_observed terms_ref 7fcbce84… payment_ref 6aaa35ab… status settled kid 7125… received 995 · receive_fee 5 received 995 · receive_fee 0 verifier (trusts neither) ✓ kid 2cbf… ≠ kid 7125… ✓ same payment_ref (same type) ✓ status settled = settled amount rule: payer.amount = received + receive_fee ✓ 1000 = 995 + 5 state is derived, not read ✗ 1000 ≠ 995 + 0 differs: amount Two conformance vectors: receive_fee 5 → agreed · receive_fee 0 → mismatch (differs: amount)

1What it is

Bilateral attestation is an exchange in which the requesting party signs what it asked for, the performing party signs what it did about it (performed, declined, or escalated), and each acknowledges the other's record.

Settlement records apply the same idea to a payment: the payer and the payee each seal, under their own key, only what their own system observed, as separate Agent Action Capsule leg records that cite one terms leg.

No record states whether the two sides agree; a verifier derives that from which records are present and whether their bound fields match.

2Design choices

Chosen: each sealer records its own side only, under its own key, and may cite the counterparty's leg as custody, never as its own observation.
Rejected: one party recording both sides; that is one party's account, the my-log-versus-your-log problem again. (settlement-records §3)
Chosen: the settlement state is derived by the verifier from the legs present; no member of a leg states it.
Rejected: a status field in the record; it would let one sealer state a fact only both sides' records can establish. (settlement-records §4.2, §9)
Chosen: exact amounts (integer string plus decimal scale) and the rule payer.amount = payee.received + payee.receive_fee, with no tolerance.
Rejected: comparing sent with received directly, or allowing a tolerance; an honest receive fee would read as a mismatch, and a tolerance would hide a real one. (settlement-records §6, §9.2)
Chosen: a verifier reports three distinct bilateral states: both halves present and agreeing, one half unavailable, both present and disagreeing.
Rejected: one "incomplete" outcome; a half that aged out of retention is not a finding against the record, while a disagreement is a contradiction that must stay one. (bilateral-attestation §10.1)
Chosen: a declined request is a completed exchange, recorded with its constraint basis and acknowledged by the refused party.
Rejected: treating a decline as a failed exchange; and a failure to countersign (delivery_unconfirmed, counterparty_timeout, countersign_refused) is kept apart from a decline. (bilateral-attestation §6, §8)
Chosen: signed objects from existing payment protocols are carried by digest, optionally with their exact bytes, and never re-signed.
Rejected: re-signing another issuer's object with the leg's own key, which a verifier reports as wrapped_resigned; restating its claims as the leg's own members is also forbidden, though no vector checks for it. (settlement-records §8, rule 3)

3What it shows / what it does not

Shows

  • That two records sealed under two distinct keys commit to the same payment reference, amount (after the receive fee) and status: agreed.
  • Which of amount, status and payment_ref differ, when both sides are present and joinable.
  • When only one side sealed: payer_stated or payee_stated, labelled as a stated claim.
  • When a payer-observed and a payee-observed leg share one key: sealer_conflation, never agreed.
  • Delivery separately: none, stated, matched or mismatch on the delivered content digest.
  • For a bilateral exchange: that a refusal was made and acknowledged, recorded with the same fidelity as performance.

Does not show

  • That either side's observation is true; agreement on a digest attests to the digest, not to what it represents (bilateral-attestation §10.1).
  • That a key belongs to the payer or the payee; that is the verifier's own key policy.
  • That a missing leg means disagreement; it may not exist yet, may not have been shared, or may never be produced.
  • Whether a fee was acceptable; agreed is about the payment only, and the terms comparison is reported separately as terms_amount.
  • That delivered content matches a description; with only a description_digest that judgment is outside the draft.
  • Who is right in a disagreement; neither half is preferred, and no dispute mechanism is defined.

A note on counts: the six slots above are the payment states of the settlement-records draft (§9.2). The bilateral-attestation draft defines three verifier states (§10.1), plus the three asymmetry dispositions and the orphan half of §8, and the reduced-assurance indicator of §7.

4Example

The payee's leg from the conformance vector for two Lightning payments: 995 received plus a receive fee of 5 explains the payer's 1000, so the expected state is agreed.

"id": "pos-ln-receive-fee-two-payments",
…
"label": "ln-payee-observed-1",
"capsule": {
  "spec_version": "draft-mih-scitt-agent-action-capsule-05",
  "format_version": "4",
  "canonicalization_id": "jcs",
  "action_id": "ln-payee-observed-1",
  "action_type": "fyi",
  "operator": "payee.example",
  …
  "settlement": {
    "version": "0",
    "leg": "payee_observed",
    "sealer_role": "payee",
    "terms_ref": "7fcbce842a8ac9c67c18c004905d06743709de02ecedd83dc486f9e4ac419005",
    "payment_ref": {
      "type": "ln.payment_hash",
      "value": "6aaa35ab4414eae0f97b9e5c2674aea404c421f8106b476295145ad7e13c6097"
    },
    "received": {
      "value": "995",
      "assetCode": "BTC",
      "assetScale": 11
    },
    "status": "settled",
    "observed_at": "2026-10-01T12:02:05Z",
    "receive_fee": {
      "value": "5",
      "assetCode": "BTC",
      "assetScale": 11
    }
  },
  "references": [
    {
      "type": "agent-action-capsule",
      "digest_alg": "SHA-256",
      "digest": "27c08813e6d7640f88d6b5ade05c4310312b47bcb3b47c89900ad411ef722173",
      "citation_purpose": "counterparty_half"
    }
  ],
  "capsule_id": "63271df109557076918258ed3e7e8586264db676054de716b18535f199a62db5"
},
…
"envelope_kid": "7125e78196a360c3a278d00944a6133f53265c295fe8e1ed7f75a2a65d4681f8"
…
"terms": "ln-terms-1",
"payment_state": "agreed",
"agreed_status": "settled",
"terms_amount": "equal",
"delivery_state": "none"
FieldTypeMeaning
settlement.legstringWhich of the four legs this is; a closed set.
settlement.sealer_rolestringpayee; must match the leg (payee_observed is sealed by the payee).
settlement.terms_refstringcapsule_id of the terms leg this leg answers; the verifier recomputes and compares it.
settlement.payment_refobjectTyped join key both sides report; here a Lightning payment hash.
settlement.receivedobjectAmount credited to the payee: integer string, asset code, decimal scale.
settlement.receive_feeobjectFee deducted on the receiving side; enters the amount rule.
settlement.statusstringWhat the payee's own system reported: pending, settled, failed or reversed.
references[]arrayCites the payer's leg by capsule_id as counterparty_half: custody, not an observation.
envelope_kidhexThe payee's key; differs from the payer leg's 2cbfe3c8….
payment_statestringThe expected derived state, written by hand from the draft text.

Source: action-state-group/agent-action-capsule:vectors/settlement/cases.json @ 2ef8aeb49e2334e24c404bd62d3fdb161770873c

5Specs