How AO turns one task into a durable, observable coding agent.
Agent Orchestrator (AO) is a pluggable TypeScript monorepo for spawning AI coding agents in isolated workspaces, supervising their lifecycle, wiring PR/CI/review feedback loops, and exposing all of it through a Next.js dashboard with live terminal attachment.
packages/**One-screen architecture
The high-level model in CLAUDE.md describes AO as parallel AI coding agents, each in an isolated git worktree with its own PR, supervised from one dashboard CLAUDE.md:3-5. The source verifies that the implementation is centered on a small core, pluggable edges, and flat-file persistence.
flowchart LR
User["Human / Orchestrator"]:::user --> CLI["ao CLI\nstart · spawn · stop · restore"]:::cli
CLI --> Core["@aoagents/ao-core\nConfig · Registry · Session Manager\nLifecycle Manager · Metadata"]:::core
Config["agent-orchestrator.yaml\nZod validated"]:::file --> Core
subgraph Slots["Plugin extension boundary"]
Runtime["Runtime\ntmux / process"]:::runtime
Agent["Agent\nClaude Code / Codex / OpenCode / Aider / Cursor"]:::agent
Workspace["Workspace\nworktree / clone"]:::workspace
Tracker["Tracker\nGitHub / Linear / GitLab"]:::tracker
SCM["SCM\nPR · CI · reviews"]:::scm
Notifier["Notifier\ndesktop / Slack / Discord / webhook"]:::notifier
Terminal["Terminal\niTerm2 / web"]:::terminal
end
Core --> Runtime
Core --> Agent
Core --> Workspace
Core --> Tracker
Core --> SCM
Core --> Notifier
Core --> Terminal
Core --> Lifecycle["Lifecycle\ncore state machine + poller"]:::lifecycle
Lifecycle --> Events["Events + reactions"]:::event
Events --> Notifier
Core --> Store["Flat-file state\n~/.agent-orchestrator"]:::file
Core --> WebAPI["Next.js API routes"]:::web
WebAPI --> Mux["Mux WebSocket\nsession snapshots + terminal streams"]:::web
Mux --> Dashboard["React dashboard\nKanban + xterm.js"]:::web
classDef core fill:#172554,stroke:#60a5fa,color:#eff6ff,stroke-width:2px;
classDef cli fill:#0f172a,stroke:#94a3b8,color:#e2e8f0;
classDef file fill:#1e293b,stroke:#64748b,color:#e2e8f0;
classDef user fill:#312e81,stroke:#a78bfa,color:#f5f3ff;
classDef runtime fill:#164e63,stroke:#22d3ee,color:#ecfeff;
classDef agent fill:#4c1d95,stroke:#a78bfa,color:#faf5ff;
classDef workspace fill:#064e3b,stroke:#34d399,color:#ecfdf5;
classDef tracker fill:#78350f,stroke:#fbbf24,color:#fffbeb;
classDef scm fill:#831843,stroke:#f472b6,color:#fdf2f8;
classDef notifier fill:#7c2d12,stroke:#fb923c,color:#fff7ed;
classDef terminal fill:#1e3a8a,stroke:#60a5fa,color:#eff6ff;
classDef lifecycle fill:#7f1d1d,stroke:#fb7185,color:#fff1f2;
classDef web fill:#0f766e,stroke:#2dd4bf,color:#f0fdfa;
classDef event fill:#3f3f46,stroke:#a1a1aa,color:#fafafa;
Core owns orchestration
session-manager.ts owns spawn/kill/restore/send, while lifecycle-manager.ts owns detection, state transitions, reactions, and notifications packages/core/src/session-manager.ts:1-12 packages/core/src/lifecycle-manager.ts:1-10.
Plugins own replaceable integrations
The public contract is PluginModule<T>: a manifest, a create function, and optional detection. The registry normalizes default/named exports and loads built-ins/configured plugins packages/core/src/types.ts:1728-1753 packages/core/src/plugin-registry.ts:297-312.
Files are the database
Session metadata lives under ~/.agent-orchestrator/projects/{projectId}/sessions/{sessionId}.json; status is computed from canonical lifecycle when read packages/core/src/metadata.ts:1-11 packages/core/src/metadata.ts:159-163.
Repository layout and build topology
The root workspace includes both first-party packages and plugin packages via packages/* and packages/plugins/* pnpm-workspace.yaml:1-3. The root build script runs recursive pnpm builds package.json:12-27; workspace dependencies make the effective topology core → plugins → cli/web, matching the high-level docs CLAUDE.md:11-33.
flowchart TB Root["Root workspace\nNode >=20.18.3 · pnpm 9.15.4"]:::root --> Core["packages/core\ninterfaces · config · lifecycle · metadata"]:::core Root --> Plugins["packages/plugins/*\nagent · runtime · workspace · tracker · scm · notifier · terminal"]:::plugins Root --> CLI["packages/cli\nao command · start/stop · supervisor"]:::app Root --> Web["packages/web\nNext.js 15 dashboard · mux WS · xterm.js"]:::app Root --> AO["packages/ao\nglobal CLI wrapper"]:::app Root --> Tests["packages/integration-tests"]:::test Root --> NotifierMac["packages/notifier-macos"]:::plugins Core --> Plugins Core --> CLI Core --> Web Plugins --> CLI Plugins --> Web Web --> CLI classDef root fill:#0f172a,stroke:#94a3b8,color:#f8fafc; classDef core fill:#172554,stroke:#60a5fa,color:#eff6ff,stroke-width:2px; classDef plugins fill:#312e81,stroke:#a78bfa,color:#f5f3ff; classDef app fill:#064e3b,stroke:#34d399,color:#ecfdf5; classDef test fill:#78350f,stroke:#fbbf24,color:#fffbeb;
| Layer | What lives there | Representative source |
|---|---|---|
| Core | Typed integration contracts, config loader, plugin registry, lifecycle, session manager, path and metadata helpers. | packages/core/src/types.ts:3-18 |
| Plugins | Concrete implementations. For example, tmux exports a runtime manifest/create/default module and Codex exports an agent manifest/create/detect/default module. | packages/plugins/runtime-tmux/src/index.ts:21-26 packages/plugins/agent-codex/src/index.ts:41-47 |
| CLI | Packages the command surface and depends on core, web, and many built-in plugins. | packages/cli/package.json:35-61 |
| Web | Next.js dashboard, API routes, WebSocket mux, xterm.js terminal client; depends on core, selected plugins, Next/React/ws/xterm. | packages/web/package.json:48-70 |
Why this split?
The core stays stable and testable while every environment-specific edge is swappable. New agents, SCMs, notifiers, or terminal UIs can be introduced as packages without changing the state machine or session manager contracts.
The plugin-slot model
AO documents eight architectural slots, but the TypeScript PluginSlot union exposes seven loadable plugin slots; Lifecycle is intentionally core/non-pluggable CLAUDE.md:80-96 packages/core/src/types.ts:1718-1726. The interfaces are all centralized in types.ts, which makes plugin changes explicit and reviewable packages/core/src/types.ts:3-18.
| Slot | Interface lines | Responsibility | Examples / defaults |
|---|---|---|---|
| Runtime | packages/core/src/types.ts:386-424 | Create, attach to, send input to, and destroy the process environment where an agent runs. | tmux, process |
| Agent | packages/core/src/types.ts:477-576 | Define how to detect, launch, prompt, and observe a particular coding tool. | Claude Code, Codex, OpenCode, Aider, Cursor, Grok, Kimi |
| Workspace | packages/core/src/types.ts:650-688 | Create isolated code workspaces and clean/restore them. | worktree, clone |
| Tracker | packages/core/src/types.ts:707-749 | Fetch issue/task context and update external task status. | GitHub, GitLab, Linear |
| SCM | packages/core/src/types.ts:786-900 | Inspect branches, PRs, CI checks, review comments, and mergeability. | GitHub, GitLab |
| Notifier | packages/core/src/types.ts:1160-1181 | Deliver lifecycle and attention notifications to humans. | Desktop, Slack, Discord, webhook, Composio |
| Terminal | packages/core/src/types.ts:1196-1215 | Open or attach a human-facing terminal UI. | iTerm2, web terminal |
| Lifecycle | packages/core/src/lifecycle-manager.ts:1-10 | Core state machine, polling loop, reactions, escalation, cleanup. Not a registry plugin. | Built into @aoagents/ao-core |
Registry and discovery
Built-in packages are enumerated by slot in plugin-registry.ts packages/core/src/plugin-registry.ts:38-72. The registry supports register, get, list, loadBuiltins, and loadFromConfig packages/core/src/plugin-registry.ts:420-623.
Export contract
A plugin module exports manifest, create(config?), and optional detect. The registry also handles default-export modules packages/core/src/types.ts:1728-1753 packages/core/src/plugin-registry.ts:297-312.
// Shape verified in packages/core/src/types.ts:1728-1753
export interface PluginModule<T> {
manifest: PluginManifest;
create(config?: Record<string, unknown>): T | Promise<T>;
detect?(): Promise<boolean>;
}
Canonical state machine + derived legacy status
AO separates durable lifecycle truth from UI/back-compat display status. Canonical lifecycle has eight states and structured reasons packages/core/src/types.ts:29-57; legacy SessionStatus remains a broader display/API vocabulary packages/core/src/types.ts:118-137.
stateDiagram-v2
[*] --> not_started: metadata reserved
not_started --> working: runtime created + agent launched
working --> idle: ready / no activity / waiting on PR
working --> needs_input: agent explicitly waits for human
working --> detecting: runtime or agent signal is uncertain
working --> done: agent reports completion without further action
working --> terminated: fatal reason
idle --> working: new activity / human sends instruction
idle --> detecting: stale runtime suspicion
idle --> terminated: auto_cleanup / pr_merged / manually_killed
needs_input --> working: human response sent
needs_input --> stuck: waiting too long / escalation exhausted
needs_input --> terminated: manually_killed
detecting --> working: probe resolves alive
detecting --> stuck: probe cannot recover
detecting --> terminated: runtime_lost / probe_failure / agent_process_exited
stuck --> working: agent recovers / restore succeeds
stuck --> terminated: manually_killed / error_in_process
done --> [*]
terminated --> [*]
note right of terminated
Terminal reasons include:
manually_killed, pr_merged, auto_cleanup,
runtime_lost, agent_process_exited,
probe_failure, error_in_process
end note
Canonical storage
createInitialCanonicalLifecycle creates new lifecycle objects, while parseCanonicalLifecycle accepts new lifecycle or legacy statePayload data packages/core/src/lifecycle-state.ts:162-193 packages/core/src/lifecycle-state.ts:415-430.
Legacy status is computed
deriveLegacyStatus maps canonical state/reason and PR runtime metadata into UI statuses. Metadata patches deliberately do not persist status; it is computed on read packages/core/src/lifecycle-state.ts:432-488 packages/core/src/lifecycle-state.ts:490-505.
Why canonical vs legacy?
Canonical lifecycle keeps the durable state space compact and reasoned: terminated plus runtime_lost is different from terminated plus pr_merged. The old status vocabulary can still represent dashboard buckets like ci_failed, review_pending, approved, or mergeable by deriving them from canonical lifecycle plus PR state packages/core/src/lifecycle-state.ts:432-488.
Detection and precedence
determineStatus probes runtime, agent activity/process, PR state, fresh self-reports, idle thresholds, and uncertain detecting decisions packages/core/src/lifecycle-manager.ts:900-1397. Fresh agent reports outrank weak inference, but not runtime death, waiting-input, or SCM truth packages/core/src/lifecycle-manager.ts:1299-1328.
Polling and reactions
The manager enriches PR metadata, checks each session, persists transitions, emits events, notifies humans, dispatches CI/review/conflict work, and performs merge cleanup last packages/core/src/lifecycle-manager.ts:530-748 packages/core/src/lifecycle-manager.ts:2438-2765.
Poll
pollAll lists sessions, batch-enriches PRs, checks sessions concurrently, prunes old state, and can stop when all work is complete packages/core/src/lifecycle-manager.ts:2865-3043.
Decide
resolveProbeDecision is part of the path that turns uncertain runtime/activity evidence into a stable lifecycle transition packages/core/src/lifecycle-manager.ts:1202-1214.
React
Reactions can retry, escalate, send instructions to agents, and call notifiers packages/core/src/lifecycle-manager.ts:1399-1624 packages/core/src/lifecycle-manager.ts:2261-2307.
Clean
PR merge cleanup is intentionally deferred while agents are busy and delegates deletion to sessionManager.kill when safe packages/core/src/lifecycle-manager.ts:2309-2436.
Spawn, list, kill, send, restore
The session manager is the imperative coordinator. Its own header summarizes the contract: create workspace, create runtime, launch agent, list metadata plus live runtime state, kill, cleanup, and send packages/core/src/session-manager.ts:1-12.
flowchart LR Task["User task / issue"] --> Resolve["Resolve project + plugins"]:::core Resolve --> Tracker["Fetch issue context\n(optional tracker)"]:::tracker Tracker --> Reserve["Reserve session id\nO_EXCL metadata"]:::file Reserve --> Branch["Compute branch"]:::workspace Branch --> Workspace["workspace.create + hooks"]:::workspace Workspace --> Prompt["buildPrompt\nbase + config + rules + task"]:::prompt Prompt --> AgentConfig["agent.getLaunchConfig"]:::agent AgentConfig --> Runtime["runtime.create\nAO env vars"]:::runtime Runtime --> Metadata["write metadata\ncanonical lifecycle"]:::file Metadata --> Post["postLaunchSetup / initial send"]:::agent classDef core fill:#172554,stroke:#60a5fa,color:#eff6ff; classDef tracker fill:#78350f,stroke:#fbbf24,color:#fffbeb; classDef file fill:#1e293b,stroke:#64748b,color:#e2e8f0; classDef workspace fill:#064e3b,stroke:#34d399,color:#ecfdf5; classDef prompt fill:#312e81,stroke:#a78bfa,color:#f5f3ff; classDef agent fill:#4c1d95,stroke:#a78bfa,color:#faf5ff; classDef runtime fill:#164e63,stroke:#22d3ee,color:#ecfeff;
| Operation | What it does | Source |
|---|---|---|
| Resolve plugins | Locates project-scoped runtime/agent/workspace/tracker/scm/notifier/terminal instances through the registry. | packages/core/src/session-manager.ts:902-918 |
| Spawn | Reserves IDs, creates workspace, builds prompt, configures agent, creates runtime with AO environment, writes metadata, then does post-launch setup. Rollback is guarded by a cleanup stack. | packages/core/src/session-manager.ts:1212-1598 |
| Orchestrator spawn | Uses the same primitives but launches an AO orchestrator session with generated prompt and permissionless settings. | packages/core/src/session-manager.ts:1619-2070 packages/core/src/session-manager.ts:1871-1888 |
| List / get | Reads metadata across projects, enriches runtime state, and computes display status. get fetches one enriched record. | packages/core/src/session-manager.ts:2235-2368 packages/core/src/session-manager.ts:2386-2443 |
| Kill / cleanup | Destroys runtime and workspace, handles idempotency, and writes canonical terminated lifecycle. | packages/core/src/session-manager.ts:2445-2607 packages/core/src/session-manager.ts:2609-2801 |
| Send / restore | Prepares or restores runtime as needed, sends input, and can rebuild workspace/runtime for restorable sessions. | packages/core/src/session-manager.ts:2803-3119 packages/core/src/session-manager.ts:3271-3618 |
| Claim PR | Attaches an existing pull request to a session record. | packages/core/src/session-manager.ts:3121-3244 |
Important invariant: list can detect stale runtimes, but not terminate them
sessionManager.list() may persist a detecting state with runtime_lost when a tmux/process runtime disappears, but comments make the lifecycle manager the “single authority on terminal decisions” packages/core/src/session-manager.ts:2303-2306 packages/core/src/session-manager.ts:2324-2348.
Flat files, no database
AO persists durable state in YAML and JSON files under a project-oriented layout. Current V2 paths are under ~/.agent-orchestrator/projects/{projectId}; older hash-based helpers remain marked deprecated/legacy in paths.ts packages/core/src/metadata.ts:1-11 packages/core/src/paths.ts:1-15 packages/core/src/paths.ts:152-200.
flowchart TB
LocalConfig["agent-orchestrator.yaml\nnearest config found from cwd"]:::yaml
GlobalConfig["~/.agent-orchestrator/config.yaml\nregistered projects + identity"]:::yaml
Home["~/.agent-orchestrator"]:::dir
Home --> GlobalConfig
Home --> Running["running.json\ncurrent ao start PID / port / projects"]:::json
Home --> LastStop["last-stop.json\nsessions stopped by ao stop / Ctrl+C"]:::json
Home --> Projects["projects/{projectId}/"]:::dir
Projects --> Sessions["sessions/{sessionId}.json\nsession metadata + lifecycle"]:::json
Projects --> Orchestrator["orchestrator.json\norchestrator session metadata"]:::json
Projects --> Worktrees["worktrees/{sessionId}/\nagent workspaces"]:::dir
Projects --> Feedback["feedback / code-reviews"]:::dir
LocalConfig --> Loader["loadConfig()\nYAML + Zod + defaults"]:::core
GlobalConfig --> Loader
classDef yaml fill:#312e81,stroke:#a78bfa,color:#f5f3ff;
classDef json fill:#1e293b,stroke:#94a3b8,color:#e2e8f0;
classDef dir fill:#064e3b,stroke:#34d399,color:#ecfdf5;
classDef core fill:#172554,stroke:#60a5fa,color:#eff6ff;
Config
config.ts loads agent-orchestrator.yaml, validates with Zod, applies defaults, and expands paths packages/core/src/config.ts:1-11 packages/core/src/config.ts:247-381 packages/core/src/config.ts:927-959 packages/core/src/config.ts:996-1018. Resolution checks AO_CONFIG_PATH, cwd parents, global config, and legacy locations packages/core/src/config.ts:747-822.
Global project registry
The global config path can come from AO_GLOBAL_CONFIG, XDG, or ~/.agent-orchestrator/config.yaml packages/core/src/global-config.ts:75-99. It is loaded/saved atomically and merges global identity with local project behavior packages/core/src/global-config.ts:341-384 packages/core/src/global-config.ts:857-886.
Metadata
metadataPath, readMetadata, atomic writes, locked mutation, list, and O_EXCL ID reservation provide file-backed session state packages/core/src/metadata.ts:137-146 packages/core/src/metadata.ts:280-325 packages/core/src/metadata.ts:331-459 packages/core/src/metadata.ts:508-546.
CLI process state
running.json tracks the active ao start process and dashboard port; last-stop.json stores sessions killed by stop/Ctrl+C so startup can offer restore packages/cli/src/lib/running-state.ts:16-29 packages/cli/src/lib/running-state.ts:171-205 packages/cli/src/lib/running-state.ts:311-353.
Why flat files?
AO is a local developer tool that needs transparent, inspectable, crash-resilient state rather than a server database. Atomic JSON writes and file locks keep session metadata durable while keeping setup to “install packages and run ao start.”
From ao start to dashboard render
The user-facing flow begins in the CLI, starts the dashboard and project supervisors, ensures an orchestrator session, then uses core services and web API routes to render session state.
sequenceDiagram autonumber actor Human participant CLI as ao CLI participant Config as Config loader participant Registry as Plugin registry participant SM as SessionManager participant LM as LifecycleManager participant Store as Flat files participant API as Next.js API participant Mux as mux WebSocket participant UI as React dashboard Human->>CLI: ao start CLI->>Config: load agent-orchestrator.yaml + global config Config->>Registry: create registry + load/register plugins CLI->>SM: createSessionManager(config, registry) CLI->>API: start dashboard server CLI->>SM: ensure orchestrator session SM->>Store: write orchestrator/session metadata CLI->>LM: start per-project lifecycle worker LM->>SM: list sessions SM->>Store: read metadata + enrich runtime state LM->>Registry: query SCM / agent / runtime plugins LM->>Store: persist lifecycle transitions + events UI->>API: GET /api/sessions initial snapshot API->>SM: list/listCached sessions API->>Store: read supplemental metadata API-->>UI: sessions + stats + orchestrators UI->>Mux: open WebSocket Mux->>API: poll /api/sessions/patches every 3s Mux-->>UI: session snapshot / terminal messages UI-->>Human: Kanban, detail, xterm terminal
CLI startup
runStartup starts the dashboard and resolves the port, ensures an orchestrator session, starts the project supervisor, restores last-stop sessions if requested, then registers running.json with PID, config path, port, and projects packages/cli/src/commands/start.ts:900-949 packages/cli/src/commands/start.ts:953-1019 packages/cli/src/commands/start.ts:1021-1160 packages/cli/src/commands/start.ts:1746-1757.
Lifecycle workers
The project supervisor reconciles lifecycle workers based on non-terminal sessions and runs a 60-second supervisor loop. Each worker creates a lifecycle manager and starts it with the default 30-second interval packages/cli/src/lib/project-supervisor.ts:118-240 packages/cli/src/lib/lifecycle-service.ts:30-75.
Web services
Web uses a singleton service module to load dashboard config, create a registry, register static plugins, and create session/lifecycle managers. Comments clarify that CLI polling is authoritative and web lifecycle is mainly for webhook checks packages/web/src/lib/services.ts:1-13 packages/web/src/lib/services.ts:104-133.
API and live transport
/api/sessions lists, filters, serializes, and enriches sessions packages/web/src/app/api/sessions/route.ts:72-201. /api/sessions/patches returns lightweight dashboard patches from cached sessions packages/web/src/app/api/sessions/patches/route.ts:8-44.
Verified current-source note about SSE
Older high-level docs mention an SSE stream. In the current source inspected for this document, live dashboard updates are implemented through the mux WebSocket: the server polls /api/sessions/patches every 3 seconds and broadcasts snapshots, while the client merges mux updates and uses full refreshes for membership/staleness. No current text/event-stream/EventSource route was found in the web package during this audit packages/web/server/mux-websocket.ts:1-7 packages/web/server/mux-websocket.ts:77-198 packages/web/src/hooks/useSessionEvents.ts:148-340.
Dashboard, mux WebSocket, and xterm.js
The dashboard is a Next.js 15 app with React 19 and xterm.js. It reads session snapshots from API routes, subscribes to mux WebSocket messages, and renders project-scoped Kanban lanes while the sidebar can include all projects.
Session data
Dashboard.tsx calls useSessionEvents and intentionally does not filter session events by project at the hook boundary; it filters the Kanban client-side and groups sessions by attention level packages/web/src/components/Dashboard.tsx:146-179 packages/web/src/components/Dashboard.tsx:287-300.
Mux provider
MuxProvider builds the WebSocket URL, reconnects, re-opens terminal streams, subscribes to session and notification channels, and handles terminal/session/notification messages packages/web/src/providers/MuxProvider.tsx:84-237.
Terminal setup
DirectTerminal delegates xterm setup to useXtermTerminal, which dynamically imports xterm/addons, opens a mux terminal, sends input/resizes, and re-sends dimensions after reconnect packages/web/src/components/DirectTerminal.tsx:35-72 packages/web/src/components/terminal/useXtermTerminal.ts:80-115 packages/web/src/components/terminal/useXtermTerminal.ts:278-390.
HTTP refresh path
The session API routes remain the canonical refresh/snapshot path. The patch endpoint is intentionally lightweight and is polled by the mux broadcaster rather than every browser tab polling independently packages/web/src/app/api/sessions/route.ts:72-201 packages/web/server/mux-websocket.ts:170-198.
Prompt assembly and orchestrator prompt generation
Agent prompts are intentionally layered so AO can enforce operational behavior while still letting project configuration and repository rules shape local coding style.
flowchart LR Base["Layer 1: Base AO agent prompt\nmanaged session lifecycle + PR workflow"]:::base Config["Layer 2: Config-derived context\nproject name, repo, agent behavior, defaults"]:::config Rules["Layer 3: User rules\nagentRules + agentRulesFile"]:::rules Task["Task / issue prompt"]:::task Agent["Coding agent launch config"]:::agent Base --> Config --> Rules --> Task --> Agent OrchTemplate["orchestrator.md template"]:::base --> OrchData["project + dashboard + reactions + rules"]:::config --> OrchPrompt["Orchestrator session prompt"]:::agent classDef base fill:#172554,stroke:#60a5fa,color:#eff6ff; classDef config fill:#064e3b,stroke:#34d399,color:#ecfdf5; classDef rules fill:#78350f,stroke:#fbbf24,color:#fffbeb; classDef task fill:#312e81,stroke:#a78bfa,color:#f5f3ff; classDef agent fill:#4c1d95,stroke:#a78bfa,color:#faf5ff;
Agent prompt builder
prompt-builder.ts documents the three layers, defines the base agent prompt, gathers config-derived context, reads user rules from config/files, then builds the final prompt with optional orchestrator back-channel instructions and task content packages/core/src/prompt-builder.ts:1-11 packages/core/src/prompt-builder.ts:21-57 packages/core/src/prompt-builder.ts:111-182 packages/core/src/prompt-builder.ts:188-237.
Orchestrator prompt
orchestrator-prompt.ts renders the bundled orchestrator.md template with project path/name/repo/default branch, session prefix, dashboard port, reaction descriptions, and orchestrator rules packages/core/src/orchestrator-prompt.ts:1-8 packages/core/src/orchestrator-prompt.ts:33-68 packages/core/src/orchestrator-prompt.ts:144-199.
Why layers?
Base behavior protects AO invariants such as PR creation, status reporting, and lifecycle signals. Config and repo rules then adapt that behavior to a project without forking agent plugins or the session manager.
Cross-platform abstractions
AO treats macOS, Linux, and Windows as first-class. The project’s golden rule is to avoid ad-hoc process.platform === "win32" checks and centralize branching in helpers from @aoagents/ao-core docs/CROSS_PLATFORM.md:9-19.
Central helper module
platform.ts defines isWindows, isMac, isLinux, chooses default runtime (process on Windows, tmux elsewhere), resolves shells, kills process trees, finds PIDs by port, and normalizes env defaults packages/core/src/platform.ts:8-29 packages/core/src/platform.ts:44-62 packages/core/src/platform.ts:88-143 packages/core/src/platform.ts:154-245.
Deep compatibility guide
docs/CROSS_PLATFORM.md inventories helpers and calls out Windows gotchas such as PowerShell vs bash, PATH wrappers, process probing, localhost binding, and agent-plugin specifics docs/CROSS_PLATFORM.md:43-68 docs/CROSS_PLATFORM.md:172-181.
Why centralize OS branching?
Platform checks are otherwise hard to mock and easy to regress. A single helper layer lets tests simulate Windows/macOS/Linux behavior and keeps plugin/runtime code from silently drifting into POSIX-only assumptions.
Key files for future readers
If you are new to AO, read these files in order. The list is intentionally biased toward source of truth over high-level docs.
Orientation: CLAUDE.md:3-33 skills/agent-orchestrator/SKILL.md:7-27
Workspace/package shape: pnpm-workspace.yaml:1-3 package.json:7-27 packages/cli/package.json:35-61 packages/web/package.json:48-70
Core interfaces and plugins: packages/core/src/types.ts:3-18 packages/core/src/types.ts:1718-1753 packages/core/src/plugin-registry.ts:38-72 packages/core/src/plugin-registry.ts:420-623
Lifecycle: packages/core/src/lifecycle-state.ts:45-127 packages/core/src/lifecycle-state.ts:432-505 packages/core/src/lifecycle-manager.ts:900-1397 packages/core/src/lifecycle-manager.ts:2438-3043
Session orchestration: packages/core/src/session-manager.ts:1212-1598 packages/core/src/session-manager.ts:2235-2368 packages/core/src/session-manager.ts:2445-2801 packages/core/src/session-manager.ts:3271-3618
Storage/config: packages/core/src/metadata.ts:1-11 packages/core/src/paths.ts:112-145 packages/core/src/config.ts:247-381 packages/core/src/global-config.ts:75-99 packages/cli/src/lib/running-state.ts:16-29
CLI + web flow: packages/cli/src/commands/start.ts:900-1019 packages/cli/src/lib/project-supervisor.ts:118-240 packages/web/src/lib/services.ts:104-133 packages/web/server/mux-websocket.ts:77-198 packages/web/src/hooks/useSessionEvents.ts:148-340
Prompt/platform: packages/core/src/prompt-builder.ts:1-237 packages/core/src/orchestrator-prompt.ts:1-199 packages/core/src/platform.ts:8-245 docs/CROSS_PLATFORM.md:9-68