mo2explains
Engineering Preview

Symphony

A long-running daemon that watches a Linear board, spins up an isolated workspace per issue, and runs a Codex coding agent inside it until the work is done — so your team manages work instead of babysitting agents.

Apache-2.0 ~2,100-line SPEC.md Elixir/OTP Reference github.com/openai/symphony
01

What It Is

Symphony ships two things: a language-agnostic spec and a prototype-grade reference implementation.

The Real Product

SPEC.md

A ~2,100-line, language-agnostic service specification written in RFC-2119 MUST / SHOULD language. You can implement Symphony in any language from it.

Reference Implementation

elixir/

An experimental Elixir/OTP implementation. Prototype-grade, "evaluation only, as-is." Demonstrates the spec, not a production product.

!

Trust posture: Symphony is an engineering preview meant for trusted environments. The reference config runs Codex with approval_policy: never — the agent acts autonomously. Do not point it at untrusted repos or issues.

02

The Mental Model

Symphony only reads the tracker and runs agents. The coding agent does everything else — moving tickets, writing comments, opening PRs.

Symphony Data Flow
Source

Linear Board

Issues in active states

poll
Core

Orchestrator

Scheduler + dispatcher

dispatch
Per Issue

Workspace

Isolated env + Codex

Agent writes state / comments / PR back through the orchestrator

A "successful" run ends at whatever handoff state your workflow defines (e.g. Human Review), not necessarily Done. Symphony manages the lifecycle; the agent manages the work.

03

How a Run Actually Flows

Eight steps from poll to reconcile. Each one is a discrete phase in the orchestrator's tick cycle.

  1. 1

    Poll

    Query Linear on a fixed cadence (default 5s) for candidate issues in active states.

  2. 2

    Claim

    Lock an eligible issue (concurrency-bounded) to prevent duplicate dispatch across restarts.

  3. 3

    Create Workspace

    Provision at <workspace.root>/<issue-id> and run the after_create hook (typically git clone).

  4. 4

    Build Prompt

    Render the WORKFLOW.md template with issue variables ({{ issue.title }}, {{ issue.description }}, etc.).

  5. 5

    Launch Codex

    Start Codex in app-server mode inside the workspace and stream turns back.

  6. 6

    Keep Working

    After each normal turn, re-check issue state. If still active, run another turn on the same thread — up to agent.max_turns.

  7. 7

    Reconcile

    If the issue moves to a terminal state (Done / Closed / Cancelled / Duplicate), stop the agent and clean up.

  8. 8

    Blocked Handling

    If Codex needs operator input / approval / secrets, Symphony keeps the issue claimed and surfaces it as blocked (in-memory only).

04

Claim States & Run Lifecycle

Internal claim states are distinct from Linear states. The run-attempt lifecycle tracks each dispatch from preparation to completion.

Claim States

Unclaimed
Claimed
Released

Within Claimed: sub-states include Running and RetryQueued.

Run-Attempt Lifecycle

PreparingWorkspace
BuildingPrompt
LaunchingAgent
InitializingSession
StreamingTurn
Finishing
Succeeded | Failed | TimedOut | Stalled | CanceledByReconciliation
i

No database. Restart recovery is tracker-driven + filesystem-driven. In-memory scheduler state (including the blocked map) is not restored on restart.

05

Architecture at a Glance

Eight components across six layers. Here's what each one does in practice.

Component
Role
Workflow Loader
Parses WORKFLOW.md{config, prompt_template}
Config Layer
Typed getters, defaults, $ENV indirection, preflight validation
Issue Tracker Client
Fetches candidates / states / terminal issues from Linear; normalizes payloads
Orchestrator
The only thing that mutates scheduling state: poll tick, dispatch, retry, reconcile
Workspace Manager
Maps issue → path, ensures dirs, runs lifecycle hooks, cleans terminal workspaces
Agent Runner
Creates workspace, builds prompt, launches Codex app-server, streams updates
Status Surface
Terminal output or Phoenix LiveView dashboard (optional)
Logging
Structured logs to configured sinks
06

WORKFLOW.md — The One File You Own

This is the heart of using Symphony. Markdown with YAML front matter (runtime config) plus a body (the Codex session prompt, rendered with issue variables).

WORKFLOW.md — minimal config
---
tracker:
  kind: linear
  project_slug: "your-project"
workspace:
  root: ~/code/workspaces
hooks:
  after_create: |
    git clone git@github.com:your-org/your-repo.git .
agent:
  max_concurrent_agents: 10
  max_turns: 20
codex:
  command: codex app-server
---

You are working on Linear issue {{ issue.identifier }}.
Title: {{ issue.title }}
Body: {{ issue.description }}

Key Config Fields

Field
Purpose & Notable Default
tracker.project_slug
Which Linear project to watch
tracker.required_labels
Issue must carry all these labels to run (case/space-insensitive)
tracker.active_states / terminal_states
Which states count as work vs. done
polling.interval_ms
Poll cadence — default 5000
hooks.after_create / before_remove
Bootstrap / teardown a workspace
agent.max_concurrent_agents
Dispatch concurrency cap
agent.max_turns
Back-to-back Codex turns per invocation — default 20
codex.command
How to launch Codex app-server (can inject model/flags/$VAR)
codex.approval_policy
Approval gating — default reject elicitations
codex.thread_sandbox
read-only / workspace-write / danger-full-access
codex.turn_sandbox_policy
Per-turn sandbox; set networkAccess: true for package installs
!

