Skip to content

About

Distributed worker runtime for AI coding agents: dispatch goal-shaped jobs to model-driven workers over authenticated SSH tunnels, with spend caps and telemetry.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

npm version CI npm downloads Node.js License

Axon Runtime

A distributed worker runtime for AI coding agents. A controller dispatches goal-shaped jobs ("fix this test", "add this endpoint") to worker services on other machines. Each worker runs its own tool-calling model loop inside a disposable git worktree and returns a patch or a report, with telemetry and cost accounting, under hard step, time, and spend limits.

Axon is how the Equational Applications agent fleet spreads spec-driven implementation work across spare laptops. This repository is the generic runtime; machine inventory, tokens, and network topology live in a separate private deployment repo.

Status: v0.1. Dispatch works end to end over SSH tunnels, with Linux and macOS workers running under dedicated OS users. Windows onboarding and metrics-driven cross-node placement are in progress.

How it works

 controller                                     worker node (one per machine)
┌──────────────────────┐   SSH -L tunnel    ┌──────────────────────────────────┐
│ axon CLI             │ ─────────────────▶ │ Fastify service on 127.0.0.1     │
│  dispatch / status   │   bearer token     │  ├─ validate: contract, allowlist│
│  cancel / artifact   │                    │  │  size + spend ceilings        │
│                      │ ◀───────────────── │  ├─ JobPool: concurrency, cancel │
│ Ledger (SQLite)      │  result envelope   │  ├─ BudgetGuard: daily USD cap   │
│  per-node metrics    │  + telemetry       │  ├─ git mirror → worktree per job│
└──────────────────────┘                    │  ├─ Harness: model ⇄ run_shell   │
                                            │  └─ JobStore (SQLite), artifacts │
                                            └──────────────────────────────────┘
                                                      │
                                                      ▼
                                   any OpenAI-compatible /chat/completions endpoint
  1. The controller sends a versioned job request: repo, ref, goal, optional context files, constraints (max_harness_steps, timeout_seconds, max_spend_usd), and the output it wants (patch or report).
  2. The worker validates it with Zod, checks the repo allowlist and the node's ceilings, and admits it against the node's daily spend cap, or returns a typed rejection (repo_not_allowlisted, queue_full, budget_exhausted, …).
  3. The worker checks the ref out into a fresh worktree from a shared bare mirror, then runs a tool loop against the configured model endpoint. The only tool is run_shell, scoped to the worktree.
  4. Every model step is written to a durable spend ledger before the job's cost checkpoint, so the daily cap never undercounts, even across crashes.
  5. The job ends in an envelope: status, exit reason (completed, step_cap, timeout, budget_exceeded, cancelled, …), the diff (inline or as an artifact), and telemetry (tokens, cost estimate, duration, model, runtime and harness versions).
  6. The controller records every dispatch and envelope in its own ledger. Routing stays explicit: every dispatch names its target node.

Security model

  • Loopback only. Workers bind to 127.0.0.1; the only way in is an SSH port forward.
  • Bearer auth on every route except /healthz, compared in constant time.
  • Allowlisted child environment. The agent's shell sees only PATH, HOME, LANG, LC_ALL, TZ, and TMPDIR. SSH_AUTH_SOCK is excluded on purpose, so a job can't authenticate as the operator.
  • No push access. Work comes back as a diff; the worker never pushes.
  • Secrets by reference. Config files name environment variables (bearer_token_env, api_key_env); secrets never live in config.
  • Bounded input and output. Goal, context, and inline patch sizes are capped per node.
  • Crash recovery. On startup, jobs left running are marked interrupted, orphaned worktrees are removed, and expired artifacts are purged.
  • OS containment is left to the deployment. Run the worker as a dedicated, non-login OS user.

Quick start

Requires Node.js 22+ and git.

better-sqlite3's implicit node-gyp install step is approved in package.json (allowScripts), which npm 11.19+ consults before running dependency install scripts (older npm runs them regardless). The step is a no-op wherever better-sqlite3 ships a prebuilt binary (macOS, Linux glibc and musl, Windows) and compiles SQLite where it does not (Android/Termux). On Windows a clean install can still invoke node-gyp rebuild before the prebuilt check (better-sqlite3#1516), so Windows consumers need Python and the MSVC build tools until npm honors gypfile: false on clean installs (npm/cli#9837). npm reads allowScripts only from the project you install into, so if you depend on axon-runtime, add the entry to your own package.json before installing: npm pkg set allowScripts.better-sqlite3=true --json.

npm ci
npm run build

# Worker
export AXON_NODE_TOKEN=$(openssl rand -hex 32)
export AXON_ENDPOINT_KEY=sk-...          # key for the endpoint in node.json
node dist/worker.js examples/node.json   # listens on 127.0.0.1:8787 (AXON_PORT)

# Controller: forward a local port to the worker
ssh -N -L 18787:127.0.0.1:8787 worker-host &
export AXON_TOKEN_NODE_01=$AXON_NODE_TOKEN
export AXON_NODES=examples/nodes.json AXON_LEDGER=./ledger.db

echo "Make the failing date test pass" > goal.md
npx axon dispatch --node node-01 --repo git@github.com:your-org/your-repo.git \
  --goal-file goal.md --wait
npx axon status   --job <job_id>
npx axon artifact --job <job_id> -o patch.diff
npx axon metrics  --node node-01

Set AXON_EXECUTOR=fake on a worker to exercise the whole pipeline without calling a model.

Android (Termux)

To run an Axon worker on a dedicated Android device via Termux:

  1. Install toolchain: pkg update && pkg install nodejs-lts git openssh python clang make -y (python, clang, and make let npm ci compile better-sqlite3, which has no prebuilt binary for Android).
  2. Prevent process kills: Run termux-wake-lock, disable battery optimization for Termux, and bypass Android's Phantom Process Killer via ADB (adb shell device_config put activity_manager max_phantom_processes 2147483647).
  3. Use internal storage: Keep git repos and worktrees inside the Termux home (~) to avoid Android FUSE/scoped-storage symlink issues.
  4. SSH tunneling: Start sshd on Termux (default port 8022) and forward loopback from the controller: ssh -N -L 18787:127.0.0.1:8787 -p 8022 user@android-ip

Worker API

Method Path Purpose
GET /healthz Liveness (no auth)
GET /nodeinfo Node ID, runtime and contract versions, capacity
POST /jobs Submit a job (idempotent on job_id)
GET /jobs/:id Current status or final envelope
POST /jobs/:id/cancel Cancel a running job
GET /jobs/:id/artifact?id= Fetch an artifact such as patch.diff

The contract lives in src/contract/schema.ts (CONTRACT_VERSION = 2).

Development

npm test            # unit + in-process integration tests
npm run typecheck
AXON_SSH=1 npm test   # also runs the real SSH tunnel round trip (needs key-based `ssh localhost`)
AXON_LIVE=1 npm test  # also calls a real model endpoint

Cost figures are estimates from fixed per-token rates in src/harness/adapter.ts. Treat them as budget guards, not billing.

License

MIT © Equational Applications LLC

About

Distributed worker runtime for AI coding agents: dispatch goal-shaped jobs to model-driven workers over authenticated SSH tunnels, with spend caps and telemetry.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages