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.
What It Is
Symphony ships two things: a language-agnostic spec and a prototype-grade reference implementation.
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.
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.
The Mental Model
Symphony only reads the tracker and runs agents. The coding agent does everything else — moving tickets, writing comments, opening PRs.
Linear Board
Issues in active states
Orchestrator
Scheduler + dispatcher
Workspace
Isolated env + Codex
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.
How a Run Actually Flows
Eight steps from poll to reconcile. Each one is a discrete phase in the orchestrator's tick cycle.
-
1
Poll
Query Linear on a fixed cadence (default 5s) for candidate issues in active states.
-
2
Claim
Lock an eligible issue (concurrency-bounded) to prevent duplicate dispatch across restarts.
-
3
Create Workspace
Provision at
<workspace.root>/<issue-id>and run theafter_createhook (typicallygit clone). -
4
Build Prompt
Render the
WORKFLOW.mdtemplate with issue variables ({{ issue.title }},{{ issue.description }}, etc.). -
5
Launch Codex
Start Codex in app-server mode inside the workspace and stream turns back.
-
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
Reconcile
If the issue moves to a terminal state (
Done/Closed/Cancelled/Duplicate), stop the agent and clean up. -
8
Blocked Handling
If Codex needs operator input / approval / secrets, Symphony keeps the issue claimed and surfaces it as blocked (in-memory only).
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
Within Claimed: sub-states include Running and RetryQueued.
Run-Attempt Lifecycle
No database. Restart recovery is tracker-driven + filesystem-driven. In-memory scheduler state (including the blocked map) is not restored on restart.
Architecture at a Glance
Eight components across six layers. Here's what each one does in practice.
WORKFLOW.md → {config, prompt_template}$ENV indirection, preflight validationWORKFLOW.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).
--- 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
500020$VAR)read-only / workspace-write / danger-full-accessnetworkAccess: true for package installsGotchas: 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.
Getting Started
Two paths: build your own from the spec, or run the Elixir reference implementation.
Build Your Own
Hand the spec to your favorite coding agent: "Implement Symphony according to SPEC.md" — and harden the trust/approval posture yourself.
Run the Elixir Reference
Clone, install, build, and run. Requires mise (Elixir 1.19.x / OTP 28), a Linear account, and Codex CLI.
# 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
Confirm Agent-Friendliness
Make sure your codebase is set up for harness engineering — tests, CI, clear boundaries.
-
2
Create Linear API Key
Settings → Security & access → export
LINEAR_API_KEY. -
3
Copy & Configure WORKFLOW.md
Set
project_slug+after_createclone command. Optionally copy thecommit/push/pull/landskills. -
4
Add Custom Linear Statuses
Add
Rework,Human Review,Mergingin 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
Observability
With --port set, you get a minimal Phoenix stack. Without it, structured logs are the baseline surface.
/
LiveView dashboard of active runs — real-time visibility into what's being worked on.
/api/v1/state
Full runtime snapshot as JSON. Query individual issues at /api/v1/<identifier>.
/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/.
Operating Notes & Gotchas
Things that will bite you if you don't know about them upfront.
- Autonomy is real. The reference
codex.commandusesapproval_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 inpath_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.
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.
SPEC.md and harden the trust/approval posture yourselfSPEC.md §1–3 and the WORKFLOW.md body — that's the whole ideaThe 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