Skip to content

Agents ​

orchestra runs a fleet of specialised agents. Each agent has a fixed trigger, a defined write scope, and a set of constraints enforced by hooks. This document describes what each agent is and does.

Roles ​

AgentTriggerWrites toKey constraint
delegatorevery inbound eventmas_tasksexits immediately after writing the dispatch plan; never spawns workers; banned from calling human_task_open.sh — ambiguous events write a failed mas_task instead. Resolves the target repo from agent scope first, falling back to explicit directives, GitHub-sync links, or ticket content.
maintainertwice daily (06:00, 22:00 UTC)orchestra (proposals via human_task)scans the harness and drafts evolutionary proposals; routes each to documenter/memoriser/builder by affected_paths; single-repo — locked to orchestra.
builderdispatcher (approved maintainer proposals)orchestra (PR + squash-merge)applies operator-approved changes to the harness; self-deploys via apply-local-by-agent.sh with health-check auto-rollback; single-repo — locked to orchestra.
documenterdaily (21:00 UTC) + maintainer proposals affecting docs/docs/ in orchestrasole writer to docs/; single-repo — locked to orchestra.
memoriserdaily (20:00 UTC) + maintainer proposals affecting memory//CLAUDE.mdCLAUDE.md and memory/ in orchestraonly agent allowed to modify CLAUDE.md and memory/; single-repo — locked to orchestra.
monitor00:00, 08:00, 16:00 UTCobserves only (no code writes)proactive health checks; silent when healthy, opens human_tasks (optionally with a handoff to builder) when attention is needed; single-repo — locked to orchestra.
reviewerpull_request.review_requested and @-mentionsGitHub PR reviewvalidates PRs; never merges; repo-agnostic across example-* (also reviews builder PRs on the orchestra harness).
auditorweekly (Fri 17:00 UTC)orchestra (PR)infrastructure security scan of the harness repo; opens a PR for findings; single-repo — locked to orchestra.
developer (optional example)Linear/GitHub/Slack events via dispatcherproduct repo PRgeneral feature/bug/refactor agent; repo-agnostic across example-*. See note below.

Delegator ​

The delegator is the router. It runs on every inbound event, decides which agent(s) handle the task, and writes a dispatch plan to mas_tasks — then exits. It never spawns workers and never opens a human_task; if an event is ambiguous it writes a failed mas_task row rather than escalating directly.

The listener pre-creates a placeholder mas_tasks row before spawning the delegator, so the delegator issues an UPDATE rather than an INSERT — every event has a visible task row even if the delegator crashes. The listener also pre-resolves the Linear issue creator via the Linear API before forwarding, so the delegator does no LLM-based extraction of that field.

Repo resolution follows a fixed order: agent scope first (single-repo agents are always dispatched to their locked repo), then explicit directives, then GitHub-sync links, then ticket content. When envelope.task_id is present the delegator uses it exactly, never generating a different id. After writing, it re-reads the row (SELECT after UPDATE) to confirm the write took effect and to repair status if a dispatcher race left the row in an unexpected state.

The delegator handles @orchestra mentions in GitHub comments (issue_comment.created, pull_request_review.submitted, pull_request_review_comment.created), routing to developer or reviewer based on comment content and acknowledging with a 👀 emoji reaction. A Slack app_mention is answered directly for non-codebase questions or routed to developer for action requests.

Maintainer ​

The maintainer is a background agent triggered by orchestra-maintainer.timer twice daily at 06:00 and 22:00 UTC via the dispatcher lifecycle. It performs autonomous roadmap review and proposal generation.

Two-source context:

  • The current codebase as-is.
  • Commits since the last run — an SHA-based window persisted to ${ORCHESTRA_STATE_DIR}/maintainer-last-sha.txt.

Roadmap bootstrapping: If ROADMAP.md is absent, the maintainer creates it and seeds it with an operator-defined autonomy definition and short/long-term priorities via a conditional human_task, adding a concrete "Create ROADMAP.md" action to its first human_task so the operator does not overlook the bootstrap step. Roadmap questions are asked only when bootstrapping or when genuinely blocked on priority ambiguity.

Proposal domains: new agent capabilities, architecture evolution, autonomy maturity tracking, and operator experience.

Routing: proposals are routed by the dispatcher based on affected_paths:

  • docs/ only → documenter
  • CLAUDE.md or paths entirely under agents/ (including memory/) → memoriser
  • everything else (or mixed paths across domains) → builder

Low-risk proposals (docs-only changes, minor memory updates) are auto-approved by the dispatcher without operator review. The TUI Maintenance tab mirrors this routing logic to display the correct target agent for each proposal.

Feedback loop: before drafting new proposals the maintainer examines past-proposal outcomes — builder/deploy logs, human_task resolutions, and commit history — to identify patterns in previously approved changes. It also persists cumulative acceptance-rate metrics to ${ORCHESTRA_STATE_DIR}/maintainer-acceptance.txt, tracking approved/declined counts by target agent (builder, documenter, memoriser) and priority band (high, medium, low), excluding proposals still status='open'. Low acceptance rates for a category act as a calibration signal — a hint to reframe, not a hard gate. The state-file format and update rules are documented in agents/maintainer/memory/ACCEPTANCE_RATE.md.

Session artifacts: the maintainer writes per-run markdown summaries to agents/maintainer/artifacts/<plan_id>.md, committed with the chore: prefix for artifact-only commits and surfaced in the TUI Artifacts tab.

Builder ​

The builder applies operator-approved maintainer proposals to the orchestra repo via PR + squash-merge. It is spawned by the dispatcher for proposals affecting non-documentation paths.

Deploy flow:

  • Builds from the worktree and runs apply-local-by-agent.sh under the deploy lock — this is the only deploy script the builder may run.
  • It cannot run apply-local.sh or deploy.sh directly; those are blocked by pre_tool_gate.sh.
  • Lock acquisition, canonical repo sync, build, and release are collapsed into a single compound command so the EXIT trap does not release the lock prematurely across separate Bash tool invocations.

Merge conflict handling: the builder checks mergeStateStatus before enabling auto-merge, handles BLOCKED/DIRTY/BEHIND states with rebase + force-with-lease on its own branch, and hands off to a human if rebase conflicts cannot be resolved mechanically.

Post-deploy smoke tests: after deploying, the builder verifies the listener health endpoint returns HTTP 200 and confirms the dispatcher process is alive, catching deploy regressions before the session closes.

Health-check verification with auto-rollback: the builder polls the listener health endpoint for up to 30s after deploy. If the listener returns 4xx/5xx, fails to start, or the service enters a failed state, the builder rolls back to the last known-good commit recorded during the deploy sequence.

Sudo access: the builder has an explicit sudo exception for apply-local-by-agent.sh in pre_tool_gate.sh, so the deploy script can restart systemd services despite the defense-in-depth sudo block that applies to all other agents.

Documenter ​

The documenter is the sole writer to docs/. It has two activation modes:

  • Mode 1 (daily routine): triggered by a systemd timer every day at 21:00 UTC via the dispatcher lifecycle. It runs in a per-invocation git worktree under ${ORCHESTRA_WORKSPACES_DIR}/{plan_id}/, branches off origin/main as agent/documenter/{plan_id}, scans non-memoriser commits, updates the docs/ directory, and squash-merges back via PR + gh pr merge --squash. It writes a session artifact to agents/documenter/artifacts/<plan_id>.md summarising what was processed.
  • Mode 2 (maintainer proposal): spawned by the dispatcher when a maintainer proposal affects docs/ only. This is a single-scope, already-approved change — the documenter applies the proposal per its implementation plan, commits, and squash-merges.

Memoriser ​

