axis

module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 13, 2026 License: MIT

README

AXIS

CI Go version

A snapshot-first Go CLI that discovers hardware facts across your cluster via SSH, builds a ClusterSnapshot, and makes deterministic reservation-aware placement decisions. Optional chat, HTTP, and MCP surfaces are subordinate to observed state, not sources of truth.

Quick Start

# Install
go install github.com/toasterbook88/axis/cmd/axis@latest

# Inspect the local machine
axis facts

# Inspect the full cluster (requires ~/.axis/nodes.yaml)
axis status

# Ask where to run a task
axis task place "run ollama inference on a 7b model"

Truth Boundary

No generated output may present itself as cluster truth unless it is backed by a real snapshot or live probe.

  • The stable operator path is axis facts, axis status, axis task place, axis task context, and the daemon cache commands.
  • axis chat is experimental and non-authoritative.

Command Surface

Stable operator path
  • axis version — print the current AXIS build version
  • axis facts — local hardware/tool snapshot
  • axis status / axis status --cached — live or daemon-cached cluster snapshot
  • axis task place / axis task place --cached — advisory placement with reasoning
  • axis task context / axis task context --cached — compact context block backed by live or cached snapshot data
  • axis daemon status / refresh / invalidate / restart — inspect and manage the local snapshot cache seam
  • axis context show / clear — inspect or reset persisted placement memory
Optional or secondary surfaces
  • axis task run — explicit execution surface layered on top of placement, with optional observed-state-gated Nix helper wrapping when the selected node proves it supports nix
  • axis serve — optional local HTTP API surface
  • axis mcp serve — optional read-only MCP server over stdio
  • axis scripts list — list built-in helper scripts
  • axis skills — show learned local skills/failures
  • axis chat — cluster-aware chat assistant using Ollama /api/chat; advisory only
  • axis agent — agentic tool-calling assistant with read-only cluster tools and operator-confirmed shell execution
  • axis completion — Cobra-generated shell completion
Available Commands (Strict Config Enforced)
  • axis version
  • axis facts — local facts only
  • axis status — full cluster snapshot (uses strict nodes.yaml)
  • axis task place <description> — deterministic placement

Removed: axis discover (fully replaced by axis status + snapshot assembly).

Execution Safety (hardened)

  • CLI execution requires axis task run --script ... or axis task run --exec ...; HTTP /run and axis_execute require explicit mode=script|exec plus confirm: "YES"
  • Hardened safety blocker with allow-list, cluster-aware RAM/GPU checks, and learned-bad fast path
  • Per-node reservation caps enforced before any command runs (RAMTotalMB - 1024 headroom)
  • Live and cached read paths both overlay local reservations before placement, context generation, or execution
  • axis task run / /run now export AXIS_TURBOQUANT_* env hints to executed commands when a TurboQuant-capable node is selected
  • Corrupt local ~/.axis/state.json / ~/.axis/skills.json files are quarantined to .corrupt-* backups and surfaced as warnings instead of crashing read paths
  • Reservations auto-clean on every daemon refresh:
    • guarded executions now write explicit per-exec heartbeats while they are alive
    • reservation entries record their owning local AXIS PID, internal caller provenance (task-run, http-run, agent-run-shell), and the observed local AXIS execution origin (node name / hostname / stable ID when available)
    • HTTP execution surfaces can also accept a signed forwarded upstream origin when the caller proves possession of the shared AXIS API token
    • heartbeat-stale exec reservations are reclaimed after about 2 minutes
    • legacy no-heartbeat state still falls back to the older 45-minute/24-hour cleanup path
    • no-exec entries are discarded immediately
  • All cached paths (task place --cached, task context --cached, mcp serve --cached) use the single internal/daemon/client
  • Visible in /snapshot/meta as reserved_mb
  • axis serve now watches ~/.axis/nodes.yaml, ~/.axis/state.json, and ~/.axis/skills.json for semantic/content changes and surfaces the resulting last_refresh_trigger in daemon metadata/health; heartbeat-only state.json writes are ignored so active executions do not force full cache refresh churn
  • axis task run and HTTP /run now emit best-effort execution-reserved / execution-finished refresh signals so the default local daemon cache can react immediately to real execution-state changes
  • axis agent now routes approved run_shell calls through the same guarded AXIS execution path as other execution surfaces, preferring the local AXIS /run daemon/API hop when available so signed upstream origin survives that boundary; agent-triggered commands still participate in placement, reservation tracking, heartbeats, caller provenance, and refresh signaling
  • axis task run now also prefers the local AXIS daemon/API /run hop when available, using a streamed NDJSON execution contract so placement readiness and live stdout/stderr survive the boundary instead of forcing a local-only execution path
  • The streamed /run contract now also carries explicit execution state-change events, and early runtime/load failures now surface as the normal final result event instead of unexpectedly falling back to buffered JSON
  • Local daemon /run clients now use a dedicated long-lived execution transport instead of inheriting the short snapshot/meta HTTP timeout, so daemon-hop executions can run for the full caller context
  • When UDP discovery is enabled, axis serve also runs a long-lived beacon watcher so new/changed/expired beacon-derived nodes can trigger beacon-change cache refreshes without waiting for the normal snapshot timer
  • Live and cached snapshots now carry a typed freshness contract describing the discovery source, expected/observed beacon window, additive beacon-node count, completion state, and any degraded-confidence warning
  • Guarded execution now records exact-scope empirical observations (node, workload, backend, tool) separately from failure memory so later placement can prefer fresh successful history without treating "slow" as "broken"

