AO

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:

RuleConditionExample
Use as-isProject ID is 4 characters or fewerapiapi
CamelCase initialsProject ID has more than one uppercase letterMyAppma
Kebab/snake initialsProject ID contains - or _my-servicems, my_appma
First 3 charactersSingle lowercase word longer than 4 charsintegratorint

Additional examples: myappmya, agent-orchestratorao, PyTorchpt.

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-1

All keys defined in the metadata schema:

KeyTypeDescription
worktreestringAbsolute path to the git worktree
branchstringGit branch name (e.g., feat/ISSUE-42)
statusstringSession lifecycle state (e.g., working, pr_open, merged)
tmuxNamestringGlobally unique tmux session name including hash
issuestringTracker issue ID (e.g., INT-1234)
prstringPR URL set when the agent creates a pull request
prAutoDetect"on" | "off"Whether automatic PR detection is enabled
summarystringShort summary of what the agent is working on
projectstringProject ID from config
agentstringAgent plugin name (e.g., claude-code, codex)
createdAtISO timestampWhen the session was spawned
runtimeHandlestringRuntime-specific handle (tmux session name)
restoredAtISO timestampSet when the session is restored from archive
rolestringSession role (worker, orchestrator)
dashboardPortnumberDashboard HTTP port (set at spawn time)
terminalWsPortnumberTerminal WebSocket port
directTerminalWsPortnumberDirect terminal WebSocket port
opencodeSessionIdstringOpenCode-internal session ID (OpenCode agent only)
pinnedSummarystringUser-pinned summary that persists across state changes
userPromptstringOriginal 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-1

Archive 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}, tracking total, success, failure, and the timestamps of the last operation and last failure. Tracked metrics include spawn, 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 status ok, warn, or error, 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 writes pr={url} and status=pr_open to the session metadata file.
  • gh pr merge — writes status=merged to the session metadata file.

All other gh subcommands are passed through transparently via exec.

~/.ao/bin/git intercepts branch operations:

  • git checkout -b {branch} and git switch -c {branch} — writes branch={name} to metadata.
  • git checkout {branch} and git switch {branch} — writes branch={name} only when the branch name looks like a feature branch (contains / or -), avoiding noise from checkouts of plain branches like main.

The wrappers rely on two environment variables set by AO for each spawned agent process:

VariableValuePurpose
AO_SESSIONsession ID (e.g., svc-1)Identifies which metadata file to update
AO_DATA_DIRsessions directory pathAbsolute 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 directly

AO 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:

FieldDescription
versionSchema version (currently 1)
idReport ID matching the filename
toolEither bug_report or improvement_suggestion
createdAtISO timestamp
dedupeKey16-hex-char hash used to avoid duplicate reports
titleShort title
bodyFull description
sessionSession ID where the issue was observed
sourceWhere in the session the issue originated
confidenceFloat 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
SettingDefaultHow 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 prefixDerived from project IDSet sessionPrefix in the project config block
Log level for observability stderr outputwarnSet 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

Next steps