The memoriser is the sole writer to the orchestra repo's CLAUDE.md and memory/. It uses the same PR + squash-merge flow as the documenter and builder, and follows a read-before-write convention when editing memory files — running Read first to verify current on-disk content before any Edit or Write. It has two activation modes:

  • Mode 1 (daily routine): memory promotion via the /memory-promote interactive operator review loop at 20:00 UTC, unified into the dispatcher lifecycle. It writes a session artifact to agents/memoriser/artifacts/<plan_id>.md summarising detected patterns and generated proposals; if the run produces zero changes, the artifact is still committed standalone.
  • Mode 2 (maintainer proposal): dispatcher-spawned for proposals affecting memory/ or CLAUDE.md only.

Monitor ​

The monitor is a stateless health agent triggered by orchestra-monitor.timer three times daily at 00:00, 08:00, and 16:00 UTC via the dispatcher lifecycle. Every run starts fresh.

Checks: queue depth (pending/failed tasks), worker liveness (running workers vs. PIDs), systemd service health, disk space, and SQLite DB size.

Alerting: silent when healthy — no output, no commits, no PRs. It opens a human_task only when something needs operator attention and never pushes code. Automatable findings (stuck workers, disk reclamation, workspace GC, DB vacuum) include a "Fix automatically" option carrying a handoff definition targeting the builder; the operator picks it to hand the work off.

Anomaly detection:

  • Tracks consecutive skipped scheduled runs and escalates if health checks fail to complete repeatedly, preventing silent degradation.
  • Detects orphaned human_tasks — tasks stuck in status='open' whose corresponding running_workers row is already done or failed.
  • Flags workers stuck in retry_requested for more than 1 hour and detects retry storms (plans with more than 5 total worker rows where at least one is retry_requested); these are non-automatable alerts for the operator.
  • Does not flag waiting_for_human workers as stuck — humans may take hours to answer, which is normal operation.
  • Scrapes the listener /metrics endpoint (latency percentiles p50/p95/p99, error rates by source) with --max-time 5 to avoid hanging on a degraded listener. Delta-based alerting fires when error rate exceeds 5% per source or p95 latency stays above 2000ms; snapshots are persisted to monitor-metrics-prev.json for run-over-run comparison.

Session artifacts: the monitor writes per-run health-check history to agents/monitor/artifacts/<plan_id>.md. The TUI Scheduled panel shows the monitor's health at the top — "Healthy" when there are zero open monitor human_tasks, or a red alert count otherwise — with each timer's last-run status displayed inline.

Reviewer ​

The reviewer validates pull requests against team and repo conventions. It is triggered by pull_request.review_requested and by @orchestra mentions on PRs. It never merges. It is repo-agnostic across example-* and also reviews builder PRs on the orchestra harness itself.

Its CLAUDE.md includes a first-actions checklist for session bootstrap — check for an existing branch, verify GitHub auth, and locate affected files. It writes per-session review summaries to agents/reviewer/artifacts/<plan_id>.md following the shared artifact convention.

Auditor ​

The auditor runs weekly on orchestra-auditor.timer, Fridays at 17:00 UTC via the dispatcher lifecycle. It performs an infrastructure security scan of the harness repo orchestra only and opens a PR for any findings.

Security checklist (from agents/auditor/memory/CONVENTIONS.md): command injection in Bash scripts, hardcoded credentials, unsafe file operations (rm -rf, chmod 777), path traversal in script arguments, sudo usage patterns, and network-exposed service hardening. It uses a dedicated state file (auditor-last-sha.txt) for commit-window tracking. The repo can be overridden via the repo field in the routing.yaml scheduled section.

Session artifacts: the auditor writes per-session summaries to agents/auditor/artifacts/<plan_id>.md recording the commit window examined, any findings, and whether a PR was opened. If no findings were detected, the artifact states that explicitly and is committed standalone on a zero-change run.

Developer (optional example) ​

The developer is an optional example agent shipped as a starting point. It is a general feature/bug/refactor agent for product repos, repo-agnostic across example-*, and triggered by Linear/GitHub/Slack events via the dispatcher. Adopters can remove it, use it as-is, or treat it as a template for their own product-facing agents.