Degraded-State Behavior

Local condition CLI behavior API/MCP behavior File outcome
Corrupt ~/.axis/state.json axis context show warns on stderr and still prints valid JSON live snapshot-bearing reads keep working; state warning appears in snapshot warnings or placement reasoning original file is renamed to state.json.corrupt-<UTCSTAMP> and a clean in-memory state is used
Corrupt ~/.axis/skills.json axis skills warns on stderr and still prints valid JSON live API/MCP reads keep working; skills warning appears in snapshot warnings or placement reasoning original file is renamed to skills.json.corrupt-<UTCSTAMP> and a clean in-memory skills store is used

These files are local operator memory, not authoritative cluster truth. AXIS now prefers warn + quarantine + continue over failing shut on read paths.

Features

Feature Details
Local fact collection OS, kernel, arch, CPU cores/model, RAM (total/free + load averages + pressure), disk (total/free + storage class: nvme/ssd/hdd), structured GPU info (vendor/model/VRAM/capabilities), network addresses (with interface name, subnet, heuristic speed class), battery %, thermal state, and additive memory-topology / pressure-source metadata where available
Tool inventory go, python3, git, docker, ollama, mlx_lm, llama-cli, llama-server, node, swift, cargo, gcc, plus probe-verified local apple-foundation-models on eligible Apple Silicon hosts running macOS 26 or later
SSH cluster sweep Concurrent fan-out over all configured nodes; per-node timeout
ClusterSnapshot Structured JSON/YAML with per-node status (complete / partial / unreachable / error), cluster-level aggregates, and additive discovery freshness metadata for both live and cached reads
Advisory task placement axis task place ranks nodes deterministically by allocatable RAM first, then fresh exact-scope empirical history, resident-model locality, backend/GPU/headroom tie-breaks, and reservation spread; heavy AI tasks still avoid nodes under critical runtime pressure signals
Optional local control surfaces axis serve, axis daemon invalidate, axis mcp serve, axis task run, axis chat, and axis agent are available when explicitly invoked
Single-binary operation No required daemon, database, or background process; local server/MCP surfaces are opt-in
Structured output axis facts and axis status support JSON/YAML; axis task place supports human output and JSON

Installation

1. Quick Install (macOS / Linux)

The fastest way to install AXIS is using our install script. It automatically detects your OS/Arch, downloads the latest release tarball securely verified against checksums.txt, and places it in ~/.local/bin.

curl -fsSL -o install.sh https://raw.githubusercontent.com/toasterbook88/axis/main/install.sh
less install.sh
bash install.sh
rm -f install.sh
2. Nix Flakes (NixOS / macOS)

AXIS offers native, reproducible Nix support.

# Install to your profile
nix profile install github:toasterbook88/axis

# Or run instantly without installing
nix run github:toasterbook88/axis

Note for contributors: Run nix develop to enter a reproducible devShell containing the matching Go toolchain and required utilities.

3. Homebrew (macOS / Linux)

Note: Homebrew Tap automation is currently pending. For now, please use the Quick Install script above.

For Developers (Build from Source)

If you need unreleased main-branch changes or specifically want to use the Go ecosystem, you can compile from source. Requirements: Go 1.26.1+ (use the latest 1.26 patch release), SSH key-based auth for remote nodes.

Using go install:

go install github.com/toasterbook88/axis/cmd/axis@latest

Manual Compilation:

