aide.sh

Your commander of agents.

aide turns any Claude Code project into an agent with one file. Multiple agents? One HQ to command them all.

Why aide?

The token problem: A frontier Claude Code session has a finite context window. Every subtask handled inline eats tokens. With 5 agents doing 50k tokens each, you burn 250k of context — most of it irrelevant to the next task.

aide's answer: Process isolation. aide dispatch runs work in separate claude -p processes. The frontier only sees a bounded summary. 50k tokens of work → ~500 tokens of output.

Quick taste

# Install
cargo install aide-sh

# Turn any project into an agent
cd ~/projects/code-reviewer
aide init
# ✓ Created Aidefile

# Run a task
aide run . "Review PR #42 and leave comments"
# ✓ Task completed (23,847 tokens used)

# Team mode: coordinate multiple agents
aide init --team
# ✓ Created crossmem-hq/

aide dispatch crossmem-rs "fix parser bug"
aide dispatch crossmem-web "update dashboard"
aide wait crossmem-rs#42
# ✓ Done (18,293 tokens)

What's an Aidefile?

[persona]
name = "Senior Reviewer"
style = "direct, cares about edge cases"

[budget]
tokens = "100k"
max_retries = 3

[trigger]
on = "issue"

[vault]
keys = ["GITHUB_TOKEN"]

[skills]
include = ["code-review"]

Drop this into any project. That's it — it's an agent now.

What aide handles

ConcernSingle agentTeam (HQ)
BudgetToken limits, auto-retryPer-agent budgets
VaultEncrypted secrets → env varsHQ controls who gets what
MemoryPer-agent compactionCentralized at HQ, agents stateless
SkillsInjected at spawnPolicy controls injection
RoutingPolicy rules or frontier fallback
TelemetryToken usage, success rate, events

What aide does NOT do

aide doesn't replace Claude Code. Claude Code does all the thinking, coding, and reasoning. aide manages the lifecycle — who works on what, with what context, under what budget.

Aidefile is to Claude Code what Dockerfile is to Linux.

aide vs Claude Code native

FeatureClaude Code (native)aide (adds)
Run a taskclaude -p "task"aide run agent "task" — with budget + vault
Subagents.claude/agents/*.mdaide dispatch — token-isolated processes
Memory~/.claude/projects/*/memory/HQ/memory/ — centralized SSOT
Secretsenv vars, manualVault — encrypted, gated by HQ
Routingyou decidePolicy — deterministic rules

Next steps

Installation

cargo install aide-sh

This installs the aide binary.

Prerequisites

  • Rust toolchainrustup.rs if you don't have cargo
  • Claude Code CLI — aide calls claude -p under the hood. Install from claude.ai/code
  • age (optional) — for vault encryption. brew install age or your package manager

Verify

aide --version
# aide-sh 2.0.0-alpha.2

From source

git clone https://github.com/yiidtw/aide.git
cd aide
cargo install --path .

Next

Once installed, proceed to the Quick Start.

Quick Start

Set up a team of agents in 2 minutes.

1. Install

cargo install aide-sh

2. Initialize your team

aide init

This walks you through:

  • Scanning for projects
  • Selecting which ones become agents
  • Creating a team HQ (coordinator workspace)

At the end you get:

✓ myteam-hq created

  cd myteam-hq && claude

3. Open the HQ

cd myteam-hq && claude

You're now in a Claude session as the team coordinator. The HQ has:

  • CLAUDE.md — tells Claude it's a coordinator, not a coder
  • .claude/agents/ — dispatch wrappers for each member
  • memory/ — centralized memory (SSOT), auto-synced after each dispatch

4. Dispatch work

From the HQ session:

aide dispatch my-agent "review the latest PR and leave comments"
# dispatched: org/my-agent#42
# wait: aide wait https://github.com/org/my-agent/issues/42

The agent runs in a separate claude -p process with its own token budget. Your HQ session only sees the bounded summary — not the 50k tokens the agent burned.

5. Check results

aide wait https://github.com/org/my-agent/issues/42
# STATUS: success
# TOKENS: 18432/200000
# NOTES: Reviewed PR, left 3 comments on error handling.

After the agent finishes, its output is automatically distilled into memory/my-agent/context.md and git-committed to the HQ repo.

6. Automate with triggers

Set [trigger] in the agent's Aidefile:

[trigger]
on = "issue"

Start the daemon:

aide up

Now the agent wakes up whenever a GitHub Issue is opened with the matching label — no manual dispatch needed.

What's next?

Concepts

What is an agent?

An agent is a Claude Code project with an Aidefile. That's it.

The Aidefile declares persona, budget, vault, hooks, and triggers. aide handles the lifecycle — Claude Code handles the thinking.

aide vs Claude Code

It's important to understand what's native to Claude Code and what aide adds:

Claude Code native (works without aide)

FeatureHow it works
claude -p "task"Headless Claude Code — runs a task and exits
.claude/agents/*.mdCustom subagent definitions, dispatched via Agent tool
~/.claude/projects/*/memory/Auto-memory, Claude Code manages per-project
CLAUDE.mdProject instructions, read on session start
HooksPreToolUse, PostToolUse, PreCompact lifecycle events

A single Claude Code project doesn't need aide. Claude Code is already a capable agent runtime.

aide adds

FeatureHow it works
AidefileSingle config: persona, budget, vault, hooks, trigger, skills
Token isolationaide dispatch runs work in separate claude -p processes
VaultEncrypted secrets, injected as env vars at spawn time
Team memoryCentralized at HQ, agents are stateless
Policy routingDeterministic rules decide which agent gets which task
Skill injectionPolicy controls which skills are injected per task
TelemetryToken usage, duration, success/fail per dispatch
DaemonBackground polling for trigger-based automation

Aidefile

The single config file that turns a project into an agent:

[persona]
name = "Senior Reviewer"
style = "direct, cares about edge cases"

[budget]
tokens = "100k"
max_retries = 3

[trigger]
on = "issue"

[vault]
keys = ["GITHUB_TOKEN"]

[skills]
include = ["code-review"]

The Aidefile is safe to commit to public repos — it contains no secrets, no memory, no state.

Two layers

Layer 1: Single agent

any-project/
├── Aidefile        ← this is all you need
├── src/
└── ...

aide run . "task" — budget control, vault injection, that's it. No HQ, no orchestration.

Layer 2: Team (HQ)

crossmem-hq/                 ← coordinator (private)
├── CLAUDE.md
├── memory/
│   ├── _shared/             ← team-level context
│   ├── crossmem-rs/         ← per-agent memory
│   └── crossmem-web/
├── policy.toml              ← routing rules
├── vault.toml               ← secrets
└── .claude/agents/          ← auto-generated wrappers

crossmem-rs/                  ← member (can be public)
├── Aidefile
└── src/

crossmem-web/                 ← member (can be public)
├── Aidefile
└── src/

HQ is the single source of truth for memory, policy, vault, and telemetry. Member agents are stateless — they receive context at spawn time and return output. They don't store anything locally.

Registry

aide keeps a registry at ~/.aide/config.toml mapping agent names to directories:

reviewer  →  ~/projects/code-reviewer
writer    →  ~/projects/blog-writer
ops       →  ~/.aide/ops
  • aide spawn <name> — creates a new directory under ~/.aide/<name>/ with a template Aidefile
  • aide register <path> — registers an existing project that already has an Aidefile

Dispatch flow

aide dispatch crossmem-rs "fix parser bug"
  │
  ├─ 1. Create GitHub Issue (labeled "crossmem-rs")
  ├─ 2. Spawn background worker (aide run-issue)
  │     ├─ Vault injection: GITHUB_TOKEN → env var
  │     ├─ claude -p "fix parser bug"  ← isolated process
  │     ├─ Post bounded summary as issue comment
  │     ├─ Close issue on success
  │     └─ Sync memory to HQ (distill + git commit)
  │
  └─ Return immediately with issue ref

The frontier session only sees the bounded summary (~500 tokens). The sub-agent may burn 50k tokens — none of it enters your context.

After the dispatch completes, the agent's output is automatically distilled into HQ/memory/<agent>/context.md and git-committed. See Dispatch Protocol for the full step-by-step.

Daemon

aide up starts a background polling loop that checks triggers:

  • issue — polls gh issue list for matching issues
  • cron — runs on a schedule (coming soon)
  • manual — no auto-trigger, only responds to aide run / aide dispatch

When a trigger fires, the daemon calls aide run for that agent.

Aidefile

The Aidefile is a TOML config file that turns a Claude Code project into an agent. Place it in the project root.

Full example

[persona]
name = "Senior Reviewer"
style = "direct, cares about edge cases"

[budget]
tokens = "100k"
max_retries = 3

[memory]
compact_after = "200k"

[hooks]
on_spawn = ["inject-vault"]
on_complete = ["commit-memory"]

[skills]
include = ["code-review"]

[trigger]
on = "issue"

[vault]
keys = ["GITHUB_TOKEN", "SLACK_WEBHOOK"]

[output]
max_summary_tokens = 500
narrative_schema = """
NOTES: <one-line what you changed>
PR: <url or none>
NEXT: <optional redirect>
"""

[workspace]
read = ["~/claude_projects/crossmem-bridge", "~/claude_projects/crossmem-chrome"]

Sections

[persona]

FieldTypeDefaultDescription
namestring"unnamed"Agent display name
stylestringPersonality hint (injected into CLAUDE.md context)

[budget]

FieldTypeDefaultDescription
tokensstring"200k"Token limit per task. Supports "100k", "1m", or raw numbers.
max_retriesinteger3Maximum retry attempts if task doesn't complete in one invocation

[memory]

FieldTypeDefaultDescription
compact_afterstringAuto-compact memory when estimated tokens exceed this threshold

[hooks]

FieldTypeDescription
on_spawnlist of stringsRun before the task starts
on_completelist of stringsRun after the task finishes

Built-in hooks:

  • "inject-vault" — decrypt and inject vault secrets (handled automatically by the runner)
  • "commit-memory" — git add + commit the memory/ directory

Custom hooks are shell commands run in the agent's directory.

[skills]

FieldTypeDescription
includelist of stringsSkill names to load from the skills/ directory