It uses conventional commits, adds no agent-name tags or Claude attribution, and transitions the Linear issue to "In Review" on PR open. Linear comments are summary-only — the PR body is the canonical context; Linear gets a one-line link with the PR headline.

Additional conventions for agent-maintained repos:

  • Never run npx tsc — use the repo's package.json build script instead. npx tsc can pick up stale global config files (e.g. ~/.tsconfig.json) and fail in confusing ways.
  • Use NODE_ENV=development npm ci (or npm install --include=dev) before any npm run build or tsc invocation. When NODE_ENV=production is set, npm ci silently skips devDependencies, so include dev deps before building.
  • Prisma 7 / prisma.config.ts: use NODE_ENV=development when running npx prisma generate (production skips the required TypeScript devDependency); ensure prisma-json-types-generator has TypeScript installed explicitly; and use the full postgresql:// connection string format when setting DATABASE_URL for Prisma-backed test runs.

Routing ​

routing.yaml holds the routing configuration for the delegator. The integrations section contains hint pipelines the delegator uses as context but can override. The agents section is binding: repo_scope defines which repo(s) each agent may work in. The scheduled section carries prompt and repo fields for each entry, serving as the canonical reference for what each scheduled agent runs — the TUI reads these for manual triggers and run-scheduled-agent.sh passes them to the dispatcher. The pipeline: [] entry for the gc timer is intentional: gc is infrastructure-only with no agent dispatched. The delegator MUST NOT dispatch an agent to a repo outside its scope.

Agent repo scopes (binding):

Agentrepo_scopeType
builderorchestrasingle-repo
maintainerorchestrasingle-repo
documenterorchestrasingle-repo
memoriserorchestrasingle-repo
monitororchestrasingle-repo
auditororchestrasingle-repo
reviewerexample-*repo-agnostic (wildcard)
developerexample-*repo-agnostic (wildcard)

The delegator (the router) has no repo_scope entry. It determines the target repo from agent scope first (single-repo agents are always dispatched to their locked repo), then falls back to explicit directives, GitHub-sync links, or ticket content. This prevents harness-internal tasks from being dispatched to the wrong repo when a Linear project's GitHub sync points elsewhere.

Event routing:

EventDefault hintDelegator override cases
Linear issue assigned to botdevelopermulti-repo → one worker per repo
pull_request.review_requestedreviewer—
pull_request.merged_to_main(none; documenter handles via schedule)—
issue_comment.created (@orchestra mention)developer or reviewerroutes to developer for feature/bug requests, reviewer for review requests; acknowledges with 👀 reaction
pull_request_review.submitted (@orchestra mention)developer or reviewerroutes based on review comment content
pull_request_review_comment.created (@orchestra mention)developer or reviewerroutes based on inline comment content
Slack app_mentiondirect answer or developerdirect answer for non-codebase questions, developer for action requests
Maintainer proposal (approved)routed by dispatcher, not delegatordocs/ → documenter, CLAUDE.md or paths under agents/ → memoriser, all others → builder; low-risk proposals auto-approved without operator review
Ambiguous / no clear repo(open human_task, no plan written)—

Human handoff protocol — controlled autonomy ​

The system operates under controlled autonomy: the human operator does all thinking and judgment; the system handles all execution.

  • Decisions belong to humans. Any choice involving trade-offs, preferences, risk assessment, or ambiguity MUST be escalated as a structured question.
  • Execution belongs to agents. Once a human has made a decision, the agent carries it out precisely and completely.

A worker that encounters ambiguity calls human_task_open.sh with a structured JSON payload, then ends its turn.

Payload shape (1–4 questions, each 2–4 options; the 2–4 option constraint is enforced at runtime by human_task_open.sh):

json
{
  "questions": [{
    "question": "...",
    "header": "...",
    "multiSelect": false,
    "options": [{"label": "...", "description": "..."}]
  }]
}

