NEWWatch an agent's payment get refused, and read the reason, in the live browser demo.Open the demo
AxorumAxorum
Reading register

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.

1. Run the service

The service is a single binary against an in-process ledger. No database, no broker.

1cargo install axorum-serve
2

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 = 1000
3
4[service]
5name = "axorum-serve"
6port = 8791
7
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-cli
2
3export AXORUM_BASE_URL=http://127.0.0.1:8791
4export AXORUM_ADMIN_TOKEN=quickstart-admin-token
5
6axorum health
7
1http://127.0.0.1:8791 is reachable
2no policy is in force — the ledger will not judge an intent until one is
3healthy
4

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-cli
2
3agent-uri key generate --out treasurer.key
4

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"
2
3TOKEN=$(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 -
2
3CASH=$(axorum account open --currency USD --label "Cash")
4REVENUE=$(axorum account open --currency USD --label "Revenue")
5
6axorum agent bind "$AGENT_URI" --party clerk_bot --roles clerk
7
1trusted 'quickstart.example' — the ledger will now accept attestations it signed
2opened 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 clerk
4 action post()
5 party clerk_bot agent roles(clerk)
6 party overseer principal
7 delegation clerk_grant {
8 principal overseer
9 agent clerk_bot
10 scope { actions(post) roles(clerk) window(0, 1000000) }
11 }
12 rule permit_post { permitted any_in_role(clerk) do post priority 1 }
13}
14EOF
15
16POLICY=$(axorum policy submit policy.ax --activate)
17
1The service compiled the policy. In plain English, it says:
2
3 This policy declares one role, 'clerk', and one action: 'post', which takes
4 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 to
6 perform 'post'. (authored priority 1).
7
8policy pol_1rfe5d5mjxa1g9as27grckn849 is now in force
9

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/mcp
6

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"
3
4axorum 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_01kxaqt8rge99tvtjmz62dcm6t
2 (minted for you — it is the ledger's idempotency key. If this command is
3 interrupted, run 'axorum tx get' with that id to find out whether the
4 intent landed. Do NOT resubmit with a fresh id.)
5POSTED — Permitted. The policy in force allows this action, and the entries
6were 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 clerk
4 action post()
5 party clerk_bot agent roles(clerk)
6 party overseer principal
7 delegation clerk_grant {
8 principal overseer
9 agent clerk_bot
10 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}
15EOF
16
17axorum policy submit forbid.ax --activate
18
19axorum 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_01kxaqtng5eb1882m0nmp6kz78
2REFUSED — nothing posted. The policy in force forbids this action, and the
3refusal itself is now recorded on the ledger. This is a verdict, not a
4failure: 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_01kxaqtng5eb1882m0nmp6kz78
2
1Transaction at clock-beat 41: agent clerk_bot attempted to post.
2Outcome: forbidden and rejected — no money moved, but the attempt itself
3was written down.
4The rule that said no: forbid_post — Any party in the role 'clerk' is
5forbidden 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.

Where to go next