[trigger]

FieldTypeDescription
onstringTrigger type: "manual", "issue", or "cron:EXPR"

[vault]

FieldTypeDescription
keyslist of stringsSecret names to decrypt and inject as env vars

[output]

[output]
max_summary_tokens = 500
narrative_schema = """
NOTES: <one-line what you changed>
PR: <url or none>
NEXT: <optional redirect>
"""
FieldTypeDefaultDescription
max_summary_tokensu32500Max tokens in the bounded summary
narrative_schemastring(default NOTES/PR/NEXT)Template the sub-agent fills in inside <aide-summary> block

This section is load-bearing for aide-as-subagent mode. Without it, sub-agent output pollutes the frontier context. The runner wraps the task with instructions requiring the sub-agent to emit an <aide-summary> block conforming to the narrative_schema. The bounded summary is what aide wait returns to the calling agent, keeping the frontier context clean.

[workspace]

[workspace]
read = ["~/claude_projects/crossmem-bridge", "~/claude_projects/crossmem-chrome"]
FieldTypeDefaultDescription
readlist of strings[]Sibling directories the sub-agent can read

The read list is translated to .claude/settings.json permission grants and a WORKSPACE section in the task prompt. This solves sandbox restrictions in non-interactive claude -p mode, where the sub-agent would otherwise be unable to access files outside its own project directory.

Token shorthand

The tokens and compact_after fields accept shorthand:

InputValue
"100k"100,000
"1m"1,000,000
"50000"50,000

Minimal Aidefile

Only [persona] is required:

[persona]
name = "Helper"

Everything else has sensible defaults (200k token budget, manual trigger, no vault).

Budget

aide enforces a token budget per task. When the budget runs out, the task stops — no surprise bills.

Configuration

[budget]
tokens = "100k"
max_retries = 3
  • tokens — maximum tokens for the entire task (all retries combined)
  • max_retries — how many times claude -p can be invoked for a single task

How it works

  1. aide calls claude -p with --output-format json
  2. Claude Code returns token usage in its JSON output
  3. aide tracks accumulated tokens across invocations
  4. If accumulated >= limit or invocations > max_retries, the task stops

Saturating arithmetic

The budget tracker uses saturating arithmetic — token counts can never overflow u64::MAX. This is formally verified with kani.

Token shorthand

InputValue
"50k"50,000
"100k"100,000
"1m"1,000,000
"200000"200,000

Default: "200k" if not specified.

Vault & Secrets

aide uses age encryption to store secrets. At spawn time, secrets are decrypted and injected as environment variables into claude -p. They never enter the LLM context window.

Setup

1. Generate a key

age-keygen -o vault.key
# Public key: age1...

2. Create secrets

cat <<'EOF' > secrets.env
export GITHUB_TOKEN='ghp_...'
export SLACK_WEBHOOK='https://hooks.slack.com/...'
EOF

age -r age1... -o vault.age secrets.env
rm secrets.env

3. Reference in Aidefile

[vault]
keys = ["GITHUB_TOKEN", "SLACK_WEBHOOK"]

How it works

vault.age (encrypted) + vault.key (private key)
    │
    └─ age -d -i vault.key vault.age
         │
         └─ parse: export KEY='VALUE'
              │
              └─ filter by [vault].keys
                   │
                   └─ Command::env("GITHUB_TOKEN", "ghp_...")
                        │
                        └─ claude -p runs with env vars set

Secrets are passed via Command::env() — the OS process environment. They are not injected into the prompt, system message, or any text the LLM sees.

File layout

my-agent/
├── Aidefile
├── vault.key      ← private key (never commit this)
├── vault.age      ← encrypted secrets
└── ...

Add to .gitignore:

vault.key

The vault.age file can be safely committed — it's encrypted.

CLI access

# Get a single secret
aide vault get GITHUB_TOKEN

# List all key names
aide vault list

MCP access

The aide_vault_get MCP tool lets other LLM agents retrieve secrets programmatically.

Hooks

Hooks are lifecycle callbacks that run before and after a task.

Configuration

[hooks]
on_spawn = ["inject-vault", "notify-start"]
on_complete = ["commit-memory", "notify-done"]

Lifecycle

on_spawn hooks  →  claude -p (task loop)  →  on_complete hooks
  1. on_spawn — runs before the first claude -p invocation
  2. task loopclaude -p invocations with budget tracking
  3. on_complete — runs after the task finishes (success or budget exhausted)

Built-in hooks

HookPhaseDescription
inject-vaulton_spawnDecrypt vault and inject secrets as env vars. This is handled automatically by the runner — you rarely need to list it explicitly.
commit-memoryon_completegit add memory/ && git commit in the agent directory

Custom hooks

Any string that isn't a built-in hook name is executed as a shell command in the agent's directory:

[hooks]
on_spawn = ["echo 'Starting task...'"]
on_complete = ["./scripts/notify-slack.sh"]

Custom hooks receive the agent directory as the working directory and inherit the vault env vars.

Triggers & Daemon

Triggers define what wakes an agent up. The daemon (aide up) polls triggers and dispatches tasks.

Trigger types

manual (default)

[trigger]
on = "manual"

Agent only runs when you explicitly call aide run.

issue

[trigger]
on = "issue"

The daemon polls gh issue list --label <agent_name> in the agent's git repo. When a matching issue is found:

  1. The issue title + body become the task
  2. aide runs the agent with that task
  3. On completion, aide comments on the issue and closes it

cron

[trigger]
on = "cron:0 9 * * *"            # daily at 09:00 (5-field cron syntax)

Or combined with issue via array form:

[trigger]
on = ["issue", "cron:*/30 * * * *"]   # poll issues + fire every 30 min

The daemon parses the 5-field expression on each tick and fires when the next scheduled time has passed since the last fire (no catch-up by default — a single missed-fire summary on daemon restart). Each cron fire dispatches a synthesized task "Scheduled run (cron: <expr>)" to the agent.

Validation happens at aide register time — invalid cron expressions fail fast instead of at fire time.

Daemon behavior:

  • Per-agent last_cron_fire map prevents double-dispatch within a single tick window.
  • On first encounter the map is seeded to now so the next minute boundary (not the current tick) decides when the first fire happens — this is by design to avoid an immediate fire when the daemon is restarted mid-minute.
  • The per-agent supervisor (see [daemon] restart = …) wraps cron-fired runs — failed runs can auto-retry on restart = "on-failure".

Observability (new in v2.0.0-alpha.10):

  • Every cron fire writes a matching dispatched + finished pair to ~/.aide/events.jsonl with kind=dispatched/finished, issue="cron:<expr>", routing="cron", and the same telemetry fields (cloud_tokens, reward, task_category) as issue-triggered runs.
  • aide events --limit N surfaces cron fires alongside issue dispatches.
  • aide ab analyze treats "cron" as a third routing arm (separate from "bandit" and "round-robin") so cron-triggered runs don't pollute A/B comparisons.
  • aide review (daily observability) applies MEDS-style failure clustering to cron-triggered failures the same way it does to dispatch-triggered ones.

Daemon

Start

aide up

Starts a background polling loop. The daemon:

  • Reads the registry to find all agents with non-manual triggers
  • Polls each agent's trigger on the configured interval
  • Dispatches aide run when a trigger fires
  • Writes a PID file for lifecycle management

Stop

aide down

Sends SIGTERM to the daemon process and cleans up the PID file.

GitHub repo detection

For issue triggers, aide detects the GitHub repo by parsing the git remote URL in the agent's directory. Both HTTPS and SSH formats are supported:

Restart Policy

Some agents are long-running daemons rather than one-shot tasks. When such an agent crashes (non-zero exit, panic, or runner error), the aide daemon's supervisor can auto-restart it according to a [daemon] block in the Aidefile.

Aidefile schema

[daemon]
restart = "on-failure"      # always | on-failure | never
max_restarts = 10           # supervisor gives up after this many consecutive restarts
backoff = "exponential"     # exponential | linear | fixed

All fields are optional. If the [daemon] block is omitted entirely, agents keep their existing single-shot behaviour (restart = "never") — backward compatible with all pre-existing Aidefiles.

Restart modes

ModeWhen the supervisor restarts
alwaysAfter every run — both success and failure. Use for true daemons.
on-failureOnly when the run failed (non-zero exit / runner Err / budget exhausted). Default when [daemon] is present.
neverNever restart. Default for legacy Aidefiles without [daemon].

max_restarts

A safety net against infinite crash loops. The supervisor counts consecutive restart attempts per agent and stops once max_restarts is reached. The counter resets to zero after any successful run, so a flaky agent that eventually succeeds gets a fresh budget.

Backoff strategies

The supervisor sleeps between restarts to avoid hot-looping. Sleep duration grows with the attempt number (0-indexed):

StrategySleep sequence (seconds)
exponential1, 2, 4, 8, 16, 32, 64, 128, 256, 300, 300, …
linear1, 2, 3, 4, 5, …
fixed1, 1, 1, 1, …

All strategies are capped at 300 seconds (5 minutes) per individual sleep, so even pathological attempt counts stay bounded.

Example: monitoring agent that should never stop

[persona]
name = "uptime-monitor"

[trigger]
on = "cron:*/5 * * * *"     # every 5 minutes

[daemon]
restart = "always"          # respawn on success and failure
max_restarts = 100
backoff = "exponential"

Use cases

  • Monitoring agents (restart = "always") that should never stop
  • Stream processors that crash intermittently (restart = "on-failure")
  • Any agent with [trigger] on = "cron:..." that you want kept alive without an external watchdog

Notes

  • The restart counter is in-memory only — it lives inside the running daemon process and is reset on aide up / aide down. This is intentional: persistent state for crash budgets is overkill for v2.
  • Restart policy is independent of trigger type. The cron trigger schedules the first run; the supervisor then decides whether to keep it alive.

Health & Dependencies

aide continuously monitors two things for every running agent:

  1. Liveness — the daemon writes a heartbeat file every tick.
  2. Declared dependencies — external services your agent needs (HTTP APIs, TCP ports, processes, files, shell commands).

Run aide ps to see the combined view.