Options can optionally carry a handoff field. When the operator picks a handoff-carrying option, the dispatcher creates a new mas_task for the target agent instead of resuming the delegating agent. The delegating agent's worker is marked done, and the handoff target picks up the work on the next tick:

json
{"label": "Fix automatically", "description": "...",
 "handoff": {"agent": "builder", "repo": "orchestra",
             "task_input": {"summary": "...", "details": "..."}}}

The human_task_open.sh hook validates that the handoff object, if present, contains non-empty agent and repo strings and a task_input object.

The dispatcher detects human_tasks.status = answered and then:

  • If the answer maps to a handoff: creates a new mas_task (source=handoff) for the target agent and marks the delegating agent's worker done.
  • Otherwise: resumes the worker via claude --resume <session_id> with the answer injected as the next user turn.

The answer key used by the dispatcher and the TUI must agree: both use the header field (short label) as the primary answer key, with question (full text) as a fallback for backward compatibility. When writing answer-consumption logic, use the header field as the lookup key.

Human task lifecycle:

  1. Agent opens → status='open' (dispatcher notifies Slack).
  2. Human answers via TUI → status='answered' (TUI writes the answer column).
  3. Dispatcher sees the answered task →
    • Handoff path: if the chosen option carries a handoff definition, the dispatcher creates a new mas_task for the target agent (source=handoff), marks the delegating agent's worker done, and the handoff target picks up on the next tick.
    • Resume path: otherwise the dispatcher resumes the agent with a [dispatcher] Human answered human_task <uuid>: ... message containing the Q&A pairs.
  4. Agent processes the answer and continues toward the original task (resume path only).
  5. On the next tick, the dispatcher marks status='consumed'.

Human task hooks:

HookPathPurpose
Open.claude/hooks/human_task_open.shCreate a structured questionnaire; pipe AskUserQuestion JSON on stdin
Check.claude/hooks/human_task_check.shRead-only lookups: by UUID or by plan id (with optional status filter). Outputs JSONL.

Agents must NOT run ad-hoc sqlite3 queries against $ORCHESTRA_DB to inspect human_task state — the schema may evolve. Use the check script instead. The dispatcher's resume message is the source of truth for the human's answer; agents process it directly rather than independently re-querying the DB.

