Skip to content

Design Patterns ​

The reusable patterns orchestra is built on. This is a reference for anyone adopting or extending the system: each section names a pattern, explains how it works today, and states what to do when you build on it.


Single-writer gate ​

Exclusive write access to auto-maintained paths is enforced at the tool-call level by pre_tool_gate.sh, not by convention. Each protected path has one owner:

  • the documenter owns docs/,
  • the memoriser owns CLAUDE.md and memory/,
  • agents/*/memory/ is writable by the memoriser, the builder, and operators; every other agent is blocked.

Script-name matches in the gate use word boundaries so that merely mentioning a script name in a comment or commit message does not trigger a false-positive block on a Bash call.

Extending: when you add a new auto-maintained directory, add an explicit write block for it in the gate rather than relying on discipline. When you add a new agent, give it explicit blocks for each protected path so it inherits controlled — not permissive — defaults.


Session artifacts ​

Every scheduled agent writes a per-session markdown record to agents/<agent>/artifacts/<plan_id>.md, surfaced in the TUI Artifacts tab. The filename is the value of the ORCHESTRA_TASK_ID environment variable, so the convention is identical across agents and gives the operator a unified view of agent activity without reading raw session transcripts.

Two content styles are established:

  • summary style — free prose describing what the run did,
  • structured style — a fixed-format report with stable sections.

Conventions:

  • Create agents/<name>/artifacts/.gitkeep and agents/<name>/sessions/.gitkeep so both directories survive a fresh clone.
  • Commit the artifact alongside the run's other changes, or standalone on a no-op run; use a chore: prefix for artifact-only commits.
  • Document the path, format, and commit rules in the agent's CLAUDE.md and system prompt (.claude/agents/<name>.md).

Memory as system prompt ​

There is no separate "memory file" to keep in sync — the system prompt is the evolved memory. Durable knowledge lives in the prompt files themselves (CLAUDE.md for team-wide constraints, .claude/agents/<name>.md for per-agent knowledge).

Promotion is an interactive operator loop, /memory-promote: the memoriser presents each learning candidate and the human approves or rejects it before it is written into a prompt. This keeps prompt evolution human-gated rather than automatic.


Memory scaffolding bootstrap ​

A new (or retrofitted) agent comes online with working memory infrastructure immediately, before its first memoriser cycle:

  • First-actions checklist — a "First actions" section at the top of the agent's CLAUDE.md that runs on session bootstrap (verify git identity, GH auth, locate affected files).
  • MEMORY.md with @import lines — the agent's MEMORY.md boots with @import directives pointing at its concept files.
  • Stub concept files — CONVENTIONS.md and PITFALLS.md exist as near-empty stubs (a single heading) so the @import lines resolve on the first run. The memoriser fills in content later.

When adding an agent, scaffold this layout even if the content is placeholder — the infrastructure itself is the deliverable.


Agent-to-agent handoff ​

Agents delegate work to other agents through two complementary primitives:

  • In-process — scripts/agent-handoff.sh is the canonical interface for one agent to hand work to another with no human in the loop. Use it when the decision is algorithmic.

    bash
    /opt/orchestra/scripts/agent-handoff.sh <target-agent> <target-repo> '<task-input-json>'

    The script generates a hoff-<uuid-short> task id, sets source='handoff', records provenance (origin_task_id, origin_agent) in source_meta_json, and writes a valid plan_json with a single worker for the target agent. It validates the target agent name, repo, and task-input JSON before touching the database.

  • Human-mediated — a handoff field on a human_task option. 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) and marks the delegating worker done. Use it when the handoff needs judgment: an agent presents findings, the human chooses the path, and the system routes accordingly. The handoff object must contain agent, repo, and task_input; the created plan records handoff_from_agent and handoff_from_task_id for traceability.

Both paths produce the same hoff--prefixed handoff task, so downstream tooling treats them uniformly.


Shared code between services ​

A shared/ directory at the repo root holds TypeScript modules consumed by both the dispatcher and the TUI, wired two ways:

  • build time — TypeScript project references,
  • runtime — Node.js subpath imports (#shared/*).

Single-source-of-truth logic (for example, the agent routing config) lives here rather than being duplicated across services; the deploy script syncs shared/dist/ to the canonical checkout. When you add logic both services need, put it in shared/ instead of copying it.


routing.yaml as single source of truth for scheduled agents ​

The scheduled section of routing.yaml is the canonical configuration for scheduled agents. Each entry carries cron, pipeline, prompt, and repo, consumed by both run-scheduled-agent.sh (which uses the prompt as the agent's task prompt) and the TUI (which parses the YAML to populate its manual-trigger feature). Entries without a prompt (e.g. the GC timer) are intentionally excluded from the trigger UI.

To add a scheduled agent, add its entry to routing.yaml — the TUI picks it up automatically and run-scheduled-agent.sh uses its prompt.

Sync hazard: the agent's task prompt lives in routing.yaml, but the systemd unit's ExecStart is a second source that must agree with it. These two are not automatically reconciled — when you change one, change the other.


Idempotent apply-local and deploy smoke-test parity ​

apply-local.sh is the single deployment script invoked by both the remote push path (deploy.sh) and operators on the VM. It runs after every deploy and is explicitly idempotent: schema migrations, path creation, ACL assertion, and workspace GC all tolerate no-op runs. New deploy steps follow this pattern.

Post-deploy smoke tests run on both the agent deploy path (apply-local-by-agent.sh) and the operator path (apply-local.sh), and are kept at parity — a check added to one is added to the other. Both share a non-fatal-warning convention: each check writes a warning to stderr and a summary count is printed at the end. Current checks include health-endpoint HTTP 200, /metrics JSON validity, systemd-analyze verify, systemctl is-active for the core services, bash -n syntax checks, and a TUI bundle load check. Add new validations to both scripts together.


ccr proxy resilience ​

The ccr (Claude Code Relay) proxy can return a synthetic model error ("Content block is not a text block"). This is treated as a first-class reliability concern, defended with layered retries so a transient proxy fault never fails a task:

  • in-process worker retry — the fastest path; the worker detects the error pattern in its own output and immediately re-spawns with --resume, preserving the caller-supplied task input (including any human answers) on the first attempt,
  • listener dedup-bypass retry — clears the dedup entry for the event and re-spawns,
  • stop-hook retry — on session stop, the same pattern is caught and marked for retry rather than failed,
  • dispatcher resume — a fast-poll sub-interval picks up retry-marked rows as a fallback.

Error-derived values written to the database are sanitized to prevent injection. The takeaway for extenders: treat known-transient upstream faults as retryable at the layer closest to where they occur, and add outer layers as fallback.


Worktree model ​

Agents never run in the canonical checkout. Every agent runs in a per-task git worktree under ${ORCHESTRA_WORKSPACES_DIR}/{plan_id}/, on a branch named agent/{name}/{plan_id}. The canonical /opt/orchestra is read-only to agents.

Because each Bash tool call runs in a fresh shell, an agent should verify its location before its first Write or Edit: git rev-parse --show-toplevel must report a path under ${ORCHESTRA_WORKSPACES_DIR}. If it shows /opt/orchestra, the agent must stop and re-check its working directory rather than write to the canonical repo. All agents land changes to main via gh pr create + gh pr merge --squash; only operators push directly to main, enforced by pre_tool_gate.sh.