Symphony turns project work into isolated, autonomous implementation runs, allowing teams to manage work instead of supervising coding agents.
In this demo video, Symphony monitors a Linear board for work and spawns agents to handle the tasks. The agents complete the tasks and provide proof of work: CI status, PR review feedback, complexity analysis, and walkthrough videos. When accepted, the agents land the PR safely. Engineers do not need to supervise Codex; they can manage the work at a higher level.
Warning
Symphony is a low-key engineering preview for testing in trusted environments.
- Polls Linear for candidate work
- Creates an isolated workspace per issue
- Launches Codex in App Server mode inside the workspace
- Sends a workflow prompt to Codex
- Keeps Codex working on the issue until the work is done
During app-server sessions, Symphony also serves a client-side linear_graphql tool so that repo
skills can make raw Linear GraphQL calls.
If a claimed issue moves to a terminal state (Done, Closed, Cancelled, or Duplicate),
Symphony stops the active agent for that issue and cleans up matching workspaces.
- Make sure your codebase is set up to work well with agents.
- Get a new personal token in Linear via Settings → Security & access → Personal API keys, and
set it as the
LINEAR_API_KEYenvironment variable. - Copy this directory's
WORKFLOW.mdto your repo. - Optionally copy the
commit,push,pull,land, andlinearskills to your repo.- The
linearskill expects Symphony'slinear_graphqlapp-server tool for raw Linear GraphQL operations such as comment editing or upload flows.
- The
- Customize the copied
WORKFLOW.mdfile for your project.- To get your project's slug, right-click the project and copy its URL. The slug is part of the URL.
- When creating a workflow based on this repo, note that it depends on non-standard Linear issue statuses: "Rework", "Human Review", and "Merging". You can customize them in Team Settings → Workflow in Linear.
- Follow the instructions below to install the required runtime dependencies and start the service.
We recommend using mise to manage Elixir/Erlang versions.
mise install
mise exec -- elixir --versiongit clone <repo-url>
cd symphony
mise trust
mise install
mise exec -- mix setup
mise exec -- mix build
mise exec -- ./bin/symphony ./WORKFLOW.mdPass a custom workflow file path to ./bin/symphony when starting the service:
./bin/symphony /path/to/custom/WORKFLOW.mdIf no path is passed, Symphony defaults to ./WORKFLOW.md.
Optional flags:
--logs-roottells Symphony to write logs under a different directory (default:./log)--portalso starts the Phoenix observability service (default: disabled)
The WORKFLOW.md file uses YAML front matter for configuration, plus a Markdown body used as the
Codex session prompt.
Minimal example:
---
tracker:
kind: linear
project_slug: "..."
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 a Linear issue {{ issue.identifier }}.
Title: {{ issue.title }} Body: {{ issue.description }}Notes:
- If a value is missing, defaults are used.
- Safer Codex defaults are used when policy fields are omitted:
codex.approval_policydefaults to{"reject":{"sandbox_approval":true,"rules":true,"mcp_elicitations":true}}codex.thread_sandboxdefaults toworkspace-writecodex.turn_sandbox_policydefaults to aworkspaceWritepolicy rooted at the current issue workspace
- Supported
codex.approval_policyvalues depend on the targeted Codex app-server version. In the current local Codex schema, string values includeuntrusted,on-failure,on-request, andnever, and object-formrejectis also supported. - Supported
codex.thread_sandboxvalues:read-only,workspace-write,danger-full-access. - Supported
codex.turn_sandbox_policy.typevalues:dangerFullAccess,readOnly,externalSandbox,workspaceWrite. agent.max_turnscaps how many back-to-back Codex turns Symphony will run in a single agent invocation when a turn completes normally but the issue is still in an active state. Default:20.- If the Markdown body is blank, Symphony uses a default prompt template that includes the issue identifier, title, and body.
- Use
hooks.after_createto bootstrap a fresh workspace. For a Git-backed repo, you can rungit clone ... .there, along with any other setup commands you need. - If a hook needs
mise execinside a freshly cloned workspace, trust the repo config and fetch the project dependencies inhooks.after_createbefore invokingmiselater from other hooks. tracker.api_keyreads fromLINEAR_API_KEYwhen unset or when value is$LINEAR_API_KEY.- For path values,
~is expanded to the home directory. - For env-backed path values, use
$VAR.workspace.rootresolves$VARbefore path handling, whilecodex.commandstays a shell command string and any$VARexpansion there happens in the launched shell.
tracker:
api_key: $LINEAR_API_KEY
workspace:
root: $SYMPHONY_WORKSPACE_ROOT
hooks:
after_create: |
git clone --depth 1 "$SOURCE_REPO_URL" .
codex:
command: "$CODEX_BIN app-server --model <model>"- If
WORKFLOW.mdis missing or has invalid YAML, startup and scheduling are halted until fixed. server.portor CLI--portenables the optional Phoenix LiveView dashboard and JSON API at/,/api/v1/state,/api/v1/<issue_identifier>, and/api/v1/refresh.- The
SYMPHONY_SERVER_PORTenvironment variable can also set the server port. Priority order: CLI--port> WORKFLOW.mdserver.port>SYMPHONY_SERVER_PORTenv var.
Symphony can target issues by project slug or team key, and optionally filter by label.
tracker.team_key is an alternative to tracker.project_slug. When both are set, team_key takes
priority. Use the team's short key from Linear (e.g. RVR, ENG).
tracker.labels is a comma-separated list of Linear labels. When set, only issues carrying at least
one of the listed labels are dispatched. Label matching is case-insensitive.
tracker:
kind: linear
team_key: RVR
labels: "symphony"By default, tracker.active_states controls both which issues are eligible for new agent dispatch
and which states keep already-running agents alive. If you want agents to stay alive through later
pipeline states (like code review or staging) without picking up new issues in those states, use
tracker.dispatch_states to restrict where new agents are created.
When dispatch_states is set, only issues in those states get new agents. active_states continues
to govern agent lifecycle — running agents won't be stopped when their issue moves to a state in
active_states but not in dispatch_states.
When dispatch_states is not set, it defaults to active_states (the original behavior).
tracker:
dispatch_states: "Todo, In Progress"
active_states: "Todo, In Progress, Code Review, On Staging"In this example, agents are only dispatched to issues in "Todo" or "In Progress". But if an agent is already working on an issue and that issue moves to "Code Review" or "On Staging", the agent keeps running. Once the issue leaves all active states (or hits a terminal state), the agent is stopped.
The observability UI runs on a minimal Phoenix stack:
- LiveView for the dashboard at
/ - JSON API for operational debugging under
/api/v1/* - Bandit as the HTTP server
- Phoenix dependency static assets for the LiveView client bootstrap
lib/: application code and Mix taskstest/: ExUnit coverage for runtime behaviorWORKFLOW.md: in-repo workflow contract used by local runs.codex/: repository-local Codex skills and setup helpers
make allElixir is built on Erlang/BEAM/OTP, which is great for supervising long-running processes. It has an active ecosystem of tools and libraries. It also supports hot code reloading without stopping actively running subagents, which is very useful during development.
Launch codex in your repo, give it the URL to the Symphony repo, and ask it to set things up for
you.
This project is licensed under the Apache License 2.0.