Slack rendering:

  • Multi-question tasks render as static_select menus plus a Submit button, collecting all answers atomically. Single-question tasks keep per-option buttons.
  • Each select menu includes an "Other… (type below)" option with a free-text input; the listener prefers free-text input over the select choice when both are present.
  • After answering, the original question text is preserved, interactive blocks are removed, and a Q&A summary is shown inline with an "Answered!" status marker.
  • Option descriptions render as markdown bullet points above each select menu rather than in Slack's 75-char-limited description field.
  • The task creator is @-mentioned in a threaded reply so Slack sends a notification (Block Kit context blocks don't trigger pings).
  • For Linear-origin tasks, notifications are sent as direct messages to the issue creator when their Slack user ID is resolved; non-Linear tasks and unresolved lookups fall back to the team channel (SLACK_CHANNEL).

Memory model ​

The memory system separates concerns across layers:

  • System prompts in .claude/agents/{name}.md — loaded on every session, containing the agent's role, constraints, and handoff protocol.
  • Agent-level memory in agents/{name}/memory/ — durable conventions and promoted patterns for a specific agent.
  • Team memory — team-wide patterns promoted by the memoriser.
  • CLAUDE.md at the repo root — team-wide rules, auto-loaded every session.

Key files:

.claude/agents/{name}.md            ← agent system prompt (loaded every session)
agents/{name}/CLAUDE.md             ← per-agent conventions (tracked)
agents/{name}/memory/MEMORY.md      ← per-agent auto-memory (tracked)
agents/{name}/memory/CONVENTIONS.md ← per-agent conventions (tracked)
team/memory/                        ← team-wide promoted patterns (tracked)
CLAUDE.md                           ← team-wide rules, auto-loaded every session

Promotion path: memoriser detects 3+ recurrences of a pattern → runs /memory-promote as an interactive operator review loop → the human approves/rejects each proposal → approved entries are promoted to the appropriate system prompt or memory file.

Each agent's memory/ directory carries the same scaffolding: MEMORY.md with @import lines for CONVENTIONS.md and PITFALLS.md, plus stub concept files for the memoriser to populate.

Branch convention ​

agent/{agent-name}/{plan_id} — e.g. agent/developer/STU-806, agent/documenter/cron-documenter-20260604T210000Z-669281. All agents run in per-invocation git worktrees under ${ORCHESTRA_WORKSPACES_DIR}/{plan_id}/, checked out on a branch named agent/{name}/{plan_id} off origin/main. The canonical checkout is read-only for agents.

Agent onboarding template ​

agents/builder/memory/NEW_AGENT_PATTERN.md is the canonical checklist for adding a new agent to orchestra. It covers:

  1. System prompt (.claude/agents/<name>.md).
  2. Agent directory scaffolding (agents/<name>/).
  3. CLAUDE.md conventions file.
  4. Dispatcher integration — routing.yaml entry, planSchema.ts, TUI agent list.
  5. Session artifacts (scheduled agents only) — write per-run markdown summaries to agents/<name>/artifacts/<plan_id>.md; commit alongside other changes (or standalone on no-op runs) using the chore: prefix for artifact-only commits; two styles exist (summary prose and structured fixed-format report), documented in agents/<name>/CLAUDE.md.
  6. Systemd units + bootstrap-vm.sh integration — new .service and .timer files are auto-discovered by glob-based install -m 0644 systemd/orchestra-*.{service,timer} commands in apply-local.sh and bootstrap-vm.sh; the only manual integration point is the systemctl enable --now list for the new timer.
  7. Hook gate (if the agent writes to protected paths).
  8. TUI integration (typically automatic) and the validation checklist.

Shared agent references ​

docs/agent-handoff-protocol.md is a shared document referenced by multiple agent system prompts (builder, developer, monitor, reviewer) via their .claude/agents/{name}.md files. It contains the canonical controlled-autonomy handoff protocol, the human_task payload shape, the handoff field spec, and the lifecycle stages — extracted from previously duplicated inline descriptions to keep the prompts in sync. The pre_tool_gate.sh hook references this file for docs-gate consistency.

Hooks ​

HookWhenDoes
pre_tool_gate.shPreToolUse: Bash, Write, Editblocks rm -rf /, force-push to main, non-memoriser writes to CLAUDE.md, non-documenter writes to docs/, and non-memoriser/non-builder writes to agents/*/memory/; blocks agents from running apply-local.sh/deploy.sh (builder exception for apply-local-by-agent.sh); only operators may push directly to main; script-name regexes require word boundaries so mentioning a script in comments or commit messages does not trip the block; blocks all sudo usage by agents for defense in depth, with an explicit builder exception for apply-local-by-agent.sh.
on_stop_auto_retry.shStop (agent session ends non-zero)detects ccr proxy synthetic model errors and marks the worker for retry instead of failure; includes SQL sanitization to prevent injection through error-message-derived values; also detects synthetic errors in cumulative (resumed) transcripts via a parallel model='<synthetic>' + isApiErrorMessage=true path, since resumed transcripts omit the result event; matches worker status IN ('failed', 'running') because the hook runs before the dispatcher marks the worker failed.
human_task_open.shon demand (agent hits ambiguity)creates a structured questionnaire from AskUserQuestion JSON piped on stdin; validates per-question option counts (2–4) and any handoff object.
human_task_check.shon demand (agent needs human_task state)read-only lookups against the human_tasks table by UUID or plan id (with optional status filter); outputs JSONL — agents use this instead of ad-hoc sqlite3 queries.