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
| Concern | Single agent | Team (HQ) |
|---|---|---|
| Budget | Token limits, auto-retry | Per-agent budgets |
| Vault | Encrypted secrets → env vars | HQ controls who gets what |
| Memory | Per-agent compaction | Centralized at HQ, agents stateless |
| Skills | Injected at spawn | Policy controls injection |
| Routing | — | Policy rules or frontier fallback |
| Telemetry | — | Token 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
| Feature | Claude Code (native) | aide (adds) |
|---|---|---|
| Run a task | claude -p "task" | aide run agent "task" — with budget + vault |
| Subagents | .claude/agents/*.md | aide dispatch — token-isolated processes |
| Memory | ~/.claude/projects/*/memory/ | HQ/memory/ — centralized SSOT |
| Secrets | env vars, manual | Vault — encrypted, gated by HQ |
| Routing | you decide | Policy — deterministic rules |
Next steps
- Installation — get the binary
- Quick Start — your first agent in 2 minutes
- Concepts — agents, Aidefiles, single vs team
Installation
From crates.io (recommended)
cargo install aide-sh
This installs the aide binary.
Prerequisites
- Rust toolchain — rustup.rs if you don't have
cargo - Claude Code CLI — aide calls
claude -punder the hood. Install from claude.ai/code - age (optional) — for vault encryption.
brew install ageor 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 membermemory/— 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 — agents, Aidefiles, two-layer architecture
- Aidefile reference — full configuration
- Memory — HQ memory SSOT, auto-distillation
- Dispatch — dispatch, wait, cancel, events
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)
| Feature | How it works |
|---|---|
claude -p "task" | Headless Claude Code — runs a task and exits |
.claude/agents/*.md | Custom subagent definitions, dispatched via Agent tool |
~/.claude/projects/*/memory/ | Auto-memory, Claude Code manages per-project |
CLAUDE.md | Project instructions, read on session start |
| Hooks | PreToolUse, PostToolUse, PreCompact lifecycle events |
A single Claude Code project doesn't need aide. Claude Code is already a capable agent runtime.
aide adds
| Feature | How it works |
|---|---|
| Aidefile | Single config: persona, budget, vault, hooks, trigger, skills |
| Token isolation | aide dispatch runs work in separate claude -p processes |
| Vault | Encrypted secrets, injected as env vars at spawn time |
| Team memory | Centralized at HQ, agents are stateless |
| Policy routing | Deterministic rules decide which agent gets which task |
| Skill injection | Policy controls which skills are injected per task |
| Telemetry | Token usage, duration, success/fail per dispatch |
| Daemon | Background 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 Aidefileaide 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 listfor 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]
| Field | Type | Default | Description |
|---|---|---|---|
name | string | "unnamed" | Agent display name |
style | string | — | Personality hint (injected into CLAUDE.md context) |
[budget]
| Field | Type | Default | Description |
|---|---|---|---|
tokens | string | "200k" | Token limit per task. Supports "100k", "1m", or raw numbers. |
max_retries | integer | 3 | Maximum retry attempts if task doesn't complete in one invocation |
[memory]
| Field | Type | Default | Description |
|---|---|---|---|
compact_after | string | — | Auto-compact memory when estimated tokens exceed this threshold |
[hooks]
| Field | Type | Description |
|---|---|---|
on_spawn | list of strings | Run before the task starts |
on_complete | list of strings | Run after the task finishes |
Built-in hooks:
"inject-vault"— decrypt and inject vault secrets (handled automatically by the runner)"commit-memory"— git add + commit thememory/directory
Custom hooks are shell commands run in the agent's directory.
[skills]
| Field | Type | Description |
|---|---|---|
include | list of strings | Skill names to load from the skills/ directory |
[trigger]
| Field | Type | Description |
|---|---|---|
on | string | Trigger type: "manual", "issue", or "cron:EXPR" |
[vault]
| Field | Type | Description |
|---|---|---|
keys | list of strings | Secret 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>
"""
| Field | Type | Default | Description |
|---|---|---|---|
max_summary_tokens | u32 | 500 | Max tokens in the bounded summary |
narrative_schema | string | (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"]
| Field | Type | Default | Description |
|---|---|---|---|
read | list 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:
| Input | Value |
|---|---|
"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 -pcan be invoked for a single task
How it works
- aide calls
claude -pwith--output-format json - Claude Code returns token usage in its JSON output
- aide tracks accumulated tokens across invocations
- If
accumulated >= limitorinvocations > 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
| Input | Value |
|---|---|
"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
- on_spawn — runs before the first
claude -pinvocation - task loop —
claude -pinvocations with budget tracking - on_complete — runs after the task finishes (success or budget exhausted)
Built-in hooks
| Hook | Phase | Description |
|---|---|---|
inject-vault | on_spawn | Decrypt vault and inject secrets as env vars. This is handled automatically by the runner — you rarely need to list it explicitly. |
commit-memory | on_complete | git 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:
- The issue title + body become the task
- aide runs the agent with that task
- 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_firemap prevents double-dispatch within a single tick window. - On first encounter the map is seeded to
nowso 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 onrestart = "on-failure".
Observability (new in v2.0.0-alpha.10):
- Every cron fire writes a matching
dispatched+finishedpair to~/.aide/events.jsonlwithkind=dispatched/finished,issue="cron:<expr>",routing="cron", and the same telemetry fields (cloud_tokens,reward,task_category) as issue-triggered runs. aide events --limit Nsurfaces cron fires alongside issue dispatches.aide ab analyzetreats"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 runwhen 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:
https://github.com/user/repo.git[email protected]:user/repo.git
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
| Mode | When the supervisor restarts |
|---|---|
always | After every run — both success and failure. Use for true daemons. |
on-failure | Only when the run failed (non-zero exit / runner Err / budget exhausted). Default when [daemon] is present. |
never | Never 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):
| Strategy | Sleep sequence (seconds) |
|---|---|
exponential | 1, 2, 4, 8, 16, 32, 64, 128, 256, 300, 300, … |
linear | 1, 2, 3, 4, 5, … |
fixed | 1, 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:
- Liveness — the daemon writes a heartbeat file every tick.
- 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
| Layer | Location | Who manages | Persistent? |
|---|---|---|---|
| Session memory | Claude 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:
- Read existing
memory/<agent>/context.md(old memory) - Read
memory/_shared/(team context, read-only) - LLM merges old + new summary — not append, but intelligent merge with dedup
- Write updated
context.md - 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 - Rollback —
git revertif 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_afterslightly below your typical task budget to avoid compaction eating into task tokens - Use the
commit-memoryhook 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
| Type | Field | Default path | Purpose | Pruned after |
|---|---|---|---|---|
core | core_files | (explicit list) | Always-loaded persona, standing rules, project invariants | never |
procedural | procedural_dir | memory/procedural | Append-only logs: skill records, failure notes, patterns | 90 days |
episodic | episodic_dir | memory/episodic | Per-task scratch, transient observations | 7 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 withmtimeolder than 7 daysprocedural→ delete files withmtimeolder than 90 dayscore→ 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.
| Op | Behavior |
|---|---|
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
| Field | Required | Constraint |
|---|---|---|
name | yes | 1-64 chars, ^[a-z0-9]+(-[a-z0-9]+)*$, MUST equal parent dir name |
description | yes | 1-1024 chars, non-empty |
license | no | free-form short string |
compatibility | no | 1-500 chars (e.g. "Requires Python 3.14+") |
metadata | no | flat map<string, string> |
allowed-tools | no | space-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:
~/.aide/skills/<name>/<agent_dir>/.aide-skills/<name>/(only when--agentis supplied)~/.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 inscripts/,references/,assets/work. - Entry point: first match of
scripts/run.sh→scripts/run.py→scripts/main.sh→scripts/main.py. No match = error. - Environment exported:
AIDE_HOME—$AIDE_HOMEoverride or~/.aideAIDE_SKILL_NAME— the skill nameAIDE_SKILL_DIR— absolute path to the skill rootAIDE_RUN_ID— nanos-based unique idAIDE_AGENT— the--agentvalue, if providedPATH— 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 markedtruncated=true. - Exit code: 0 = success; non-zero is logged, not bailed —
aide skill execitself 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:
- Generates
<skill>/SKILL.md(name from dir, description from the first# commentinrun.sh). - Moves
run.shtoscripts/run.shand marks it executable. - Skips dirs that already contain a
SKILL.md, or that don't have arun.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:
AidefileCLAUDE.mdskills/
It excludes private data:
memory/— per-deployment statevault.key— private encryption keyvault.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:
- Each task is classified into 12 categories (infra, gpu, memory, disk, network, pipeline, parser, web, test, debug, deploy, security) via keyword matching
- Each agent has a bandit "arm" that tracks its performance per category
- When routing, the bandit scores each agent: exploitation (past success) + exploration (uncertainty bonus)
- 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:
- Failed tasks are embedded via local ollama (
nomic-embed-text, 768 dims) - Greedy cosine-similarity clustering groups recurring failure patterns
- 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
| File | Purpose |
|---|---|
~/.aide/bandit.json | Per-agent LinUCB state (A matrices, b vectors) |
~/.aide/policy.toml | Routing weights per category, updated by policy-update |
~/.aide/failure_patterns.json | Failure 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
| Feature | Requirement |
|---|---|
| Bandit routing | None (pure CPU) |
--auto dispatch | Registered agents + events history |
| Failure clustering | ollama + 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
mode | Behavior |
|---|---|
bandit | Pick the top-scored agent from sidecar::select_agent. |
round-robin | Cycle through candidates via the file-backed RR counter. |
fanout | Dispatch the same task to every candidate. |
Candidate resolution
- If
[router].candidatesis non-empty, that explicit list wins. - If empty, the HQ's group members (from
~/.aide/config.toml) are used, matched bygroups[*].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:
- GitHub issues opened against the HQ repo.
- Member-agent completions (local tasks routed by
[router]finishing). - 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_handlekicks 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:
- Ensures
Aidefilehas a[router]block (creates the file if missing). - Writes/merges
.claude/settings.local.jsonto add Claude Code hooks (SessionStartandUserPromptSubmit) that callaide hq-touch <hq>— so every user turn refreshes the activity marker. - Appends an "HQ inbox protocol" section to
CLAUDE.mdinstructing 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:
- Summarize the new item for you in one sentence.
- Ask how to handle it (route to a member, defer, ignore).
- 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_activitymtime — if newer than the threshold, no-op. - Otherwise, drains the inbox in FIFO order, routing each item via
router::route_issueusing 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>.json → done/<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
| Source | Producer |
|---|---|
github-issue | router::route_issue after dispatching to a member |
member-completed | dispatch::run_local_task finalize when routed_from_hq is set |
cli | aide 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
- Prerequisites check — verifies
ghandclaudeCLI are installed, checks GitHub auth - Scan for projects — finds directories with
.git,Aidefile,src/,package.json, etc. - Select members — interactive multi-select of which projects to include as agents
- 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/
- Register agents — adds all members to
~/.aide/config.toml - 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
| Flag | Description |
|---|---|
--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 membermemory/<agent>/— directory for auto-distilled memory (SSOT)- Registry entry —
aide registersoaide 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
- Resolve agent name → directory path
- Load and parse Aidefile
- Decrypt vault secrets (if configured)
- Run
on_spawnhooks - Loop: invoke
claude -pwith the task- Track token usage after each invocation
- Stop if budget exhausted or task complete
- Run
on_completehooks - 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
| Flag | Description |
|---|---|
--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>]
| Flag | Description |
|---|---|
--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 - STATUS —
running,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 okorn/m fail:name1,name2
Related
- Health & Dependencies guide — declare what to monitor
- Daemon (aide up) — start the heartbeat writer
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
issuetriggers: pollsgh issue listfor matching issues - Dispatches
aide runwhen 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>]
| Flag | Description |
|---|---|
--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
| Tool | Description |
|---|---|
aide_run | Run a task on a registered agent |
aide_list | List all registered agents |
aide_spawn | Create a new agent |
aide_vault_get | Retrieve 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
| Flag | Description |
|---|---|
--dry-run | Print the issue that would be created without actually creating it or spawning a worker |
--auto, -a | Auto-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
| Flag | Default | Description |
|---|---|---|
--timeout | 60m | Maximum time to wait before returning exit code 124 |
--poll-interval | 10s | How often to check issue status |
Exit codes
| Code | Meaning |
|---|---|
| 0 | Sub-agent completed successfully |
| 1 | Sub-agent reported partial completion or failure |
| 2 | Issue was cancelled (closed without summary) |
| 124 | Timeout 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
- Looks up the worker PID from the local SQLite state database
- Sends
SIGTERMto kill theclaude -pprocess - Posts
STATUS: cancelled (by user)as an issue comment - Closes the GitHub issue
- Marks the run as cancelled in the state database
- Logs a
cancelledevent 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
| Flag | Default | Description |
|---|---|---|
--limit | 50 | Number 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
- Reads existing HQ
memory/<agent>/context.md - Reads
memory/_shared/(team-level context) - Uses
claude -pto merge old memory + new summary - Writes updated
context.md - 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
| Flag | Default | Description |
|---|---|---|
--port | 7979 | Port to listen on |
Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/runs | GET | List recent runs with status and token usage |
/api/agents | GET | List registered agents |
/api/heartbeat | GET | Last heartbeat timestamp from daemon |
/api/stats | GET | Aggregate stats (today's runs, tokens, agents) |
/api/health | GET | Health check (returns 200 if daemon is running) |
/api/telemetry | GET | Dispatch 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
| Platform | Mechanism | Location |
|---|---|---|
| macOS | launchd plist | ~/Library/LaunchAgents/sh.aide.daemon.plist |
| Linux | systemd 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
| Flag | Description |
|---|---|
--local | Use the local FS queue under ~/.aide/dispatch/<agent>/ instead of GitHub |
--dry-run | Print 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
| Panel | Content |
|---|---|
| Top bar | Title, timestamp, active dispatch count |
| Left | Active dispatches table (agent, issue, elapsed, tokens) |
| Right | Registered agents (name, trigger, status) |
| Bottom | Recent events timeline (scrollable) |
Key bindings
| Key | Action |
|---|---|
q / Esc | Quit |
↑ / ↓ | Scroll events |
r | Force 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-skill aide watch— lightweight terminal monitor (no TUI, just prints)aide-skill aide serve— web dashboard at localhost:7610
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
- Reads
~/.aide/events.jsonlfor finished dispatch events - Extracts task features (12-category keyword classification)
- Computes reward per event:
r = 0.7 * success + 0.3 * token_efficiency - Updates per-agent LinUCB bandit parameters (A matrix + b vector)
- Writes routing weights to
~/.aide/policy.toml
With --full:
- Embeds failed tasks via local ollama (
nomic-embed-text) - Clusters failures by cosine similarity (threshold 0.75)
- 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
| Flag | Description |
|---|---|
--full | Also 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
- Smart Routing guide — how the bandit works
- aide dispatch --auto — auto-select agent using bandit scores
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 whenaide 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 policy-update — how the bandit learns
- aide dispatch —
--autoflag tags events asrouting=bandit
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
| Flag | Default | Description |
|---|---|---|
--days N | 7 | Only consider events from the last N days |
--top K | 5 | Show top K recurring-failure clusters and top K most-penalized arms |
--format | text | text 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(viasidecar::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.
| Column | Meaning |
|---|---|
agent | Agent that failed (clusters are per-agent — same task on a different agent is a separate cluster) |
category | Dominant 12-class task category from the cluster's representative task |
cluster_size | Number of failures grouped under this cluster (cosine similarity > 0.75 in embedding space) |
example | Representative task snippet for the cluster |
first_seen / last_seen | Relative 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.
| Column | Meaning |
|---|---|
penalty | Signed value: -failure_penalty(example, agent). More negative = bigger drag on bandit score |
penalty_applied? | Bucket label: heavy, light, or none (review!) |
bandit_reward_trend | The 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:
| Raw | Bucket | Meaning |
|---|---|---|
>= 0.30 | heavy | The MEDS centroid for past failures of this kind is strongly biting |
>= 0.05 | light | Some penalty but not material |
< 0.05 | none (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 (seeaide listand the Triggers guide) so an agent reads the digest and emails it. Not in this PR — keepsaide reviewpurely 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 (
tsfield, RFC3339). --format jsonmirrors the text fields under a strict schema; safe to pipe intojqor downstream tooling.
See also
- aide policy-update — runs the MEDS clustering that produces the centroids
aide reviewchecks - aide ab analyze — A/B comparison of bandit vs round-robin routing
- Smart Routing guide — how the bandit and penalty work together
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:
- Runs
aide dispatch <agent> "<task>" - Captures the issue reference
- Runs
aide wait <issue-ref> - 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
| Flag | Default | Description |
|---|---|---|
-o / --output | .claude/agents | Output 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:
| Problem | aide's answer |
|---|---|
| Token explosion — frontier context grows with every subtask | aide dispatch runs work in isolated claude -p processes |
| Secrets scattered across projects | Vault — centralized, encrypted, injected at spawn time |
| Each agent remembers different things | HQ memory — single source of truth, agents are stateless |
| No visibility into what agents are doing | Telemetry, events timeline, dashboard |
| Manual routing — you decide which agent gets which task | Policy — deterministic rules, or frontier fallback |
| Skill bloat — injecting everything wastes context | Policy 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.
| Dockerfile | Aidefile |
|---|---|
| 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-rsin~/.aide/config.toml→/path/to/crossmem-rs - Reads
git remote get-url origin→crossmem/crossmem-rs - Creates GitHub Issue via
gh issue create:- Title: first line of task (truncated to 80 chars)
- Body: full task wrapped in
## Tasksection - 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:
- Fetch issue —
gh issue view 42 --repo crossmem/crossmem-rs --json title,body,labels - Resolve agent — first label = agent name → look up in registry → directory path
- Run task — calls
runner::run():- Parse Aidefile for budget, vault keys, hooks, workspace, output config
- Decrypt vault secrets
- Run
on_spawnhooks - Wrap task with
<aide-summary>instructions (forces sub-agent to emit bounded output) - Loop
claude -puntil success or budget exhausted - Run
on_completehooks
- Post summary —
gh issue commentwith 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 - Close issue —
gh issue close(only on success) - Sync memory to HQ — see below
- 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:
- Check config — read
hqfrom~/.aide/config.toml. If not set → skip (backward compatible). - Read old memory —
<hq>/memory/<agent>/context.md(may not exist yet) - Read shared memory —
<hq>/memory/_shared/*.md(team-level context) - Distill —
claude -pwith a merge prompt:- Input: old memory + shared memory + new dispatch summary
- Rules: merge (not append), deduplicate, remove stale info, keep concise
- Output: updated
context.mdorNOTHING_NEWif nothing worth remembering
- Write — overwrite
<hq>/memory/<agent>/context.md - Git commit —
git 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
ghauthenticated - 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
| State | Meaning |
|---|---|
| Open | Task dispatched, worker running (or pending daemon pickup) |
| Open + comment | Worker posted progress or error |
| Closed + summary comment | Worker 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:
- The registry (
~/.aide/config.toml) for a matching name - 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:
- Run
age -d -i vault.key vault.agein the agent directory - Parse the decrypted output as
export KEY='VALUE'lines - Filter to only the keys listed in
[vault].keys - 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 -pruns with vault secrets as env vars (viaCommand::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:
- Estimate total tokens in
memory/(file bytes / 4) - If over threshold, run
claude -pwith a compaction prompt - 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
aide dispatch <agent> "<task>"creates a GitHub issue labeled for the target agent, then spawns a detachedaide run-issueworker. The caller gets back an issue URL immediately.aide run-issuepicks up the issue, runs the agent's task loop (same asaide run), builds a summary conforming to the[output].narrative_schema, posts it as a closing comment, and closes the issue.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.