Gotchas: Bad/missing YAML at startup → Symphony won't boot. A bad reload → it keeps running on the last-good workflow and logs the error. Workflows that hit the network (npm/mix/pip) must set networkAccess: true in turn_sandbox_policy, or DNS is denied by the turn sandbox.

The shipped WORKFLOW.md body is not a toy prompt — it's a full operating contract for the agent: a status map, a persistent Codex Workpad comment as source-of-truth, a mandatory PR-feedback sweep, a blocked-access escape hatch, and a strict "no next-steps-for-user, this is unattended" posture.

07

Getting Started

Two paths: build your own from the spec, or run the Elixir reference implementation.

Option 1

Build Your Own

Hand the spec to your favorite coding agent: "Implement Symphony according to SPEC.md" — and harden the trust/approval posture yourself.

Option 2

Run the Elixir Reference

Clone, install, build, and run. Requires mise (Elixir 1.19.x / OTP 28), a Linear account, and Codex CLI.

Terminal — Elixir reference setup
# Prereqs: mise, Linear account, Codex CLI
git clone https://github.com/openai/symphony
cd symphony/elixir
mise trust && mise install
mise exec -- mix setup
mise exec -- mix build
mise exec -- ./bin/symphony ./WORKFLOW.md

Wire Up Linear + Your Repo

  1. 1

    Confirm Agent-Friendliness

    Make sure your codebase is set up for harness engineering — tests, CI, clear boundaries.

  2. 2

    Create Linear API Key

    Settings → Security & access → export LINEAR_API_KEY.

  3. 3

    Copy & Configure WORKFLOW.md

    Set project_slug + after_create clone command. Optionally copy the commit / push / pull / land skills.

  4. 4

    Add Custom Linear Statuses

    Add Rework, Human Review, Merging in Team Settings → Workflow — or edit the workflow to match your board.

Runtime Flags

  • ./bin/symphony /path/to/custom/WORKFLOW.md — custom workflow (defaults to ./WORKFLOW.md)
  • --logs-root <dir> — log directory (default ./log)
  • --port <n> — enables the optional Phoenix dashboard + JSON API
08

Observability

With --port set, you get a minimal Phoenix stack. Without it, structured logs are the baseline surface.

Dashboard

/

LiveView dashboard of active runs — real-time visibility into what's being worked on.

JSON API

/api/v1/state

Full runtime snapshot as JSON. Query individual issues at /api/v1/<identifier>.

Control

/api/v1/refresh

Force a poll cycle — useful when you've just moved issues and don't want to wait.

Without the dashboard, structured logs cover token accounting and humanized event summaries — documented in elixir/docs/.

09

Operating Notes & Gotchas

Things that will bite you if you don't know about them upfront.

  • Autonomy is real. The reference codex.command uses approval_policy: never + workspace-write. That's deliberate for trusted setups — treat it as a loaded tool.
  • Workspace safety is a hard invariant. The agent's turn cwd must never be the source repo; everything stays under workspace.root. The Elixir impl enforces this in path_safety.ex.
  • Restart = amnesia for scheduler state. Blocked issues and in-flight retry timers are in memory only. After a restart, still-active issues become dispatch candidates again.
  • GitHub is not a valid blocker in the shipped workflow — the agent must exhaust fallback auth/remotes before flagging blocked.
  • Terminal cleanup on startup removes stale workspaces for issues already done — expect disk churn.
  • SSH workers are an optional extension (Appendix A): run agents on remote hosts over SSH; the e2e test can spin up disposable Docker SSH workers.
!

Non-standard statuses: The reference workflow depends on Rework, Human Review, and Merging. Add them in Linear Team Settings → Workflow, or edit the workflow to match your board.

10

Should You Use It?

Depends on where you are and what you need. The durable asset is the spec + the workflow contract; the Elixir code is a demonstration.

If you…
Then…
Run a Linear board + have adopted harness engineering + want unattended agent runs in a trusted env
Try the Elixir reference as-is for evaluation
Want production-grade or a different language
Implement from SPEC.md and harden the trust/approval posture yourself
Just want to understand the pattern
Read SPEC.md §1–3 and the WORKFLOW.md body — that's the whole idea

The repo is explicit that this is a preview. The durable asset is the spec + the workflow contract; the Elixir code is a demonstration, not a product.

Quick Reference

  • Spec: SPEC.md — domain model §4, workflow contract §5, config §6, state machine §7, scheduling §8, workspace safety §9, agent protocol §10, Linear integration §11, reference algorithms §16, checklist §18
  • Reference impl: elixir/orchestrator.ex, agent_runner.ex, workspace.ex, config.ex, linear/, codex/
  • Workflow: elixir/WORKFLOW.md
  • Skills: .codex/skills/{commit, push, pull, land, linear, debug}
  • Test gate: make all · live e2e: make e2e
  • Demo: Vimeo