Heartbeats

When the daemon is up, each scanned agent gets a record at ~/.aide/heartbeats/<agent>.json:

{
  "pid": 1234,
  "last_alive": "2026-04-18T12:00:00Z",
  "status": "running",
  "cycle": 42,
  "deps": [{"name": "database", "ok": true}]
}

aide ps flags an agent as DEAD if last_alive is older than 10 minutes — useful for catching stuck or crashed daemons.

Declaring dependencies

Add a [health] section to your Aidefile. Every field is optional; omitting [health] is equivalent to interval = "10m" with no dependencies.

[health]
interval = "10m"                       # how often to probe (default 10m)
alert    = "email:[email protected]"     # optional — alert wiring TBD

Then add one [[health.dependencies]] block per check.

http — HTTP GET

[[health.dependencies]]
name = "api_session"
type = "http"
url = "https://api.example.com/health"
expect_status = 200            # optional; otherwise any 2xx passes
# expect_json = '"ok": true'   # optional substring match on response body

tcp — port reachability

[[health.dependencies]]
name = "database"
type = "tcp"
host = "localhost"
port = 5432

command — shell exit code

Runs through sh -c; exit code 0 = healthy.

[[health.dependencies]]
name = "gpu_server"
type = "command"
run = "ssh gpu-server echo ok"
timeout = 10                   # seconds; default 10

process — pgrep pattern

[[health.dependencies]]
name = "claude_proc"
type = "process"
pattern = "claude"             # passed to `pgrep -f`

file_age — recent file modification

Useful for "is something writing logs?" or "did the model checkpoint update today?"

[[health.dependencies]]
name = "training_log"
type = "file_age"
path = "~/.aide/logs/agent.log"
max_age = "1h"                 # passes if mtime within last hour

Inspecting status

$ aide ps
NAME                 STATUS     LAST ALIVE             CYCLE    DEPS
────────────────────────────────────────────────────────────────────────────────
reviewer             running    12s ago                204      2/2 ok
gpu-trainer          running    8s ago                 11       1/2 fail:gpu_server
crawler              DEAD       42m ago                88       —

Alerts

health.alert is reserved for future use — the daemon currently logs failures via tracing::warn! and persists them in the heartbeat file's deps array. Wiring email/webhook sinks is tracked in issue #102 follow-ups.

Memory

