Getting started
Quickstart
From nothing to a refusal on the record: a running ledger, a signed agent identity, a posted transaction, and a decline the books remember. One process, three installed binaries, about ten minutes.
Finance reading
This walkthrough is written for the engineer standing the system up. What it produces is worth knowing in this register too: at the end there is a ledger that posted one transaction because policy permitted it, and declined a second because policy forbade it — and the decline is a permanent record with the who, the what, and the why attached. The control demonstrates itself in step five.
1. Run the service
The service is a single binary against an in-process ledger. No database, no broker.
1cargo install axorum-serve2
It reads config.toml from the directory you start it in. Two knobs matter today: the admin token that guards the administrative plane, and the port.
1admin_token = "quickstart-admin-token"2tick_interval_ms = 100034[service]5name = "axorum-serve"6port = 87917
1axorum-serve &2
The operator's tool is the axorum CLI. Tell it where the service is and how to speak as the administrator, once, and every command from here on is short:
1cargo install axorum-cli23export AXORUM_BASE_URL=http://127.0.0.1:87914export AXORUM_ADMIN_TOKEN=quickstart-admin-token56axorum health7
1http://127.0.0.1:8791 is reachable2no policy is in force — the ledger will not judge an intent until one is3healthy4
Every axorum command puts its one machine-readable value on stdout and its narration on stderr, so anything you see here pipes.
2. Mint an identity
Agents are named by agent:// URIs and prove themselves with signed attestations. The agent-uri CLI is the key ceremony in three commands.
1cargo install agent-uri-cli23agent-uri key generate --out treasurer.key4
The key file is written owner-read-only; the public half is what the ledger will trust. Issue the agent its credential — the issuer is derived from the URI's own authority, and the capability names what this credential may do. A capability is scoped beneath the URI's own path: this clerk's identity is books, and books/post is its permission to post to them. A credential can narrow what an agent is; it can never grant authority from outside the agent's own name:
1export AGENT_URI="agent://quickstart.example/books/clerk_01h455vb4pex5vsknk084sn02q"23TOKEN=$(agent-uri attest issue --key treasurer.key \4 --agent "$AGENT_URI" --capability books/post --ttl 90d)5
The token — and nothing else — lands on stdout, so it pipes. A summary of what was granted, and until when, goes to stderr where your eyes are.
3. Open the books
Four administrative commands: trust the issuer, open two accounts, and bind the agent to a policy party.
1agent-uri key public --key treasurer.key | axorum trust add quickstart.example --public-key -23CASH=$(axorum account open --currency USD --label "Cash")4REVENUE=$(axorum account open --currency USD --label "Revenue")56axorum agent bind "$AGENT_URI" --party clerk_bot --roles clerk7
1trusted 'quickstart.example' — the ledger will now accept attestations it signed2opened account acc_01kxaqsw4qez0a05wvtf9bxr5q (USD)3opened account acc_01kxaqsw4xe4srt5x555n5vtxm (USD)4bound agent://quickstart.example/books/clerk_01h455vb4pex5vsknk084sn02q to party 'clerk_bot'5
Account ids are client-minted — the CLI minted these two and printed them on stdout, which is why $CASH and $REVENUE now hold them. Pass --id acc_… to bring your own; the SDKs ship the same minting helpers.
The policy is DSL source. A clerk may post, under a delegation that terminates at a principal:
1cat > policy.ax <<'EOF'2policy {3 role clerk4 action post()5 party clerk_bot agent roles(clerk)6 party overseer principal7 delegation clerk_grant {8 principal overseer9 agent clerk_bot10 scope { actions(post) roles(clerk) window(0, 1000000) }11 }12 rule permit_post { permitted any_in_role(clerk) do post priority 1 }13}14EOF1516POLICY=$(axorum policy submit policy.ax --activate)17
1The service compiled the policy. In plain English, it says:23 This policy declares one role, 'clerk', and one action: 'post', which takes4 no parameters. 'clerk_bot' is an agent acting in the role 'clerk'. …5 Rule 1 — permit_post. Any party in the role 'clerk' is permitted to6 perform 'post'. (authored priority 1).78policy pol_1rfe5d5mjxa1g9as27grckn849 is now in force9
The service compiles the source, reads its own understanding back to you in plain English, and the returned pol_… id — the policy's content hash — is all that lands on stdout. Every intent from here on pins to it: a claim about which law the agent believed was in force. If the source does not compile, axorum renders the parser's diagnostics with line, column, and caret, and nothing is registered.
4. Give an agent the ledger
The fastest way to submit intents is to hand the whole agent plane to an MCP-capable agent. The @axorum/mcp server exposes five tools — submit_intent, get_active_policy, get_transaction, get_obligations, get_balance — and the agent's identity comes from your configuration, never from the model.
1claude mcp add axorum \2 --env AXORUM_BASE_URL=http://127.0.0.1:8791 \3 --env AXORUM_AGENT_URI="$AGENT_URI" \4 --env AXORUM_ATTESTATION="$TOKEN" \5 -- npx -y @axorum/mcp6
Then ask the agent to pay something. The tool result leads with a plain-language verdict line, and the model reads it like any other fact.
Prefer to hold the wire yourself? The CLI speaks the agent plane too, with the same configuration:
1export AXORUM_AGENT_URI="$AGENT_URI"2export AXORUM_ATTESTATION="$TOKEN"34axorum intent submit \5 --debit $CASH:120000 \6 --credit $REVENUE:120000 \7 --currency USD --action post \8 --justification "renew the burst-capacity reservation"9
1transaction: txn_01kxaqt8rge99tvtjmz62dcm6t2 (minted for you — it is the ledger's idempotency key. If this command is3 interrupted, run 'axorum tx get' with that id to find out whether the4 intent landed. Do NOT resubmit with a fresh id.)5POSTED — Permitted. The policy in force allows this action, and the entries6were posted to the ledger.7
Amounts are integer minor units — cents, not dollars — and the entries must balance per currency. The CLI minted the transaction id, told you before submitting, and handled the policy pin (and the re-pin, if policy had moved) for you; the typed clients — axorum-client, @axorum/client, and axorum-client on PyPI — do the same for programs.
5. Get refused on purpose
Now the part worth the walk. Activate a policy that forbids the same action — a stricter rule outweighing the permit:
1cat > forbid.ax <<'EOF'2policy {3 role clerk4 action post()5 party clerk_bot agent roles(clerk)6 party overseer principal7 delegation clerk_grant {8 principal overseer9 agent clerk_bot10 scope { actions(post) roles(clerk) window(0, 1000000) }11 }12 rule permit_post { permitted any_in_role(clerk) do post priority 1 }13 rule forbid_post { forbidden any_in_role(clerk) do post priority 10 }14}15EOF1617axorum policy submit forbid.ax --activate1819axorum intent submit \20 --debit $CASH:80000 \21 --credit $REVENUE:80000 \22 --currency USD --action post \23 --justification "renew it a second time"24
1transaction: txn_01kxaqtng5eb1882m0nmp6kz782REFUSED — nothing posted. The policy in force forbids this action, and the3refusal itself is now recorded on the ledger. This is a verdict, not a4failure: the system worked exactly as intended.5
The command exits 1 — the ledger answered, and it said no. Nothing moved, and that is not an error: the refusal was recorded, permanently, with the agent, the action, the justification, and the policy that forbade it. Ask the ledger to explain itself:
1axorum report transaction txn_01kxaqtng5eb1882m0nmp6kz782
1Transaction at clock-beat 41: agent clerk_bot attempted to post.2Outcome: forbidden and rejected — no money moved, but the attempt itself3was written down.4The rule that said no: forbid_post — Any party in the role 'clerk' is5forbidden to perform 'post'. (authored priority 10).6Acting under the authority overseer delegated to clerk_bot.7Governed by the policy in force (activated at clock-beat 41).8
Engineering reading
Branch on the verdict, never on the HTTP status — in the CLI's terms: exit 0 is a post, exit 1 is a recorded refusal, and exit 3 is a fault (the request itself failed — bad attestation, unknown agent, unreachable service). A refusal is a successful judgment. axorum tx get <txn> replays any stored outcome, including refusals, without resubmitting.
Finance reading
This record is the point. A conventional stack shows you the payments that happened; this one also shows you the payment that did not — and axorum report transaction reads it back the way a memo to the audit committee would: what was attempted, which rule said no, and under whose delegated authority. Evidence that a control operated before the money moved, not an alert filed after it.
Where to go next
- The dual-law commit — why both laws are checked in one step.
- Verdicts — all five outcomes, in both registers.
- The SDK reference — the typed clients and the frozen wire contract.