Skip to main content

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 process
  • axon 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

ComponentBindPortAuth
Daemon HTTP API127.0.0.147890 (default; configurable via AXON_PORT)Bearer token (most routes)
Daemon SSE127.0.0.147890 (same as HTTP)Bearer token
MCP serverstdioReads bearer from ~/.axon/session.token
Dashboard SPASame as daemon47890Token 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: axon runs as a supervisor + child process pair. The child calls process.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 a restartHook callback that does stop() + 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.