git clone https://github.com/toasterbook88/axis.git
cd axis
go build -o axis ./cmd/axis/
# Optional: move to $PATH
mv axis /usr/local/bin/axis
Updating AXIS

AXIS includes a built-in self-updater via the axis update command.

[!WARNING] If you installed AXIS via a package manager (like Nix or Homebrew), DO NOT use axis update to upgrade the binary. Doing so attempts an in-place executable swap which violates immutable system paths and desyncs your package manager's state. AXIS is package-manager aware and will politely refuse to upgrade itself, instructing you to use your respective tool (e.g. nix profile upgrade, brew upgrade).

To safely check if there is a newer version available without triggering an upgrade, run:

axis update --check

Important Notes for axis update on Quick Install / Source builds: By default, axis update safely scopes its upgrade only to the currently executing binary to prevent cross-contamination in mixed dev/prod PATH environments. If you want it to automatically upgrade all other axis binaries it finds in your $PATH, pass the --all flag.

Release Pipeline & Security

Tagged release pipeline:

  • v* tags are published through GitHub Actions and GoReleaser
  • Release artifacts are configured for darwin/linux on amd64/arm64
  • The release workflow refuses to publish if the pushed tag and internal/buildinfo/version.go disagree
  • Published releases are listed on the GitHub Releases page

Security hygiene:

  • Weekly Dependabot updates cover Go modules and GitHub Actions
  • govulncheck runs on pull requests, pushes to main, and a weekly schedule
  • Private vulnerability reporting and automated security fixes are enabled on GitHub
  • Security reporting guidance lives in SECURITY.md

Usage

axis facts — local machine snapshot
axis facts               # JSON (default)
axis facts --format yaml # YAML
axis status — cluster snapshot

Create ~/.axis/nodes.yaml (see nodes.example.yaml):

nodes:
  - name: node-a
    hostname: node-a.local
    # stable_id: f47ac10b-58cc-4372-a567-0e02b2c3d479
    ssh_user: alice
    role: primary

  - name: node-b
    hostname: node-b.local
    ssh_user: alice
    role: worker
    # ssh_port: 22
    # timeout_sec: 10

Then:

axis status              # JSON cluster snapshot
axis status --format yaml
axis status --cached     # read explicit daemon cache instead of live SSH sweep
axis task place — advisory placement
axis task place "analyze a git repo"
# → Selected node: node-b (remote, fit 82/100)
#   Tool: git
#   Reason:
#     - has required tool: git
#     - free RAM: 14336 MB

axis task place "run ollama inference on a 7b model" --format json
axis task place --cached "run ollama inference on a 7b model"

Placement uses keyword matching against the task description (no ML). It infers the required tool (ollama, git, go, docker) and minimum free RAM from specific keywords (model, 7b, inference, heavy, etc.), then scores each reachable node. Tool presence is a hard requirement, and eligible nodes are ranked by allocatable RAM first, then fresh exact-scope empirical history, resident-model locality, backend/GPU/headroom tie-breaks, reservation spread, storage class (HDD penalized for heavy loads), and stable name ordering.

AXIS now treats allocatable RAM as min(live free RAM, total RAM minus a protected 1GB system reserve) - local reservations, so cached reads, placement, and last-second execution admission use the same headroom model. When allocatable and per-node reservation ratios tie, placement also prefers the node holding a smaller share of the current cluster reservation pool, which makes RAM spreading more explicit instead of repeatedly picking the same machine on late ties.

When UDP discovery is enabled, both live and cached snapshot surfaces now also expose a typed freshness contract instead of only a free-form warning string. That contract reports whether the expected beacon window completed, how much of it was actually observed, and how many additive beacon-derived nodes were included.

For Ollama-shaped tasks, observed resident_models from ollama ps now provide a truth-backed warm-runtime locality hint, and guarded execution now feeds back exact-scope observations (wall time always, RAM/VRAM peaks only when directly observed) into later placement decisions.

Long-context hints such as 128k, book-length, or million-token also trigger a TurboQuant-aware preference when a node exposes mlx or llama.cpp-style backends, with stronger RAM reduction and fit bonuses reserved for recognizable backend help/probe responses. For heavy inference tasks, AXIS filters out nodes showing critical runtime pressure, thermal throttling, or low battery (< 20%), and respects tombstone blacklists for task+node combinations that have repeatedly crashed.

