Storage
Where AO keeps session metadata, worktrees, observability traces, and agent workspace hooks on disk.
AO has no database. Everything lives in flat files and directories you can inspect with cat and ls. Session state is plain KEY=VALUE text, worktrees are ordinary git worktrees, and observability data is JSON written atomically to a well-known directory. This makes it easy to debug, back up, or manually recover from any state.
Directory layout
~/.agent-orchestrator/
├── {hash}-{projectId}/ # One directory per project per config location
│ ├── .origin # Config path recorded for collision detection
│ ├── sessions/
│ │ ├── {sessionId} # Flat KEY=VALUE metadata file
│ │ └── archive/
│ │ └── {sessionId}_{timestamp} # Archived metadata after cleanup
│ └── feedback-reports/
│ └── report_{id}.kv # Agent bug/improvement reports (KEY=VALUE)
└── {hash}-observability/
└── processes/
└── {component}-{pid}.json # Per-process observability snapshot
~/.worktrees/
└── {projectId}/
└── {sessionId}/ # The actual git worktree
└── .ao/
├── AGENTS.md # Injected session context (gitignored)
└── activity.jsonl # Activity stream (agents without native JSONL)
~/.ao/
└── bin/
├── ao-metadata-helper.sh # Shared bash helper (sourced by wrappers)
├── gh # PATH wrapper intercepting gh pr create/merge
├── git # PATH wrapper intercepting git branch ops
└── .ao-version # Wrapper version marker (skip reinstall when current)The worktree base defaults to ~/.worktrees/. You can override it via the worktreeDir config option on the workspace plugin (see Configuration reference). The ~/.agent-orchestrator/ base can be overridden with AO_DATA_DIR.
Hash-based isolation
Every ~/.agent-orchestrator/ subdirectory is prefixed with a 12-character hash derived from the absolute path of the directory that contains agent-orchestrator.yaml. AO computes sha256(realpath(configDir)).slice(0, 12) and combines it with the project ID to form a directory name like a3b4c5d6e7f8-myapp.
This hash ensures that multiple checkouts of the same repo — for example ~/work/myapp and ~/personal/myapp — each get their own isolated storage directory, even when both use a project with the same ID. Sessions, worktrees, and archives never collide across checkouts.
The .origin file inside each project directory records the resolved config path written on first run. On every subsequent run AO reads .origin and confirms it still matches. If two config directories ever happen to produce the same 12-character hash prefix (a SHA-256 collision in 48 bits — vanishingly rare but theoretically possible), AO throws an explicit error rather than silently corrupting data.
Session prefix derivation
Every session gets a short human-readable ID like int-1 or ao-3. The prefix part is derived automatically from the project ID using these rules, applied in order:
| Rule | Condition | Example |
|---|---|---|
| Use as-is | Project ID is 4 characters or fewer | api → api |
| CamelCase initials | Project ID has more than one uppercase letter | MyApp → ma |
| Kebab/snake initials | Project ID contains - or _ | my-service → ms, my_app → ma |
| First 3 characters | Single lowercase word longer than 4 chars | integrator → int |
Additional examples: myapp → mya, agent-orchestrator → ao, PyTorch → pt.
You can override the derived prefix by setting sessionPrefix in your project config:
projects:
my-service:
sessionPrefix: svc # sessions will be svc-1, svc-2, ...Tmux session names are globally unique and include the hash: {hash}-{prefix}-{num} (e.g., a3b4c5d6e7f8-svc-1). The user-facing session ID (svc-1) is what appears in the dashboard and CLI output.
Session metadata format
Each active session has a metadata file at ~/.agent-orchestrator/{hash}-{projectId}/sessions/{sessionId}. The format is plain KEY=VALUE text, one pair per line — the same format bash scripts can read with grep or source:
project=my-service
worktree=/home/user/.worktrees/my-service/svc-1
branch=feat/ISSUE-42
status=working
tmuxName=a3b4c5d6e7f8-svc-1
issue=ISSUE-42
pr=https://github.com/org/repo/pull/99
agent=claude-code
createdAt=2024-01-15T10:30:00.000Z
runtimeHandle=a3b4c5d6e7f8-svc-1All keys defined in the metadata schema:
| Key | Type | Description |
|---|---|---|
worktree | string | Absolute path to the git worktree |
branch | string | Git branch name (e.g., feat/ISSUE-42) |
status | string | Session lifecycle state (e.g., working, pr_open, merged) |
tmuxName | string | Globally unique tmux session name including hash |
issue | string | Tracker issue ID (e.g., INT-1234) |
pr | string | PR URL set when the agent creates a pull request |
prAutoDetect | "on" | "off" | Whether automatic PR detection is enabled |
summary | string | Short summary of what the agent is working on |
project | string | Project ID from config |
agent | string | Agent plugin name (e.g., claude-code, codex) |
createdAt | ISO timestamp | When the session was spawned |
runtimeHandle | string | Runtime-specific handle (tmux session name) |
restoredAt | ISO timestamp | Set when the session is restored from archive |
role | string | Session role (worker, orchestrator) |
dashboardPort | number | Dashboard HTTP port (set at spawn time) |
terminalWsPort | number | Terminal WebSocket port |
directTerminalWsPort | number | Direct terminal WebSocket port |
opencodeSessionId | string | OpenCode-internal session ID (OpenCode agent only) |
pinnedSummary | string | User-pinned summary that persists across state changes |
userPrompt | string | Original user prompt that spawned the session |
You can inspect any session's state at any time:
cat ~/.agent-orchestrator/$(ls ~/.agent-orchestrator | head -1)/sessions/svc-1Archive behavior
When a session is cleaned up (after merging, ao session cleanup, or manual removal), AO moves the metadata file to sessions/archive/{sessionId}_{timestamp}. The timestamp is an ISO 8601 string with colons and dots replaced by dashes, for example svc-1_2024-01-15T10-30-00-000Z.
Archives are retained indefinitely — AO never automatically deletes them. To recover a session's last known state, read the archive file directly or use ao session restore.
ao session ls --all shows orchestrator-role sessions alongside worker sessions, not archived sessions. Archived sessions are not shown in the dashboard or CLI by default. To inspect archives, browse sessions/archive/ directly.
Observability directory
The {hash}-observability/processes/ directory holds per-process JSON snapshots. Each running AO process (CLI, dashboard server, lifecycle manager) writes its own file named {component}-{pid}.json. When you open the dashboard's observability view, the server merges all snapshot files into a single summary.
Each snapshot file records:
- metrics — counters keyed by
{projectId}::{metricName}, trackingtotal,success,failure, and the timestamps of the last operation and last failure. Tracked metrics includespawn,kill,lifecycle_poll,restore,claim_pr,cleanup,api_request,send, and WebSocket/SSE connection events. - traces — the 80 most recent operation trace records. Each trace includes a UUID, ISO timestamp, component name, operation name, outcome, correlation ID, and optional duration, session ID, project ID, and reason.
- sessions — the 200 most recently active sessions, showing last operation, outcome, and timestamp.
- health — named health surfaces (e.g.,
github-api,tmux) with statusok,warn, orerror, plus optional reason and details.
The dashboard serves this merged view at /api/observability. You can also read the files directly for offline debugging:
ls ~/.agent-orchestrator/*-observability/processes/
cat ~/.agent-orchestrator/*-observability/processes/*.json | jq '.metrics'See Architecture for how the observability system fits into the broader data flow.
Agent workspace .ao/ directory
When AO creates a worktree for a non-Claude-Code agent, it injects a .ao/ directory inside the worktree root. This directory is automatically gitignored and never affects tracked files.
.ao/AGENTS.md
A markdown file that tells the agent it is running inside an AO-managed session. It contains an "Agent Orchestrator (ao) Session" section with instructions and a note that session metadata is updated automatically via shell wrappers. If the wrappers are bypassed for any reason, the file documents how to call update_ao_metadata manually.
Claude Code does not use .ao/AGENTS.md. It receives session context through its own .claude/settings.json PostToolUse hook mechanism instead.
.ao/activity.jsonl
Agents that do not have native JSONL activity streams (Aider, OpenCode) write their activity state here. The lifecycle manager calls recordActivity() on every poll cycle, which classifies the latest terminal output, deduplicates entries within a 20-second window for non-actionable states, and appends a JSONL entry. The dashboard and stuck-detection logic read from this file via readLastActivityEntry().
Claude Code and Codex use their own native JSONL files and do not write to .ao/activity.jsonl.
PATH wrappers
~/.ao/bin/gh and ~/.ao/bin/git are shell wrapper scripts installed once per machine by setupPathWrapperWorkspace(). The wrappers are versioned (0.2.0 at time of writing); AO checks a .ao-version marker file and only reinstalls when the version changes.
~/.ao/bin/gh intercepts two subcommands:
gh pr create— extracts the new PR URL from the command's output and writespr={url}andstatus=pr_opento the session metadata file.gh pr merge— writesstatus=mergedto the session metadata file.
All other gh subcommands are passed through transparently via exec.
~/.ao/bin/git intercepts branch operations:
git checkout -b {branch}andgit switch -c {branch}— writesbranch={name}to metadata.git checkout {branch}andgit switch {branch}— writesbranch={name}only when the branch name looks like a feature branch (contains/or-), avoiding noise from checkouts of plain branches likemain.
The wrappers rely on two environment variables set by AO for each spawned agent process:
| Variable | Value | Purpose |
|---|---|---|
AO_SESSION | session ID (e.g., svc-1) | Identifies which metadata file to update |
AO_DATA_DIR | sessions directory path | Absolute path to the sessions/ folder for this project |
Metadata updates are performed safely: the helper script validates that AO_DATA_DIR is under a known trusted root (~/.agent-orchestrator/ or ~/.ao/), checks for path traversal in the session name, escapes sed metacharacters in values, and uses a temp-file-plus-rename pattern to prevent partial writes.
Claude Code uses PostToolUse hooks in .claude/settings.json instead of PATH wrappers. The hooks call the same metadata-update logic but through Claude Code's native hook system rather than shell interception.
If you run an agent manually in a worktree, prepend ~/.ao/bin to your PATH so the wrappers intercept gh and git commands:
export PATH="$HOME/.ao/bin:$PATH"
cd ~/.worktrees/my-service/svc-1
# now run the agent binary directlyAO does this automatically for all spawned sessions.
Feedback reports
The feedback-reports/ directory accumulates reports written by agents via two tools that AO advertises to them: bug_report and improvement_suggestion. These tools are meant for agents to surface AO-specific issues they encounter during a session (bugs in the orchestrator itself, or ideas for improvement).
Each report is stored as a report_{timestamp}_{uuid8}.kv file using the same KEY=VALUE format as session metadata. A report contains:
| Field | Description |
|---|---|
version | Schema version (currently 1) |
id | Report ID matching the filename |
tool | Either bug_report or improvement_suggestion |
createdAt | ISO timestamp |
dedupeKey | 16-hex-char hash used to avoid duplicate reports |
title | Short title |
body | Full description |
session | Session ID where the issue was observed |
source | Where in the session the issue originated |
confidence | Float 0–1 from the agent |
evidence.0, evidence.1, … | Supporting evidence items (one key per item) |
Reports accumulate with no automatic cleanup. You can list them with the dashboard's feedback UI or read the directory directly:
ls ~/.agent-orchestrator/*-myapp/feedback-reports/Configuration
Environment variables and config overrides
| Setting | Default | How to override |
|---|---|---|
| AO data directory | ~/.agent-orchestrator/ | Set AO_DATA_DIR environment variable |
| Worktree base directory | ~/.worktrees/ | Set worktreeDir under the workspace plugin config in agent-orchestrator.yaml |
| Session prefix | Derived from project ID | Set sessionPrefix in the project config block |
| Log level for observability stderr output | warn | Set AO_LOG_LEVEL to debug, info, warn, or error |
The worktreeDir override uses tilde expansion:
plugins:
workspace:
name: worktree
config:
worktreeDir: ~/code/.worktrees # custom location