aide manages memory at two layers: per-agent session memory (Claude Code native) and centralized HQ memory (aide's SSOT).

Two layers

LayerLocationWho managesPersistent?
Session memoryClaude Code ~/.claude/projects/*/memory/Claude Code (native)Per-session, native compact
HQ memory (SSOT)<team>-hq/memory/<agent>/aide (commander)Git-tracked, versioned

Session memory

Claude Code's built-in memory. Each agent session has its own working memory that persists across compactions within that session. aide adds auto-compaction:

[memory]
compact_after = "200k"

When estimated tokens in memory/ exceed the threshold, aide triggers a compaction pass.

HQ memory (centralized SSOT)

After each aide dispatch completes, the agent's output is automatically distilled into HQ memory:

<team>-hq/
├── memory/
│   ├── _shared/              # team-level context (arch decisions, conventions)
│   ├── crossmem-rs/
│   │   └── context.md        # auto-distilled from dispatch outputs
│   └── crossmem-web/
│       └── context.md

The distillation process:

  1. Read existing memory/<agent>/context.md (old memory)
  2. Read memory/_shared/ (team context, read-only)
  3. LLM merges old + new summary — not append, but intelligent merge with dedup
  4. Write updated context.md
  5. Git commit: memory: sync <agent> @ <timestamp>

This means:

  • One source of truth — no conflicting memories across agents
  • Git-tracked — full version history, git log memory/<agent>/ shows evolution
  • Rollbackgit revert if a distillation goes wrong
  • Multi-machine sync — push/pull the HQ repo

Configuration

Set the HQ directory in ~/.aide/config.toml:

hq = "/path/to/team-hq"

aide init sets this automatically when creating an HQ.

Manual sync

Backfill or manually trigger memory distillation:

# From a summary string
aide memory-sync crossmem-rs --summary "STATUS: success ..."

# From stdin (pipe from a file, etc.)
cat output.txt | aide memory-sync crossmem-rs

Tips

  • Set compact_after slightly below your typical task budget to avoid compaction eating into task tokens
  • Use the commit-memory hook to track per-agent memory in git
  • HQ memory is injected into future dispatches automatically — keep it concise
  • _shared/ is for team-level context (architecture, conventions) that all agents should know

Agent Memory (typed)

Issue #97 introduces a structured agent-memory layer. Memory stays as plain markdown files inside each agent's memory/ directory — no vector DB, no external service. The schema just declares three named types and the runner / aide memory CLI knows how to manage them.

The three types

TypeFieldDefault pathPurposePruned after
corecore_files(explicit list)Always-loaded persona, standing rules, project invariantsnever
proceduralprocedural_dirmemory/proceduralAppend-only logs: skill records, failure notes, patterns90 days
episodicepisodic_dirmemory/episodicPer-task scratch, transient observations7 days

Configure in your Aidefile:

[memory]
compact_after  = "200k"
core_files     = ["memory/persona.md", "memory/rules.md"]
procedural_dir = "memory/procedural"
episodic_dir   = "memory/episodic"

All three are optional and backward-compatible — existing Aidefiles keep working with the documented defaults.

Pruning

aide memory prune              # apply defaults across every registered agent
aide memory prune --dry-run    # preview only

Type-specific defaults:

  • episodic → delete files with mtime older than 7 days
  • procedural → delete files with mtime older than 90 days
  • core → never touched

.lock sentinel files are skipped. Errors on individual files are swallowed so one bad file does not abort the whole pass.

Failure distillation

When aide run exits with a non-success status (partial or timeout), the runner appends a structured note to <procedural_dir>/failures.md:

## 2026-04-18T12:00:00+00:00
- **Issue**: local
- **Task snippet**: <first 200 chars of task>
- **Tokens**: 12345
- **Why it failed**: partial

The append is best-effort — it never fails the run itself. This forms the substrate for ReMe-style failure distillation: cron-fed jobs can later summarize failures.md into longer-term procedural notes.

Typed memory ops (text2mem-style)

The memory_ops module provides five free functions over a single agent's memory/ dir. All ops are markdown-aware and stay file-local.

OpBehavior
merge(path, content, dedupe)Append + dedupe (none, line_hash, section_replace)
promote(src, dst)Atomic rename — typically episodic → core
demote(src, dst)Atomic rename — opposite direction
expire(path, max_age)Delete if mtime older than max_age; missing file = no-op
lock(path) / unlock(path)Advisory <path>.lock sentinel (cooperative; not OS-enforced)

Dedupe strategies

  • none — straight append.
  • line_hash — skip lines whose exact text is already present.
  • section_replace — for markdown with ## headings, replace any block whose heading matches; preserve everything else.

Lock semantics (limitation)

lock(path) writes <path>.lock with O_CREAT|O_EXCL. A second lock on the same file fails until unlock removes the sentinel. This is purely cooperative — code that does not call lock/unlock will happily ignore the sentinel and clobber the file. It is intended for protecting batch mutations across the same agent dir, not for cross-process exclusion against arbitrary writers.

What is intentionally out of scope

  • Cross-agent shared memory (separate ticket, needs more design)
  • Token-usage benchmarks vs full context (separate eval ticket)
  • Vector retrieval / RAG — explicitly excluded by the issue

Skills (SKILL.md)

aide adopts the open agentskills.io standard. A skill is a directory holding a SKILL.md manifest plus optional scripts/, references/, assets/ subdirectories. The same skill packaging is consumed today by Claude Code, OpenAI Codex, Gemini CLI, Cursor, Cline, Windsurf, and others — aide is the governance / audit / safety / cross-domain routing layer layered on top of that ecosystem, not a competing format.

Skill layout

my-skill/                          # dir name MUST equal SKILL.md `name`
├── SKILL.md                       # REQUIRED — YAML frontmatter + Markdown body
├── scripts/                       # optional executable code (any language)
│   └── run.sh
├── references/                    # optional documentation (REFERENCE.md, …)
└── assets/                        # optional templates / schemas / images

Minimal SKILL.md

---
name: hello
description: Print a greeting and exit.
---

# hello

Run via `aide skill exec hello`. Add usage notes here.

Frontmatter fields

FieldRequiredConstraint
nameyes1-64 chars, ^[a-z0-9]+(-[a-z0-9]+)*$, MUST equal parent dir name
descriptionyes1-1024 chars, non-empty
licensenofree-form short string
compatibilityno1-500 chars (e.g. "Requires Python 3.14+")
metadatanoflat map<string, string>
allowed-toolsnospace-separated tool patterns (captured but not enforced yet)

The body is free-form Markdown. The spec recommends keeping it under ~500 lines.

aide skill commands

aide skill list   [--root PATH]              # discover skills under search paths
aide skill show   <name> [--with-body]       # print manifest, optionally body
aide skill validate <skill-dir>              # exit 0/1, lists ALL errors
aide skill exec   <name> [--agent A] [-- ARGS...]   # run, stream output
aide skill events [--limit N] [--skill N] [--agent A]   # tail skill-events.jsonl

Search-path resolution

aide skill exec <name> resolves the skill in this order:

  1. ~/.aide/skills/<name>/
  2. <agent_dir>/.aide-skills/<name>/ (only when --agent is supplied)
  3. ~/.claude/skills/<name>/

First hit wins. aide skill list (without --root) scans both #1 and #3.

Execution semantics (aide's defaults)

The agentskills.io spec deliberately leaves execution undefined. aide's runner picks safe defaults:

  • Working directory: the skill's own skill_dir, so relative paths in scripts/, references/, assets/ work.
  • Entry point: first match of scripts/run.shscripts/run.pyscripts/main.shscripts/main.py. No match = error.
  • Environment exported:
    • AIDE_HOME$AIDE_HOME override or ~/.aide
    • AIDE_SKILL_NAME — the skill name
    • AIDE_SKILL_DIR — absolute path to the skill root
    • AIDE_RUN_ID — nanos-based unique id
    • AIDE_AGENT — the --agent value, if provided
    • PATH — inherited
  • Args: anything after -- is passed verbatim as positional args.
  • Timeout: 5 minutes default. Override with AIDE_SKILL_TIMEOUT_SECS. On timeout: SIGTERM → 10 s grace → SIGKILL.
  • Stdout/stderr capture: written to ~/.aide/skill-runs/<skill>/<run_id>.{out,err}, capped at 1 MiB per stream. Overflow is dropped from the file but counted, and the finished event is marked truncated=true.
  • Exit code: 0 = success; non-zero is logged, not bailed — aide skill exec itself exits with the skill's exit code.

~/.aide/skill-events.jsonl schema

Two event kinds, one line per event, append-only.

// started
{
  "ts": "2026-04-18T12:34:56.789Z",
  "kind": "started",
  "run_id": "1750000000000000000",
  "skill": "hello",
  "agent": null,
  "args": []
}
// finished
{
  "ts": "2026-04-18T12:34:57.123Z",
  "kind": "finished",
  "run_id": "1750000000000000000",
  "skill": "hello",
  "agent": null,
  "exit_code": 0,
  "duration_ms": 334,
  "stdout_bytes": 6,
  "stderr_bytes": 0,
  "truncated": false,
  "timed_out": false
}

This is aide's pillar #1 (FS-first audit) for skills. Read it with aide skill events. A future PR will use it for behavior shaping (forbidden tables / penalties), like the existing dispatch event log.

Aidefile integration

[skills]
include = ["code-review", "git-ops"]

Names listed under [skills].include resolve through the same search order documented above. The legacy skills/<name>.md flat-file layout still works for context injection, but new skills should use the SKILL.md directory layout so that aide skill exec (and any other agentskills.io-compatible runner) can run them.

Migrating an existing aide-skill repo

Run the bundled migrator (dry-run by default):

./scripts/migrate-aide-skill-to-skillmd.sh                  # dry-run
./scripts/migrate-aide-skill-to-skillmd.sh --apply          # mutate
./scripts/migrate-aide-skill-to-skillmd.sh --root /path     # custom root

For each <skill>/ containing a top-level run.sh:

  1. Generates <skill>/SKILL.md (name from dir, description from the first # comment in run.sh).
  2. Moves run.sh to scripts/run.sh and marks it executable.
  3. Skips dirs that already contain a SKILL.md, or that don't have a run.sh (not recognized as a legacy skill).

After migration, validate one with aide skill validate <skill-dir> and test with aide skill exec <name>.

Teams (Import / Export)

aide supports sharing agent templates via git repositories.

Export

# Export all agents
aide export --to ./my-team-template

# Export a specific agent
aide export --to ./my-team-template --name reviewer

Export copies only shareable files:

  • Aidefile
  • CLAUDE.md
  • skills/

It excludes private data:

  • memory/ — per-deployment state
  • vault.key — private encryption key
  • vault.age — encrypted secrets
  • .git/

Import

aide import https://github.com/user/agent-templates.git

Import clones the repo and registers every subdirectory that contains an Aidefile. Agents are copied to ~/.aide/<name>/.

If the repo root itself has an Aidefile, it's also imported (named after the repo).

Team workflow

team-agents/           ← git repo
├── reviewer/
│   ├── Aidefile
│   ├── CLAUDE.md
│   └── skills/
├── writer/
│   ├── Aidefile
│   ├── CLAUDE.md
│   └── skills/
└── README.md
# Team lead exports
aide export --to ./team-agents
cd team-agents && git push

# Team member imports
aide import https://github.com/org/team-agents.git
# ✓ Imported 'reviewer'
# ✓ Imported 'writer'

Each member sets up their own vault locally. The templates are shared; the secrets are not.

Smart Routing (RL Sidecar)

aide includes a CPU-only reinforcement learning sidecar that learns which agent handles which type of task best. It runs entirely on your local machine — zero cloud LLM cost for routing decisions.

How it works

The sidecar uses a LinUCB contextual bandit:

  1. Each task is classified into 12 categories (infra, gpu, memory, disk, network, pipeline, parser, web, test, debug, deploy, security) via keyword matching
  2. Each agent has a bandit "arm" that tracks its performance per category
  3. When routing, the bandit scores each agent: exploitation (past success) + exploration (uncertainty bonus)
  4. After each dispatch, the reward signal updates the bandit: r = 0.7 * success + 0.3 * token_efficiency

The entire computation is a 12x12 matrix inverse — microseconds on any CPU.

MEDS failure clustering (Phase C)

When running aide policy-update --full, the sidecar also clusters past failures using embedding similarity:

  1. Failed tasks are embedded via local ollama (nomic-embed-text, 768 dims)
  2. Greedy cosine-similarity clustering groups recurring failure patterns
  3. At dispatch time, tasks similar to known failure patterns get a penalty for the agent that failed them

This is inspired by MEDS (Memory-Enhanced Dynamic Reward Shaping) — density-based clustering of recurring error patterns penalizes repeated failures.

Files

FilePurpose
~/.aide/bandit.jsonPer-agent LinUCB state (A matrices, b vectors)
~/.aide/policy.tomlRouting weights per category, updated by policy-update
~/.aide/failure_patterns.jsonFailure cluster centroids (Phase C)

Usage

Automatic routing

# Let the bandit pick the best agent for a task
aide dispatch --auto "check GPU utilization on all nodes"

Output:

auto-selected: infra-guardian (score: 1.26)
  runner-up: pipeline-doctor (1.12)

Manual policy update

# Quick update: bandit only
aide policy-update

# Full update: bandit + failure clustering (requires ollama)
aide policy-update --full

Daemon integration

The daemon (aide up) runs policy-update automatically:

  • Once on startup
  • Every hour thereafter

Requirements

FeatureRequirement
Bandit routingNone (pure CPU)
--auto dispatchRegistered agents + events history
Failure clusteringollama + ollama pull nomic-embed-text

Configuration

No configuration needed. The sidecar learns from your dispatch history automatically. The exploration parameter (alpha = 0.5) balances trying new agents vs exploiting known-good ones.

HQ as Router

An HQ is a directory holding centralized memory for a group of agents (e.g. crossmem-hq for crossmem-bridge, crossmem-chrome, …). With a [router] block in its Aidefile, the HQ also becomes the dispatch entry point for inbound GitHub Issues opened against the HQ repo. Instead of running claude on the HQ itself, the daemon picks an HQ member agent and dispatches the issue to it via the local sidecar transport.

Why

Without [router], an issue opened on yiidtw/crossmem-hq triggers a claude run inside the HQ directory — wasting tokens, and producing a context that doesn't know about the actual code repos. With [router], the daemon hands the issue off to whichever member agent is best suited (per the bandit) or to all of them (fanout).

Setup

Add a [router] block to the HQ's Aidefile:

[persona]
name = "crossmem-hq"

[trigger]
on = "issue"

[router]
mode = "bandit"            # "bandit" | "round-robin" | "fanout"
# candidates = ["crossmem-bridge", "crossmem-chrome"]   # optional override

Then register the HQ like any other agent:

aide register ~/claude_projects/crossmem-hq

Modes

modeBehavior
banditPick the top-scored agent from sidecar::select_agent.
round-robinCycle through candidates via the file-backed RR counter.
fanoutDispatch the same task to every candidate.

Candidate resolution

  • If [router].candidates is non-empty, that explicit list wins.
  • If empty, the HQ's group members (from ~/.aide/config.toml) are used, matched by groups[*].hq == <HQ directory>.

Register-time validation

When you aide register an HQ with explicit candidates, every name must already be registered. Empty candidates is fine — group lookup happens at fire time.

What the routed agent sees

The dispatched task text is prefixed with HQ context so the agent knows where the work originated:

[from HQ crossmem-hq issue #42]
<issue title>

<issue body>

On the GitHub side

After successful routing the daemon comments on the issue (dispatched to <agent> or fanned out to N agents: ...) and closes it. Per agent execution still produces its own dispatch event log entries downstream through aide events.

HQ Inbox: Interrupts + Idle Autopilot

Each HQ in aide gets its own inbox queue under ~/.aide/hq-queue/<hq-name>/. The queue exists so the aide binary can unify three kinds of incoming events into one stream that the HQ chat session (or the daemon, when nobody's home) can consume:

  1. GitHub issues opened against the HQ repo.
  2. Member-agent completions (local tasks routed by [router] finishing).
  3. Manual / external pushes from the CLI (aide hq-inbox push ...).

Two consumers attach to the same queue:

  • Online (you're chatting with the HQ): the HQ Claude session has a long-running Monitor watching aide hq-inbox watch <hq>. When a new event arrives mid-conversation, Claude summarizes it and asks how you want to handle it.
  • Offline (chat idle ≥ 5 min): the daemon's auto_handle kicks in and routes the inbox via the existing [router] selector (bandit / round-robin / fanout) without waiting for you.

Layout

~/.aide/hq-queue/<hq>/
  inbox/        pending JSON files, one per inbound message (FIFO)
  processing/   atomic move from inbox during handling
  done/         finished, with result/summary/acked_at appended
  last_activity touched by `aide hq-touch <hq>` (mtime tracked)

Each inbox file is JSON:

{
  "id": "<nanos-based id>",
  "ts": "<RFC3339>",
  "source": "github-issue|member-completed|cli|other",
  "title": "<short>",
  "body": "<full>",
  "context": { "...arbitrary..." }
}

Bootstrap an HQ

The fastest way to get a new HQ wired up:

aide hq-init ~/projects/my-hq
aide register ~/projects/my-hq

aide hq-init does three things:

  1. Ensures Aidefile has a [router] block (creates the file if missing).
  2. Writes/merges .claude/settings.local.json to add Claude Code hooks (SessionStart and UserPromptSubmit) that call aide hq-touch <hq> — so every user turn refreshes the activity marker.
  3. Appends an "HQ inbox protocol" section to CLAUDE.md instructing the Claude session about the watcher pattern below.

Online pattern (Monitor + interrupt)

In the HQ project, attach a long-running Monitor to the inbox watcher:

/monitor aide hq-inbox watch my-hq

watch polls the inbox dir every 2s and prints one JSON line per new file:

{"id": "172...", "source": "github-issue", "title": "fix the thing", "ts": "..."}

Each line is a Monitor notification. The CLAUDE.md protocol tells the model:

  1. Summarize the new item for you in one sentence.
  2. Ask how to handle it (route to a member, defer, ignore).
  3. After you decide, dispatch via:
    aide dispatch --auto --local "<task>"
    aide hq-inbox ack my-hq <id> --summary "<one-line>" --success
    

Offline pattern (idle autopilot)

If you don't reply within [router].auto_idle_secs (default 300 = 5 min), the daemon calls auto_handle on its next tick. auto_handle:

  • Checks last_activity mtime — if newer than the threshold, no-op.
  • Otherwise, drains the inbox in FIFO order, routing each item via router::route_issue using the same selector you've configured (bandit, round-robin, or fanout).
  • Acks each item with the route outcome.

Tune the threshold per HQ via auto_idle_secs:

[router]
mode = "bandit"
auto_idle_secs = 600   # 10 min instead of default 5 min

CLI reference

aide hq-inbox push <hq> --source <s> --title <t> [--body <b>] [--context <json>]

Write a new inbox JSON file. Body reads from stdin if --body omitted.

aide hq-inbox push my-hq --source cli --title "manual ping" --body "do the thing"

aide hq-inbox ls <hq> [--state inbox|processing|done] [--limit N]

List files in the chosen state directory, newest first. Default state is inbox.

aide hq-inbox pop <hq>

Atomically moves the oldest inbox file → processing/, prints the full JSON to stdout. Returns exit 0 with empty stdout if nothing pending.

aide hq-inbox ack <hq> <id> [--summary <s>] [--success]

Atomically moves processing/<id>.jsondone/<id>.json with appended result + summary + acked_at fields.

aide hq-inbox watch <hq> — long-running

Tails the inbox directory and prints one JSON line per new file (line-buffered, Monitor-friendly). Runs forever until SIGTERM.

aide hq-inbox auto-handle <hq> [--max-idle 5m]

If last_activity mtime > --max-idle ago AND inbox non-empty: drains via router::route_issue and acks each. Otherwise no-op. Prints exactly one summary line.

aide hq-touch <hq>

Touches ~/.aide/hq-queue/<hq>/last_activity (creates dir + file if missing). Used by Claude Code's UserPromptSubmit hook so each user turn extends the idle window.

aide hq-init <hq-dir>

Bootstrap helper described above.

Recovery

If the HQ chat session crashes mid-processing, files in processing/ are left in place. On restart, you can:

aide hq-inbox ls my-hq --state processing
# Inspect or move them back manually:
mv ~/.aide/hq-queue/my-hq/processing/<id>.json ~/.aide/hq-queue/my-hq/inbox/

auto_handle will pick them up on the next idle window.

Sources fed by aide-binary

SourceProducer
github-issuerouter::route_issue after dispatching to a member
member-completeddispatch::run_local_task finalize when routed_from_hq is set
cliaide hq-inbox push <hq> ...

aide init

Interactive setup that creates a team HQ — the coordinator workspace for your agents.

Usage

# Interactive (recommended)
aide init

# Non-interactive (CI / scripting)
aide init --name myteam --members /path/to/agent-a,/path/to/agent-b

What it does

  1. Prerequisites check — verifies gh and claude CLI are installed, checks GitHub auth
  2. Scan for projects — finds directories with .git, Aidefile, src/, package.json, etc.
  3. Select members — interactive multi-select of which projects to include as agents
  4. Create HQ — builds the coordinator workspace:
myteam-hq/
├── CLAUDE.md              # coordinator instructions
├── .gitignore
├── .claude/agents/        # dispatch wrappers for each member
├── memory/
│   ├── _shared/           # team-level context
│   ├── agent-a/           # per-agent memory (auto-distilled)
│   └── agent-b/
  1. Register agents — adds all members to ~/.aide/config.toml
  2. Set HQ path — stores hq = "/path/to/myteam-hq" in config for memory sync

After init

cd myteam-hq && claude

This opens a Claude session as the team coordinator. From here you dispatch work:

aide dispatch agent-a "implement feature X"
aide dispatch agent-b "fix bug Y"
aide events   # check progress

The HQ session stays clean — agents do the heavy lifting in their own token budgets.

Flags

FlagDescription
--name <name>Team name (creates <name>-hq/). Skips interactive prompt.
--scan-dir <path>Directory to scan for projects (default: ~/projects)
--members <paths>Comma-separated paths to register as agents
--vault <path>Path to vault file
--skill-dir <path>Path to skill directory

When --name and --members are both provided, all interactive prompts are skipped.

What gets created for each member

  • Aidefile — if the project doesn't have one, a default is created
  • .claude/agents/ wrapper — in the HQ, a dispatch wrapper for each member
  • memory/<agent>/ — directory for auto-distilled memory (SSOT)
  • Registry entryaide register so aide dispatch <name> works

Example

$ aide init

aide — your commander of agents
─────────────────────────────────

Checking prerequisites... ✓ gh CLI found
                         ✓ claude CLI found
✓ GitHub: logged in as yiidtw
  Where should we look for projects? ~/claude_projects
  Found 6 projects (4 with Aidefile, 2 without)

  Select members:
  ✓ crossmem-rs
  ✓ crossmem-web
  ✓ crossmem-chrome
    (use space to toggle, enter to confirm)

  Team name: crossmem

Creating crossmem-hq/
  ✓ git init
  ✓ CLAUDE.md
  ✓ .gitignore
  ✓ .claude/agents/ (3 wrappers)
  ✓ Registered 3 agents
  ✓ HQ registered in ~/.aide/config.toml
  ✓ memory/ (team SSOT)

✓ crossmem-hq created

  cd crossmem-hq && claude

aide run

Execute a task in an agent's directory.

Usage

aide run <agent> <task>
  • agent — registered name or path to directory with Aidefile
  • task — natural language task description

Examples

# By registered name
aide run reviewer "Review PR #42"

# By path
aide run ./my-agent "Summarize recent changes"
aide run ~/projects/ops "Check server health"

What happens

  1. Resolve agent name → directory path
  2. Load and parse Aidefile
  3. Decrypt vault secrets (if configured)
  4. Run on_spawn hooks
  5. Loop: invoke claude -p with the task
    • Track token usage after each invocation
    • Stop if budget exhausted or task complete
  6. Run on_complete hooks
  7. Check memory compaction threshold

Output

▸ Running task in ~/projects/code-reviewer
  agent: Senior Reviewer
  budget: 100000 tokens
✓ Task completed (23,847 tokens used)

Or on budget exhaustion:

✗ Task incomplete (100000 tokens used, budget exhausted)

aide spawn

Create a new agent under ~/.aide/.

Usage

aide spawn <name> [--persona <persona>]

Options

FlagDescription
--persona <name>Persona name for the Aidefile. Defaults to the agent name.

Example

aide spawn reviewer --persona "Senior Reviewer"
# ✓ Spawned agent 'reviewer' at ~/.aide/reviewer
#   Edit ~/.aide/reviewer/Aidefile to configure

Created structure

~/.aide/reviewer/
├── Aidefile
├── CLAUDE.md
├── memory/
└── skills/

The agent is automatically registered in the registry. Errors if an agent with the same name already exists.

aide register / unregister

Register or unregister an existing project as an agent.

register

aide register <path> [--name <name>]
FlagDescription
--name <name>Agent name. Defaults to the directory name.

The directory must contain an Aidefile. After registration, you can use aide run <name> instead of the full path.

aide register ~/projects/my-agent --name reviewer
# ✓ Registered 'reviewer' → ~/projects/my-agent

unregister

aide unregister <name>

Removes the agent from the registry. Does not delete the directory or its files.

aide unregister reviewer
# ✓ Unregistered 'reviewer' (files not deleted)

aide list

List all registered agents.

Usage

aide list

Output

NAME                 PATH                                               STATUS
────────────────────────────────────────────────────────────────────────────────
reviewer             ~/projects/code-reviewer                           issue
writer               ~/projects/blog-writer                             manual
ops                  ~/.aide/ops                                        cron:0 9 * * *

The STATUS column shows the trigger type from each agent's Aidefile. Shows "missing" if the Aidefile can't be found, or "error" if it can't be parsed.

aide ps

Show liveness and dependency-check status for every registered agent.

aide ps

Reads heartbeat files written by the daemon at ~/.aide/heartbeats/<agent>.json and prints a table:

NAME                 STATUS     LAST ALIVE             CYCLE    DEPS
────────────────────────────────────────────────────────────────────────────────
reviewer             running    12s ago                204      2/2 ok
gpu-trainer          running    8s ago                 11       1/2 fail:gpu_server
crawler              DEAD       42m ago                88       —

Columns

  • NAME — agent name from aide list
  • STATUSrunning, DEAD (last heartbeat > 10 min ago), or if never seen
  • LAST ALIVE — wall-clock age of the last heartbeat
  • CYCLE — daemon tick counter for this agent (monotonic per process)
  • DEPS — dependency-check summary: n/m ok or n/m fail:name1,name2

aide up / down

Start and stop the trigger daemon.

aide up

aide up

Starts a background daemon that polls triggers for all registered agents. The daemon:

  • Checks each agent's [trigger] setting
  • For issue triggers: polls gh issue list for matching issues
  • Dispatches aide run when a trigger fires
  • Writes a PID file to ~/.aide/daemon.pid

aide down

aide down

Sends SIGTERM to the daemon process and removes the PID file.

Notes

  • Only one daemon instance runs at a time
  • The daemon skips agents with trigger.on = "manual"
  • Poll interval is configured in ~/.aide/config.toml

aide import / export

Share agent templates via git.

import

aide import <git-url>

Clones the repo, finds all directories with Aidefiles, copies them to ~/.aide/, and registers them.

aide import https://github.com/org/team-agents.git
# ▸ Cloning https://github.com/org/team-agents.git...
#   ✓ Imported 'reviewer'
#   ✓ Imported 'writer'
# ✓ Imported 2 agent(s)

export

aide export --to <directory> [--name <agent>]
FlagDescription
--to <dir>Output directory
--name <agent>Only export this agent (exports all if omitted)

Copies only shareable files: Aidefile, CLAUDE.md, skills/. Excludes memory/, vault.*, .git/.

aide export --to ./team-template
#   ✓ Exported 'reviewer'
#   ✓ Exported 'writer'
# ✓ Exported 2 agent(s) to ./team-template

See Teams for the full workflow.

aide vault

Access vault secrets from the CLI.

aide vault get

aide vault get <key>

Decrypts the vault and prints the value of the specified key. Output has no trailing newline (suitable for piping).

aide vault get GITHUB_TOKEN
# ghp_abc123...

# Use in scripts
export TOKEN=$(aide vault get GITHUB_TOKEN)

aide vault list

aide vault list

Lists all key names in the vault (values are not shown).

aide vault list
# GITHUB_TOKEN
# SLACK_WEBHOOK

Vault file location

The vault files (vault.age, vault.key) are looked up in the current working directory. Run these commands from the agent's directory, or the directory containing your vault files.

aide mcp

Start the MCP (Model Context Protocol) stdio server.

Usage

aide mcp

This starts a JSON-RPC 2.0 server over stdin/stdout, allowing LLM hosts (Claude Code, Cursor, etc.) to use aide agents as tools.

Available tools

ToolDescription
aide_runRun a task on a registered agent
aide_listList all registered agents
aide_spawnCreate a new agent
aide_vault_getRetrieve a secret from the vault

Claude Code integration

Add to your Claude Code MCP config (.claude/settings.json):

{
  "mcpServers": {
    "aide": {
      "command": "aide",
      "args": ["mcp"]
    }
  }
}

Once configured, Claude Code can orchestrate your agents:

"Use the reviewer agent to check PR #42"
→ Claude calls aide_run(agent="reviewer", task="Review PR #42")

Protocol

  • Transport: stdio (line-delimited JSON)
  • Methods: initialize, tools/list, tools/call
  • Follows the MCP specification

aide dispatch / wait / events

Commands for inter-agent delegation and observability.

aide dispatch

aide dispatch <agent> "<task>"
aide dispatch reviewer "Review PR #42 for security issues"
aide dispatch --dry-run deployer "Ship v1.2.0"

# Auto-select the best agent using the RL bandit router
aide dispatch --auto "check GPU utilization on all nodes"

Creates a GitHub issue labeled for the target agent, spawns a detached aide run-issue background worker, and returns immediately with the issue reference and a wait command you can use to block on the result.

Flags

FlagDescription
--dry-runPrint the issue that would be created without actually creating it or spawning a worker
--auto, -aAuto-select the best agent using the bandit router. The positional arg becomes the task text instead of agent name

Output

Dispatched: https://github.com/org/repo/issues/99
Wait with: aide wait https://github.com/org/repo/issues/99

aide wait

aide wait <issue-url>
aide wait --timeout 30m https://github.com/org/repo/issues/99

Blocks until the target issue closes, then prints the bounded summary extracted from the closing comment.

Flags

FlagDefaultDescription
--timeout60mMaximum time to wait before returning exit code 124
--poll-interval10sHow often to check issue status

Exit codes

CodeMeaning
0Sub-agent completed successfully
1Sub-agent reported partial completion or failure
2Issue was cancelled (closed without summary)
124Timeout reached

Output

On success, prints the content of the <aide-summary> block from the issue's closing comment. This is the bounded summary controlled by the sub-agent's [output] config.

aide cancel

aide cancel <issue-ref>
aide cancel owner/repo#42
aide cancel https://github.com/org/repo/issues/42

Stops a running dispatch: sends SIGTERM to the background worker, posts a cancellation comment, and closes the issue.

What it does

  1. Looks up the worker PID from the local SQLite state database
  2. Sends SIGTERM to kill the claude -p process
  3. Posts STATUS: cancelled (by user) as an issue comment
  4. Closes the GitHub issue
  5. Marks the run as cancelled in the state database
  6. Logs a cancelled event to ~/.aide/events.jsonl

aide events

aide events
aide events --limit 20

Reads ~/.aide/events.jsonl and prints a timeline table of recent dispatch and completion events.

Flags

FlagDefaultDescription
--limit50Number of events to show

Example output

TIME            EVENT       AGENT       ISSUE   STATUS
2026-04-11 14:01 dispatch   reviewer    #99     running
2026-04-11 14:03 complete   reviewer    #99     success
2026-04-11 14:05 dispatch   deployer    #100    running

aide memory-sync

aide memory-sync <agent> --summary "STATUS: success ..."
cat output.txt | aide memory-sync <agent>

Manually triggers HQ memory distillation for an agent. Useful for backfilling history or re-syncing after a failed auto-sync.

What it does

  1. Reads existing HQ memory/<agent>/context.md
  2. Reads memory/_shared/ (team-level context)
  3. Uses claude -p to merge old memory + new summary
  4. Writes updated context.md
  5. Git commits to HQ repo

If --summary is not provided, reads from stdin.

Requires hq to be set in ~/.aide/config.toml.

aide api

aide api
aide api --port 7979

Starts a local HTTP API server for programmatic access to aide state.

Flags

FlagDefaultDescription
--port7979Port to listen on

Endpoints

EndpointMethodDescription
/api/runsGETList recent runs with status and token usage
/api/agentsGETList registered agents
/api/heartbeatGETLast heartbeat timestamp from daemon
/api/statsGETAggregate stats (today's runs, tokens, agents)
/api/healthGETHealth check (returns 200 if daemon is running)
/api/telemetryGETDispatch telemetry summary (compression ratio, token savings)

aide stats

aide stats

Prints a summary of today's activity from the local SQLite database:

Today (2026-04-11):
  Runs:   12
  Tokens: 847,230
  Agents: 3 (reviewer, deployer, helper)

aide status

aide status

Shows daemon health and last heartbeat:

Daemon: running (pid 12345)
Last heartbeat: 2s ago
Uptime: 4h 12m

aide install-service / aide uninstall-service

aide install-service
aide uninstall-service

Installs or removes the aide daemon as a system service so it starts automatically on login.

Platform support

PlatformMechanismLocation
macOSlaunchd plist~/Library/LaunchAgents/sh.aide.daemon.plist
Linuxsystemd user unit~/.config/systemd/user/aide-daemon.service

After installing, the daemon starts on login and restarts on failure. Use aide uninstall-service to remove the service definition and stop the daemon.

aide dispatch --local

For pipeline agents on the same machine — or air-gapped environments — you can dispatch through a local filesystem queue instead of GitHub Issues. Same protocol, same memory sync, no GitHub round-trip.

aide dispatch --local <agent> "<task>"

When to use it

  • Pipeline agents on the same server (issue noise, API rate limits)
  • High-frequency batch processing (too fast for GitHub Issues)
  • Air-gapped machines without GitHub access

The agent itself doesn't know how it was dispatched — runner::run is invoked the exact same way. --local is a transport choice at dispatch time, not an agent property. No Aidefile change required.

Layout

Each agent gets a per-agent queue under ~/.aide/dispatch/:

~/.aide/dispatch/<agent>/pending/local_<nanos>.json
~/.aide/dispatch/<agent>/done/local_<nanos>.json

Files start in pending/. The daemon scans each tick, runs the task, then atomically renames the file into done/ with the result fields appended.

Task file schema

{
  "id": "local_1729000000000000000",
  "agent": "research",
  "task": "scan logs",
  "created_at": "2026-04-18T12:00:00Z"
}

After completion, the same file (now under done/) gains:

{
  "id": "local_1729000000000000000",
  "agent": "research",
  "task": "scan logs",
  "created_at": "2026-04-18T12:00:00Z",
  "finished_at": "2026-04-18T12:01:43Z",
  "success": true,
  "tokens": 4123,
  "summary": "STATUS: success\nTOKENS: 4123/200000\n...",
  "result": "ok"
}

End-to-end example

# 1. Dispatch a task to the local 'research' agent
$ aide dispatch --local research "scan logs"
dispatched: local_1729000000000000000
agent: research
transport: local
budget: 200000 tokens
file: /Users/me/.aide/dispatch/research/pending/local_1729000000000000000.json

# 2. The daemon (already running via `aide up`) sees it on its next tick
$ ls ~/.aide/dispatch/research/pending/
local_1729000000000000000.json

# 3. After the task completes the file moves to done/ with full result
$ ls ~/.aide/dispatch/research/pending/
$ ls ~/.aide/dispatch/research/done/
local_1729000000000000000.json

$ cat ~/.aide/dispatch/research/done/local_1729000000000000000.json
{
  "id": "local_1729000000000000000",
  "agent": "research",
  "task": "scan logs",
  ...
  "success": true,
  "summary": "STATUS: success\n..."
}

Flags

FlagDescription
--localUse the local FS queue under ~/.aide/dispatch/<agent>/ instead of GitHub
--dry-runPrint where the file would be written without creating it

Notes

  • IDs use nanosecond timestamps (local_<nanos>), so they sort by creation time.
  • The pending → done move uses std::fs::rename, which is atomic on the same filesystem.
  • The daemon picks up local tasks on every tick for every registered agent — no opt-in flag in the Aidefile.
  • Memory sync to HQ runs after completion just like the GitHub path.

aide dashboard

Live terminal UI for monitoring dispatches in flight.

aide dashboard

Built on ratatui — pure Rust, no external dependencies.

Layout

PanelContent
Top barTitle, timestamp, active dispatch count
LeftActive dispatches table (agent, issue, elapsed, tokens)
RightRegistered agents (name, trigger, status)
BottomRecent events timeline (scrollable)

Key bindings

KeyAction
q / EscQuit
/ Scroll events
rForce refresh

Auto-refreshes every 2 seconds.

Data sources

  • Active dispatches: derived from ~/.aide/events.jsonl (dispatched/started without matching finished/failed)
  • Agents: loaded from the aide registry
  • Events: last 100 from ~/.aide/events.jsonl
  • Runs: from ~/.aide/state.db

See also

aide policy-update

Update the routing policy from dispatch history.

Usage

# Standard update: LinUCB bandit learns from events.jsonl
aide policy-update

# Full update: bandit + MEDS failure clustering
aide policy-update --full

What it does

  1. Reads ~/.aide/events.jsonl for finished dispatch events
  2. Extracts task features (12-category keyword classification)
  3. Computes reward per event: r = 0.7 * success + 0.3 * token_efficiency
  4. Updates per-agent LinUCB bandit parameters (A matrix + b vector)
  5. Writes routing weights to ~/.aide/policy.toml

With --full:

  1. Embeds failed tasks via local ollama (nomic-embed-text)
  2. Clusters failures by cosine similarity (threshold 0.75)
  3. Saves failure centroids to ~/.aide/failure_patterns.json

Output

Policy updated: 25 events processed, 5 agents, 14529μs CPU
State: /home/user/.aide/bandit.json
Policy: /home/user/.aide/policy.toml

CATEGORY     TOP AGENT            SCORE
──────────────────────────────────────────
infra        infra-guardian       1.00
gpu          infra-guardian       1.00
pipeline     pipeline-doctor      1.00
web          crossmem-chrome      1.00
...

Options

FlagDescription
--fullAlso run failure clustering (requires ollama + nomic-embed-text)

Daemon integration

The daemon runs policy-update (without --full) on startup and every hour. You only need to run this manually for immediate updates or --full clustering.

See also

aide ab

A/B comparison harness for routing strategies (ICSE issue #95).

Compares two arms over finished events in ~/.aide/events.jsonl:

  • Treatment: bandit — LinUCB + MEDS failure penalty (set when aide dispatch --auto).
  • Control: round-robin — flat cycling over candidates.

Each dispatched event carries an optional routing field; the matching finished event copies it via issue lookup.

Subcommands

aide ab analyze [--events PATH]

Partition finished events by arm and print a per-metric table plus deltas. Warns if either arm has fewer than 5 events; target is 25+ per arm.

ARM              N   SUCCESS_RATE   AVG_TOKENS   AVG_TIME_MS   AVG_COMPRESSION  AVG_REWARD
──────────────────────────────────────────────────────────────────────────────────────
round-robin     50          0.620      33102.0       40221.0            0.0146       0.730
bandit          50          0.840      18011.0       28019.0            0.0140       0.851

deltas (bandit − round-robin; rel = (b-r)/|r|):
  success_rate       abs=     +0.2200  rel= +35.48%
  avg_tokens         abs= -15091.0000  rel= -45.59%
  ...

Default events path: ~/.aide/events.jsonl.

aide ab export [--events PATH] [--out PATH.csv]

Emit a CSV with one row per finished event. Columns:

timestamp,arm,agent,task_category,success,cloud_tokens,local_cpu_ms,compression_ratio,reward,duration_ms

Default output: ./ab-export.csv. task_category is keyword-inferred from the issue + task snippet.

aide ab simulate --n N [--seed N]

Generate N synthetic dispatched+finished event pairs per arm, write to a fresh tempfile (NEVER touches production), and run analyze on it. Useful for validating the analysis pipeline before real experiment data lands.

aide ab simulate --n 50 --seed 42

The simulator is hardcoded so bandit beats round-robin in success rate and tokens, matching the hypothesis under test.

Statistical approach

The harness reports raw absolute and relative deltas (no t-test or bootstrap CI). Rationale: with the target n=25 per arm, classical NHST is underpowered for small effect sizes anyway; deltas + per-event CSV export let downstream notebooks (R / pandas) run whatever test the paper demands.

See also

aide review

Daily observability digest. Read-only — never mutates bandit state, events log, or anything else.

Answers the daily-driver question:

Are any agents repeating the same kind of failure across multiple dispatches, and is the MEDS penalty machinery actually penalizing those patterns?

Usage

# Default: last 7 days, top 5
aide review

# Look back further, show more rows
aide review --days 30 --top 10

# Machine-readable output
aide review --format json
FlagDefaultDescription
--days N7Only consider events from the last N days
--top K5Show top K recurring-failure clusters and top K most-penalized arms
--formattexttext or json

What it reads

  • ~/.aide/events.jsonl — dispatch lifecycle events
  • ~/.aide/bandit.json — per-agent LinUCB state (arm pull counts)
  • ~/.aide/failure_patterns.json (via sidecar::failure_penalty) — MEDS centroids

It calls sidecar::cluster_failures() once to re-cluster failures inside the window. That step depends on local ollama + nomic-embed-text. When ollama is unavailable, the recurring-failures section is empty and the rest of the report still renders (you still see the failure count summary).

Output sections

RECURRING FAILURES

Clusters of failures with cluster_size >= 2. Sorted by cluster size descending, capped at --top.

ColumnMeaning
agentAgent that failed (clusters are per-agent — same task on a different agent is a separate cluster)
categoryDominant 12-class task category from the cluster's representative task
cluster_sizeNumber of failures grouped under this cluster (cosine similarity > 0.75 in embedding space)
exampleRepresentative task snippet for the cluster
first_seen / last_seenRelative time of the earliest / latest matching failed event for that agent in the window

PENALTY APPLIED

For each recurring cluster: the current MEDS penalty for the representative task and that agent.

ColumnMeaning
penaltySigned value: -failure_penalty(example, agent). More negative = bigger drag on bandit score
penalty_applied?Bucket label: heavy, light, or none (review!)
bandit_reward_trendThe 3 most recent reward values for that (agent, category) bucket inside the window, plus the delta r3 - r1

Penalty thresholds

failure_penalty(task, agent) returns a non-negative value in [0, 1]. We classify the raw value:

RawBucketMeaning
>= 0.30heavyThe MEDS centroid for past failures of this kind is strongly biting
>= 0.05lightSome penalty but not material
< 0.05none (review!)The pattern is recurring but the bandit isn't down-weighting it — concerning, investigate

Displayed as a signed value in the penalty column (-0.42 etc.) for visual consistency with reward deltas.

TOP-PENALIZED

Across all recurring clusters, sorted by current_penalty ascending (most negative first). Includes reward_arm_n, the bandit arm's pull count for that agent — high n with heavy penalty means the bandit has converged to "this agent should not get this category".

SUMMARY

Headline counts for the window: total finished events, failed events + success rate, recurring patterns, recurring patterns with no measurable penalty (the most actionable line), and total tokens spent.

Daily cron

Wire up via your scheduler of choice:

0 9 * * * aide review > ~/aide-review-$(date +\%F).txt

TODO: this can also be wired into one of the [trigger] on = "cron:..." agents (see aide list and the Triggers guide) so an agent reads the digest and emails it. Not in this PR — keeps aide review purely read-only.

Constraints

  • Pure read. No mutations to ~/.aide/bandit.json, ~/.aide/events.jsonl, ~/.aide/failure_patterns.json, or anything else.
  • Window cut-off uses event timestamps (ts field, RFC3339).
  • --format json mirrors the text fields under a strict schema; safe to pipe into jq or downstream tooling.

See also

aide emit-claude-agents

Auto-generate Claude Code subagent wrappers from the aide registry.

aide emit-claude-agents
aide emit-claude-agents -o .claude/agents

What it does

For each registered aide agent, generates a .claude/agents/<name>.md file that Claude Code recognizes as a custom subagent. Each wrapper is a thin dispatch layer:

  1. Runs aide dispatch <agent> "<task>"
  2. Captures the issue reference
  3. Runs aide wait <issue-ref>
  4. Returns the bounded summary

This lets a frontier Claude Code session dispatch work to aide agents natively via the Task tool interface, while aide handles token isolation externally.

Flags

FlagDefaultDescription
-o / --output.claude/agentsOutput directory for generated wrappers

Generated wrapper format

---
name: crossmem-rs
description: "Dispatch crossmem-rs work via aide. Runs in isolated token budget."
tools:
  - Bash
---

You are a thin dispatch wrapper for the `crossmem-rs` aide agent.

## Rules
- You ONLY run `aide dispatch` and `aide wait` commands
- Do NOT attempt to do the work yourself

## Workflow
1. Run: `aide dispatch crossmem-rs "{task}"`
2. Capture the issue reference
3. Run: `aide wait {issue_ref}`
4. Return the summary

When to use

Run this after registering new agents or changing Aidefiles. The generated wrappers are gitignore-safe (they're derived, not hand-written) and can be committed to your coordinator repo (e.g. crossmem-hq).

Philosophy

What aide is (and isn't)

aide is a commander — it manages agents, not replaces them. Claude Code does all the thinking. aide decides who works on what, with what context, under what budget.

What's Claude Code (native)

These features exist without aide:

  • claude -p — headless Claude Code, runs a task and exits
  • .claude/agents/ — custom subagent definitions, Claude Code dispatches via Agent tool
  • Auto-memory — Claude Code's built-in per-project memory in ~/.claude/projects/
  • CLAUDE.md — project-level instructions Claude Code reads on startup
  • Hooks — PreToolUse, PostToolUse, PreCompact lifecycle events

Claude Code is already a powerful single-agent runtime. A single agent doesn't need a framework.

What aide adds

aide's value shows up when you have multiple agents that need to coordinate:

Problemaide's answer
Token explosion — frontier context grows with every subtaskaide dispatch runs work in isolated claude -p processes
Secrets scattered across projectsVault — centralized, encrypted, injected at spawn time
Each agent remembers different thingsHQ memory — single source of truth, agents are stateless
No visibility into what agents are doingTelemetry, events timeline, dashboard
Manual routing — you decide which agent gets which taskPolicy — deterministic rules, or frontier fallback
Skill bloat — injecting everything wastes contextPolicy controls which skills get injected per task

What aide does NOT do

  • Replace Claude Code's reasoning, planning, or coding
  • Provide its own LLM runtime
  • Require you to write Python/TypeScript glue code
  • Run containers or cloud infrastructure

Aidefile is to Claude Code what Dockerfile is to Linux

A Dockerfile doesn't replace Linux. It declares how to package and run a process on top of Linux. An Aidefile doesn't replace Claude Code. It declares how to package and run an agent on top of Claude Code.

DockerfileAidefile
FROM, RUN, COPY[persona], [skills]
ENV[vault]
HEALTHCHECK[budget]
ENTRYPOINT[trigger]

A single Aidefile is all you need. Drop it into any project — public or private — and it becomes an agent.

Two layers

Layer 1: Aidefile (single agent)

Any project with an Aidefile is an agent. aide run gives it budget control, vault injection, memory compaction. No HQ, no daemon, no orchestration. This is all most people need.

Layer 2: HQ (multi-agent)

When you have multiple Aidefiles, aide init creates a coordinator repo (HQ) that manages:

  • Dispatch — route tasks to the right agent
  • Memory — centralized team memory, agents are stateless
  • Policy — deterministic routing rules + skill/vault gating
  • Telemetry — token usage, success rate, routing decisions

Layer 2 builds on Layer 1. Every agent in a team is still just a directory with an Aidefile.

The token problem

A frontier Claude Code session has a finite context window. Every subtask you handle inline eats tokens and pushes older context out. With 5 agents doing 50k tokens each, your frontier burns 250k tokens of context — most of which is irrelevant to the next task.

aide solves this by process isolation: aide dispatch spawns a separate claude -p process with its own context window. The frontier only sees the bounded summary. 50k tokens of agent work compresses to ~500 tokens of output.

This is why aide exists. Not to be a framework, but to save tokens through isolation.

Commander, not framework

aide is your commander of agents. You give orders (aide dispatch), aide handles the rest — who gets the task, what context they need, what secrets they get, how much they can spend. Agents do the work and report back. You check results when you're ready.

You → Claude Code (frontier) → aide dispatch → agent (claude -p) → output
         ↑                         ↑
     your session            aide binary handles:
     stays lean              vault, memory, skills,
                             policy, telemetry

Dispatch Protocol

The complete lifecycle of an aide dispatch — from task creation to memory sync.

Overview

aide dispatch <agent> "task"
  │
  ├─ 1. Create GitHub Issue (labeled with agent name)
  ├─ 2. Spawn background worker: `aide run-issue <repo>#<N>`
  │     │
  │     ├─ 3. Fetch issue body + labels from GitHub
  │     ├─ 4. Resolve agent directory from registry
  │     ├─ 5. Run task: `claude -p` with vault + budget
  │     ├─ 6. Post bounded summary as issue comment
  │     ├─ 7. Close issue (on success)
  │     ├─ 8. Sync memory to HQ
  │     └─ 9. Log telemetry
  │
  └─ Return immediately with issue ref

Step by step

1. aide dispatch — create issue

aide dispatch crossmem-rs "fix parser bug in src/lexer.rs"

What happens:

  • Resolves crossmem-rs in ~/.aide/config.toml/path/to/crossmem-rs
  • Reads git remote get-url origincrossmem/crossmem-rs
  • Creates GitHub Issue via gh issue create:
    • Title: first line of task (truncated to 80 chars)
    • Body: full task wrapped in ## Task section
    • Label: agent name (crossmem-rs)
  • Spawns a detached background worker: aide run-issue crossmem/crossmem-rs#42
  • Returns immediately — the frontier session is not blocked

Output:

dispatched: crossmem/crossmem-rs#42
agent: crossmem-rs
budget: 250000 tokens
wait: aide wait https://github.com/crossmem/crossmem-rs/issues/42

2. aide run-issue — background worker

Runs in a separate process (stdout goes to ~/.aide/logs/dispatch-crossmem_crossmem-rs-42.log).

Steps:

  1. Fetch issuegh issue view 42 --repo crossmem/crossmem-rs --json title,body,labels
  2. Resolve agent — first label = agent name → look up in registry → directory path
  3. Run task — calls runner::run():
    • Parse Aidefile for budget, vault keys, hooks, workspace, output config
    • Decrypt vault secrets
    • Run on_spawn hooks
    • Wrap task with <aide-summary> instructions (forces sub-agent to emit bounded output)
    • Loop claude -p until success or budget exhausted
    • Run on_complete hooks
  4. Post summarygh issue comment with the bounded summary:
    STATUS: success
    TOKENS: 18432/250000
    RETRIES: 1
    CHANGED: src/lexer.rs, src/parser.rs
    NOTES: Fixed off-by-one in lexer token boundary detection.
    PR: none
    NEXT: none
    
  5. Close issuegh issue close (only on success)
  6. Sync memory to HQ — see below
  7. Log telemetry — tokens used, compression ratio, duration → SQLite + events.jsonl

3. Memory sync to HQ

After the worker posts the summary and closes the issue:

  1. Check config — read hq from ~/.aide/config.toml. If not set → skip (backward compatible).
  2. Read old memory<hq>/memory/<agent>/context.md (may not exist yet)
  3. Read shared memory<hq>/memory/_shared/*.md (team-level context)
  4. Distillclaude -p with a merge prompt:
    • Input: old memory + shared memory + new dispatch summary
    • Rules: merge (not append), deduplicate, remove stale info, keep concise
    • Output: updated context.md or NOTHING_NEW if nothing worth remembering
  5. Write — overwrite <hq>/memory/<agent>/context.md
  6. Git commitgit add memory/<agent>/ && git commit -m "memory: sync <agent> @ <timestamp>"

The HQ git log becomes the version history of agent memory:

e879c97 memory: sync crossmem-rs @ 2026-04-14 09:38
e9424c2 memory: sync crossmem-web @ 2026-04-14 09:38
40d664a memory: sync crossmem-chrome @ 2026-04-14 09:38

4. aide wait — frontier blocks on result

aide wait crossmem/crossmem-rs#42

Polls gh issue view every 5s (configurable). When the issue closes:

  • Extracts the last comment (the bounded summary)
  • Prints it to stdout — this is the only thing that enters the frontier session context
  • Exit code: 0 (success), 1 (partial/failed), 2 (cancelled), 124 (timeout)

Cross-machine dispatch

GitHub Issues bridge machines. The dispatch and the worker don't need to be on the same host.

Machine A (your Mac):
  aide dispatch infra-guardian "check GPU queue"
  → creates GitHub Issue
  → aide wait (polls GitHub)

Machine B (formace-00):
  aide daemon polls gh issue list
  → picks up the issue
  → aide run-issue (local claude -p)
  → posts summary comment, closes issue
  → memory sync → git commit to storylens-hq

Machine A:
  aide wait returns with the summary

Requirements:

  • Both machines have gh authenticated
  • Both machines have the agent registered in ~/.aide/config.toml
  • HQ repo is git-cloneable from both machines (push/pull for memory sync)

Issue lifecycle

StateMeaning
OpenTask dispatched, worker running (or pending daemon pickup)
Open + commentWorker posted progress or error
Closed + summary commentWorker completed — summary is the last comment
Closed + "cancelled"User ran aide cancel

Bounded summary format

The sub-agent's output is wrapped in an <aide-summary> block, enforced by prompt injection:

STATUS: success | partial | timeout | error
TOKENS: <used>/<limit>
RETRIES: <count>
CHANGED: <files> or (none)
NOTES: <one-line summary of what was done>
PR: <url> or none
NEXT: <optional redirect suggestion for coordinator>

This is controlled by [output] in the Aidefile:

  • max_summary_tokens — cap on summary length (default: 500)
  • narrative_schema — the template the sub-agent fills in

If the sub-agent doesn't emit the block, the runner falls back to a truncated tail of raw output.

How aide run Works

Detailed walkthrough of what happens when you run aide run reviewer "Review PR #42".

1. Resolution

aide resolves "reviewer" by checking:

  1. The registry (~/.aide/config.toml) for a matching name
  2. If not found, treats it as a filesystem path and checks for an Aidefile

2. Aidefile parsing

The Aidefile is parsed from TOML. All sections are optional except that [persona].name defaults to "unnamed".

3. Vault decryption

If [vault].keys is set:

  1. Run age -d -i vault.key vault.age in the agent directory
  2. Parse the decrypted output as export KEY='VALUE' lines
  3. Filter to only the keys listed in [vault].keys
  4. Store as Vec<(String, String)> for later injection

4. on_spawn hooks

Each hook in [hooks].on_spawn is executed in order:

  • Built-in hooks (like inject-vault) are handled internally
  • Custom hooks are run as shell commands in the agent directory

5. Task loop

while budget.can_invoke():
    result = claude -p <task> --output-format json
    budget.record(result.tokens_used)
    if result.success:
        break

Key details:

  • claude -p runs with vault secrets as env vars (via Command::env())
  • Token usage is extracted from the JSON output
  • Budget uses saturating arithmetic (formally verified with kani)
  • The loop stops when the task succeeds OR budget/retries are exhausted

6. on_complete hooks

Same as on_spawn — executed in order after the task loop finishes.

7. Memory compaction check

If [memory].compact_after is set:

  1. Estimate total tokens in memory/ (file bytes / 4)
  2. If over threshold, run claude -p with a compaction prompt
  3. This compaction invocation also counts against the budget

Dispatch flow (aide-as-subagent)

When an agent needs to delegate work to another agent, the dispatch flow provides token isolation:

aide dispatch → gh issue create → spawn aide run-issue →
  runner::run (budgeted claude -p) → build_summary →
  gh issue comment (bounded summary) → gh issue close →
  aide wait picks up summary → returns to frontier

Why token isolation matters

Without dispatch, a sub-task runs inside the calling agent's context window. A 50k-token sub-task expands the frontier, consuming budget and degrading the caller's reasoning quality. With dispatch, the sub-agent runs in its own claude -p invocation with an independent token budget. Only the bounded summary (controlled by [output].max_summary_tokens in the Aidefile) flows back to the caller.

How it works

  1. aide dispatch <agent> "<task>" creates a GitHub issue labeled for the target agent, then spawns a detached aide run-issue worker. The caller gets back an issue URL immediately.
  2. aide run-issue picks up the issue, runs the agent's task loop (same as aide run), builds a summary conforming to the [output].narrative_schema, posts it as a closing comment, and closes the issue.
  3. aide wait <issue-url> polls the issue until it closes, then extracts the bounded summary from the final comment and returns it to the calling agent's context.

This three-step handshake keeps each agent's context window independent while allowing structured results to flow back through the GitHub Issues transport.