When axis task run selects a TurboQuant-capable node, AXIS exports AXIS_TURBOQUANT, AXIS_TURBOQUANT_STATUS, AXIS_TURBOQUANT_BACKENDS, AXIS_TURBOQUANT_CAPABILITIES, and long-context hints into the execution environment. For probe-verified llama.cpp commands with --ctx-size support, AXIS can also inject safe additive flags such as --ctx-size and --flash-attn when they are absent.

Experimental local Apple Foundation Models execution is available on eligible Apple Silicon machines running macOS 26 or later through the source-visible Swift helper in hack/apple-foundation-models.swift:

axis task run --exec 'xcrun swift hack/apple-foundation-models.swift --prompt "Summarize this text"'

AXIS treats apple-foundation-models as a local-only verified capability. Remote nodes are excluded for that backend instead of being treated as fallback candidates.

With --cached, placement uses the explicit daemon snapshot cache instead of a fresh SSH sweep. JSON output includes a source wrapper so you can tell whether the decision came from daemon-cache or live fallback.

axis task context — compact operator/agent prompt block
axis task context "test inference"
axis task context --cached "test inference"

--cached uses the explicit daemon snapshot cache and includes a Source: line in the rendered context block so you can tell where the prompt data came from.

axis serve — optional local HTTP API
axis serve

Starts the local AXIS HTTP API and execution surface on 127.0.0.1:42425 by default, plus a background snapshot refresh loop that powers the explicit cached-read path.

axis daemon invalidate — clear local daemon cache
axis daemon invalidate
axis daemon invalidate --cache-addr 127.0.0.1:42425

Clears the daemon-backed snapshot cache explicitly. This does not change the default axis status live path; it only affects cached reads and other daemon-backed surfaces.

axis daemon refresh — force a fresh daemon snapshot now
axis daemon refresh
axis daemon refresh --cache-addr 127.0.0.1:42425

Forces the daemon to rebuild its cached snapshot immediately. This is the fastest way to ensure axis status --cached and axis task place --cached use fresh cluster state without waiting for the next background tick.

axis daemon status — inspect local daemon freshness
axis daemon status
axis daemon restart

axis daemon status reports cache readiness, age, and version metadata. axis daemon restart restarts the local cache seam from the current binary when you need to refresh stale daemon state explicitly.

axis update — self-update to the latest release
axis update           # download and install the latest release (only replaces current binary)
axis update --all     # replace ALL discovered `axis` binaries in your $PATH
axis update --check   # report whether an update is available (no download)

Checks GitHub Releases for a newer version, downloads the matching platform binary, verifies its SHA-256 checksum against the release's checksums.txt, and replaces the current binary in-place. The checksum is always verified — the release workflow produces checksums.txt alongside every archive.

Note: if your copy of axis was installed using a package manager like nix or homebrew, axis update will refuse to perform an in-place replacement and will direct you to update using your package manager natively instead.

Configuration Reference

~/.axis/nodes.yaml fields:

Unknown YAML keys are rejected at load time so config typos fail fast instead of being silently ignored.

Field Required Default Description
name yes Logical node name
hostname yes Resolvable hostname or IP
stable_id no Optional observed machine identity (machine-id / platform UUID) used for locality and discovery dedupe
ssh_user yes SSH username
role no primary or worker
ssh_port no 22 SSH port
timeout_sec no 10 Per-node collection timeout (seconds)

Optional discovery block used by experimental UDP-assisted discovery:

Field Required Default Description
discovery.enabled no false Enable UDP beacon discovery alongside configured nodes
discovery.udp_port no 42424 UDP beacon port
discovery.beacon_interval_sec no 3 Beacon broadcast interval
discovery.secret no empty Shared discovery secret for filtering beacons

When a host exposes a stable identity, AXIS includes it in UDP beacons and uses it only to avoid duplicate nodes or preserve an explicit config binding when names or IPs drift.

Architecture

