Skip to content
Version v0.4 (draft)
ChangelogContributing

Objects

Decision and signing

The authorizer's signed verdict, how it is signed, verified and versioned.

A decision is the authorizer’s answer to an intent. It is signed with Ed25519 over canonical JSON, and it is the only thing an executor will act on.

Verdict Executable Meaning
ACCEPT yes Every rule passed. The buying power the order needs is reserved.
RESIZE yes Accepted at a smaller qty because the mandate has allow_resize. requested_qty keeps the original.
REJECT no A rule failed. code and message say which. Signed, so the refusal is attributable.
PENDING_APPROVAL no Above approval_above_notional. Carries an approval_id. After a human approves, every rule is re-run and an executable decision is issued.
CHECK_PASS no The answer of a dry run (/v1/check). Never executable.
Field Notes
v Payload version, 4
decision_id, intent_id
verdict See above
agent_id, user_id, mandate_id Who asked, on whose behalf, under which mandate
instrument, side, qty, order_type, limit_price, venue The order exactly as it may be sent
requested_qty RESIZE only
account_id, account_ref The user’s account at the venue, and the venue’s own account reference passed to the adapter
protected_unit The venue and product class whose rules applied
reference_price, price_collar_bps Used to bound market orders
reservation_id, reserved_notional, currency The hold taken on buying power
reduce_only
time_in_force Canonical value (post_only, not alo)
enforcement Who stands between this decision and the venue. See below
extended_hours Whether the order may trade in pre- and post-market sessions. Required from v3
asset_class The instrument’s asset class (equity, etf, option, crypto), which selects the venue’s declared capabilities. v4
order_class, stop_price, trail_price, trail_percent, notional, position_intent, take_profit, stop_loss, legs, replaces, action The order terms, as in the intent. Absent values are null (legs an empty list). v4
expires_at Short-lived. The reference implementation defaults to 30 seconds
client_order_id Minted by the authorizer. The idempotency and reconciliation key at the venue. See Orders
approval_id PENDING_APPROVAL only
code, message Reason for a rejection
key_id Identifies the signing key
  1. Build the payload above, including key_id.
  2. Serialise it as canonical JSON.
  3. Sign the bytes with Ed25519. The signature is hex-encoded and travels next to the decision, not inside it.

Verifiers fetch public keys from GET /v1/keys, which returns entries of key_id, public_key (hex) and alg. A verifier selects the key by the payload’s key_id. An unknown key_id SHOULD trigger a bounded refresh of the key set, so keys can rotate without restarts.

Any change to a signed field makes verification fail. The executor answers A2M_BAD_SIGNATURE with HTTP 422.

Value Meaning Status
custodial_fairx The executor holds the venue credential and is the only path to the venue. Enforced. Implemented. Every decision issued today.
client_hook A hook in the agent’s own client asks the authorizer first. Bypassable. Planned
watcher A read-only connection reconciles the account afterwards and flags orders with no decision. Detects, does not prevent. Planned
venue_verified The venue verifies the authorizer’s signature itself. Planned

An executor MUST refuse an enforcement mode it does not implement (A2M_ENFORCEMENT).

  • Adding a signed field, or changing the meaning of one, is a version bump.
  • A verifier MUST refuse a version it does not know, and MUST refuse a payload that carries a field its version does not define.
  • v: 1 (legacy) has no time_in_force and no enforcement. It is still accepted so a decision issued before an upgrade stays usable until it expires, and executes as gtc with custodial_fairx and extended_hours false. A v1 payload carrying a later field is rejected.
  • v: 2 requires time_in_force and enforcement. It is still verified and means extended_hours false. A v2 payload that carries extended_hours is rejected.
  • v: 3 adds extended_hours (boolean, required). Still verified. A v3 payload that carries a v4 field is rejected.
  • v: 4 (current) adds asset_class and the order terms. A simple limit or market order’s terms are all empty. The executor still verifies versions 1 to 3.

Each executable decision holds the worst case of what it can spend:

Form Hold (buy side)
stop qty × max(stop, reference) × (1 + collar). A gap past the hold is recorded and settled from cash
stop_limit qty × limit; the limit must be within the collar of the stop
trailing_stop (reference + trail) × (1 + collar), or reference × (1 + percent) × (1 + collar)
notional the notional
bracket, oto the entry; the exits only reduce
oco nothing; it closes a position
long option qty × 100 × premium
covered call, cash-secured put 0 with the shares held, or qty × 100 × strike
mleg (maximum loss at expiry + net price) × 100 × qty, never below 0; undefined risk is refused

A group is one decision, one hold and one order at the executor. If a bracket’s stop loss is rejected after its entry filled, the executor records group.unprotected evidence and the authorizer puts the user in reduce-only mode.

A decision is issued, then either consumed by the executor or voided. It cannot be sent twice (A2M_DECISION_CONSUMED). A decision that expires before it is sent is refused (A2M_DECISION_EXPIRED) and its reservation is released.

The authorizer runs these rules in this order and stops at the first failure. Rules after kya.mandate may assume exactly one live mandate.

# Rule Layer
1 kya.agent kya
2 kya.mandate kya
3 allowlist.instrument user
4 allowlist.venue user
5 market.instrument market
6 broker.unit broker
7 account.open account
8 market.session market
9 price.reference market
10 market.lot_tick market
11 market.price_band market
12 price.collar user
13 broker.notional broker
14 fat_finger.qty user
15 fat_finger.notional user
16 kill.halt user
17 direction user
18 position.limit user
19 throttle.open_orders user
20 throttle.rate user
21 buying_power account
22 budget account
23 account.cash account

The authorize response carries the rule results as hits (each with rule, layer, passed, code, message), next to verdict, decision, signature and key_id.

Shape only; identifiers are placeholders.

{
"verdict": "REJECT",
"code": "A2M_FAT_FINGER_QTY",
"message": "quantity exceeds the fat-finger cap",
"decision": {
"v": 4,
"decision_id": "dec_...",
"intent_id": "demo-0002",
"verdict": "REJECT",
"instrument": "ETH",
"side": "buy",
"qty": "5",
"venue": "hyperliquid",
"enforcement": "custodial_fairx",
"time_in_force": "gtc",
"extended_hours": false,
"code": "A2M_FAT_FINGER_QTY",
"key_id": "...",
"...": "..."
},
"signature": "<hex>",
"key_id": "..."
}