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.
cargo install axorum-serve
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.
admin_token = "quickstart-admin-token"
tick_interval_ms = 1000
[service]
name = "axorum-serve"
port = 8791
Flat keys come first
The top-level keys must appear before the [service] header. TOML folds any bare key that follows a table header into that table — an admin_token placed after [service] silently becomes service.admin_token, the real one defaults to empty, and an empty token rejects every admin request.
axorum-serve &
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:
cargo install axorum-cli
export AXORUM_BASE_URL=http://127.0.0.1:8791
export AXORUM_ADMIN_TOKEN=quickstart-admin-token
axorum health
http://127.0.0.1:8791 is reachable
no policy is in force — the ledger will not judge an intent until one is
healthy
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.
cargo install agent-uri-cli
agent-uri key generate --out treasurer.key
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:
export AGENT_URI="agent://quickstart.example/books/post/clerk_01h455vb4pex5vsknk084sn02q"
TOKEN=$(agent-uri attest issue --key treasurer.key \
--agent "$AGENT_URI" --capability post --ttl 90d)
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.
agent-uri key public --key treasurer.key | axorum trust add quickstart.example --public-key -
CASH=$(axorum account open --currency USD --label "Cash")
REVENUE=$(axorum account open --currency USD --label "Revenue")
axorum agent bind "$AGENT_URI" --party clerk_bot --roles clerk
trusted 'quickstart.example' — the ledger will now accept attestations it signed
opened account acc_01kxaqsw4qez0a05wvtf9bxr5q (USD)
opened account acc_01kxaqsw4xe4srt5x555n5vtxm (USD)
bound agent://quickstart.example/books/post/clerk_01h455vb4pex5vsknk084sn02q to party 'clerk_bot'
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:
cat > policy.ax <<'EOF'
policy {
role clerk
action post()
party clerk_bot agent roles(clerk)
party overseer principal
delegation clerk_grant {
principal overseer
agent clerk_bot
scope { actions(post) roles(clerk) window(0, 1000000) }
}
rule permit_post { permitted any_in_role(clerk) do post priority 1 }
}
EOF
POLICY=$(axorum policy submit policy.ax --activate)
The service compiled the policy. In plain English, it says:
This policy declares one role, 'clerk', and one action: 'post', which takes
no parameters. 'clerk_bot' is an agent acting in the role 'clerk'. …
Rule 1 — permit_post. Any party in the role 'clerk' is permitted to
perform 'post'. (authored priority 1).
policy pol_1rfe5d5mjxa1g9as27grckn849 is now in force
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.
claude mcp add axorum \
--env AXORUM_BASE_URL=http://127.0.0.1:8791 \
--env AXORUM_AGENT_URI="$AGENT_URI" \
--env AXORUM_ATTESTATION="$TOKEN" \
-- npx -y @axorum/mcp
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:
export AXORUM_AGENT_URI="$AGENT_URI"
export AXORUM_ATTESTATION="$TOKEN"
axorum intent submit \
--debit $CASH:120000 \
--credit $REVENUE:120000 \
--currency USD --action post \
--justification "renew the burst-capacity reservation"
transaction: txn_01kxaqt8rge99tvtjmz62dcm6t
(minted for you — it is the ledger's idempotency key. If this command is
interrupted, run 'axorum tx get' with that id to find out whether the
intent landed. Do NOT resubmit with a fresh id.)
POSTED — Permitted. The policy in force allows this action, and the entries
were posted to the ledger.
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:
cat > forbid.ax <<'EOF'
policy {
role clerk
action post()
party clerk_bot agent roles(clerk)
party overseer principal
delegation clerk_grant {
principal overseer
agent clerk_bot
scope { actions(post) roles(clerk) window(0, 1000000) }
}
rule permit_post { permitted any_in_role(clerk) do post priority 1 }
rule forbid_post { forbidden any_in_role(clerk) do post priority 10 }
}
EOF
axorum policy submit forbid.ax --activate
axorum intent submit \
--debit $CASH:80000 \
--credit $REVENUE:80000 \
--currency USD --action post \
--justification "renew it a second time"
transaction: txn_01kxaqtng5eb1882m0nmp6kz78
REFUSED — nothing posted. The policy in force forbids this action, and the
refusal itself is now recorded on the ledger. This is a verdict, not a
failure: the system worked exactly as intended.
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:
axorum report transaction txn_01kxaqtng5eb1882m0nmp6kz78
Transaction at clock-beat 41: agent clerk_bot attempted to post.
Outcome: forbidden and rejected — no money moved, but the attempt itself
was written down.
The rule that said no: forbid_post — Any party in the role 'clerk' is
forbidden to perform 'post'. (authored priority 10).
Acting under the authority overseer delegated to clerk_bot.
Governed by the policy in force (activated at clock-beat 41).
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
- 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.