┌─────────────────────┬─────────────────────────────────────────────────────────────────────────────────┐
│       Package       │                                      Role                                       │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ cmd/axis/           │ Cobra CLI entry — chat, facts, status, task, serve, context, scripts, skills   │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/config/    │ Loads ~/.axis/nodes.yaml (node list, SSH user/port/timeout)                     │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/facts/     │ SSH into each node, collects RAM/CPU/GPU/tools                                  │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/placement/ │ Filter + rank nodes by free RAM, pressure, GPU capability, storage class, thermal state, tombstones, locality; ComputeFitScore 0–100 │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/chat/      │ Structured /api/chat client with rolling context window and system prompt      │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/agent/     │ Tool-calling agent loop with read-only tools and safety-gated shell execution   │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/snapshot/  │ Assembles `ClusterSnapshot` from `[]NodeFacts`                                  │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/daemon/    │ Background snapshot refresh, in-memory cache, and explicit invalidation         │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/state/     │ Persists local placement memory and execution state                              │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/api/       │ Local HTTP API and execution surface                                             │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/mcp/       │ Read-only MCP server over stdio                                                  │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/transport/ │ Raw SSH execution layer                                                         │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/discovery/ │ Node discovery                                                                  │
├─────────────────────┼─────────────────────────────────────────────────────────────────────────────────┤
│ internal/models/    │ Shared types: NodeFacts, TaskRequirements, Locality                             │
└─────────────────────┴─────────────────────────────────────────────────────────────────────────────────┘
Key design notes
  • Config lives at ~/.axis/nodes.yaml — no cluster IPs hardcoded in code
  • Placement is deterministic: RAM pressure → GPU → effective headroom → allocatable RAM → reservation ratio → name
  • ComputeFitScore factors in GPU (+25pts) and local-node bonus (+10pts) — M1↔M3 RAM sharing would be relevant here
  • Chat uses the Ollama /api/chat endpoint with structured messages and a rolling context window; defaults to localhost:11434
  • axis agent provides a tool-calling loop with read-only cluster tools (status, facts, place) and safety-gated shell execution; --auto-approve enables automatic execution for low-risk commands
  • axis serve hosts an optional daemon-backed cache; axis status --cached, axis task place --cached, axis task context --cached, axis daemon refresh, and axis daemon invalidate use it explicitly
  • The daemon cache now refreshes immediately on config/state/skills content changes, explicit execution-state signals, and long-lived UDP beacon changes, in addition to its normal timer loop
  • axis serve and axis mcp serve are optional local surfaces, not required infrastructure
  • axis chat and axis agent are advisory helpers and must not outrank snapshot-backed truth
  • Placement memory lives locally in ~/.axis/state.json

Current phase: The observability and placement core is stable. Chat and agent surfaces use structured /api/chat with tool calling and safety gates, but remain subordinate to observed state and must not present model output as authoritative cluster truth.

See Phase 1 Spec and White Paper for detailed design notes.

Roadmap

The following are planned directions beyond the functionality shipped in v0.7.0:

  • Mesh networking / peer discovery beyond a static seed file
  • Future roadmap phases beyond the current runtime-hardening wave — see white paper and future roadmap
Phase 3 (Shipped in v0.4.0) · Phases 4–7 (Shipped in v0.7.0)

The following features were delivered in Phase 3:

  • nodes.yaml hot-reload — daemon detects config changes and re-discovers nodes without restart
  • Daemon refresh metrics/health reports refresh_count, last_refresh_duration_ms, stale_nodes
  • Graceful shutdownaxis serve drains in-flight work before exit
  • axis task context --format json — machine-readable context block with fit score, skills, and recent decisions
  • HMAC-SHA256 beacon auth — UDP discovery signs beacons instead of transmitting the shared secret

Phases 4–7 are now shipped in v0.7.0, including professional CLI UX, structured chat and agent surfaces, transport and execution hardening, and the trust-and-foundations work tracked in the roadmap docs.

Contributing

See CONTRIBUTING.md. Keep PRs small and focused; open an issue before adding major new roadmap phases or control surfaces.

License

MIT — see LICENSE.

Directories

Path Synopsis
cmd
axis command
Package main is the CLI entry point for AXIS.
Package main is the CLI entry point for AXIS.
internal
api
config
Package config loads AXIS node configuration from ~/.axis/nodes.yaml.
Package config loads AXIS node configuration from ~/.axis/nodes.yaml.
discovery
Package discovery enumerates configured nodes and collects facts.
Package discovery enumerates configured nodes and collects facts.
facts
Package facts collects hardware and software facts from nodes.
Package facts collects hardware and software facts from nodes.
mcp
models
Package models defines core AXIS data types.
Package models defines core AXIS data types.
placement
Package placement implements deterministic task placement logic reused by both advisory selection and guarded execution.
Package placement implements deterministic task placement logic reused by both advisory selection and guarded execution.
snapshot
Package snapshot assembles a ClusterSnapshot from collected NodeFacts.
Package snapshot assembles a ClusterSnapshot from collected NodeFacts.
transport
Package transport provides command execution abstractions.
Package transport provides command execution abstractions.
ui
Package ui provides colored, table, and spinner output for the AXIS CLI.
Package ui provides colored, table, and spinner output for the AXIS CLI.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL