Architecture
A walking tour of how Axon's pieces fit together.
The pieces
~/.axon/
├── keystore.json (encrypted)
├── policy.json.enc (encrypted)
├── audit.db (SQLite, hash chain)
├── session.token (bearer, 0600)
├── config.json (overlay)
├── daemon.pid (single-process lock)
└── strategies/ (installed)
┌─────────────────────┐ stdio ┌─────────────────────────────┐
│ AI agent │◄──────────►│ axon mcp (MCP server) │
│ • Claude Code │ │ • ~35 typed tools │
│ • Cursor / Cline │ │ • Validates inputs │
│ • Codex / Codex │ │ • Calls HTTP API │
│ • Continue / etc. │ └──────────┬───────────────────┘
└─────────────────────┘ │
│ HTTP, bearer token,
│ same-machine only
▼
┌─────────────────────┐ HTTP/SSE ┌─────────────────────────────┐
│ Dashboard (React) │◄──────────►│ Daemon (Fastify, Node) │
│ • Vite SPA │ │ │
│ • RainbowKit │ │ ┌───────────────────────┐ │
│ • Lightweight │ │ │ Keystore (in-memory) │ │
│ Charts │ │ │ Policy engine │ │
│ • Live SSE events │ │ │ Risk guard │ │
└─────────────────────┘ │ │ Audit log writer │ │
│ │ Event bus (in-proc) │ │
│ └──────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ Venue adapters │ │
│ │ • Hyperliquid (HL) │ │
│ │ • Lighter │ │
│ │ • Uniswap V3 │ │
│ │ • Aave V3 │ │
│ │ • GMX v2 (read-only) │ │
│ └─────┬─────────────────┘ │
└─────────┼────────────────────┘
│ EIP-712 signed
▼
┌─────────────────────────────┐
│ External chains / venues │
│ Arbitrum, HL, Lighter, ... │
└─────────────────────────────┘
Process model
Axon runs as a single Node.js process (the daemon) plus N short-lived MCP subprocesses (one per agent runtime). Specifically:
axon/ Axon Desktop spawn the daemon → 1 long-running processaxon mcp(called by agents) spawns one MCP server subprocess → 1 per agent- The MCP server is a thin client; it forwards tool calls to the daemon over HTTP
The desktop app embeds the daemon in the Electron main process — no separate process, no IPC. Same code path as the CLI's start().
Port layout
| Component | Bind | Port | Auth |
|---|---|---|---|
| Daemon HTTP API | 127.0.0.1 | 47890 (default; configurable via AXON_PORT) | Bearer token (most routes) |
| Daemon SSE | 127.0.0.1 | 47890 (same as HTTP) | Bearer token |
| MCP server | stdio | — | Reads bearer from ~/.axon/session.token |
| Dashboard SPA | Same as daemon | 47890 | Token in URL fragment |
The dashboard is served as static assets by the daemon — there's no separate web server. This keeps the auth model simple: same-origin, bearer-token-gated, localhost only.
Daemon lifecycle
┌─────────────────────────────────────────────┐
│ start() │
│ 1. acquire ~/.axon/daemon.pid lock │
│ 2. mkdir ~/.axon/ + chmod 0700 │
│ 3. open audit.db, verify chain │
│ 4. construct EventBus │
│ 5. construct venue adapters │
│ 6. mount HTTP routes │
│ 7. listen on 47890 │
│ 8. (in --live mode) validate config │
│ 9. write session.token + agent.env │
└─────────────────────────────────────────────┘
Keystore: starts LOCKED
Dashboard's unlock form drives → POST /v1/keystore/unlock → keystore.unlock(passphrase)
│
▼
Now UNLOCKED.
Policy loaded.
Settlement worker started (in --live).
/v1/service/restart → process.exit(42) → supervisor respawns
(CLI) or restartHook() → in-process restart (Desktop)
In-process vs supervisor restart
- CLI:
axonruns as a supervisor + child process pair. The child callsprocess.exit(42)to signal restart; the supervisor respawns a fresh child. - Desktop: The Electron main process IS the daemon's host. A
process.exit(42)would kill the whole app. Instead, the daemon's restart endpoint calls arestartHookcallback that doesstop()+start()in-process.
Both paths end with a fresh daemon + a re-issued bearer token. The dashboard recovers via /v1/session/refresh (localhost-only, no auth).
Event bus
Every audit-log append also publishes an AxonEvent to an in-process EventBus. Subscribers:
- Dashboard SSE at
/v1/events— fans out to connected dashboard tabs - MCP
watch_*tools — agents poll for state changes between turns - Notification bridge (desktop) — fires native OS notifications on critical events (kill switch, errors)
The audit log is canonical; the bus is a live tap. Subscribers can disconnect/reconnect without missing state — they query the audit log to backfill.
Strategies + marketplace
Strategies are signed JSON manifests installed at ~/.axon/strategies/. The daemon's strategy store loads them at boot, verifies signatures against the pinned Strykr ed25519 public key (or marks them unsigned), and exposes them via /v1/strategies/*.
The marketplace at strategies.axonterminal.ai is a separate Vercel project; the daemon's catalog refresher fetches new strategies daily and signature-verifies before caching.
Where to dig next
- CLI source:
packages/cli/src/ - MCP server:
packages/adapters/mcp/ - Dashboard:
packages/dashboard/ - Desktop app:
packages/desktop/ - SDK:
packages/sdk/
Each src/ has a top-level README and inline comments explaining design choices.