#### Context Replace the custom Elixir observability TCP server with Phoenix while keeping the shipped escript and existing operational API calls working the same way they did before. #### TL;DR *Move the Elixir observability dashboard and API onto Phoenix without breaking the escript or curl workflows.* #### Summary - replace the custom HTTP server with a Phoenix endpoint, router, controller, and shared presenter - add a LiveView operations dashboard with PubSub-driven updates and embed its CSS and JS assets in code - preserve runtime compatibility by accepting form-style POSTs and extending the endpoint coverage around those paths #### Alternatives - keep extending the hand-rolled TCP server, but that keeps custom parsing and routing complexity in the app - serve dashboard assets from priv directories, but the shipped escript cannot rely on those files existing on disk #### Test Plan - [x] `make -C elixir all` - [x] `mix -C elixir test test/symphony_elixir/extensions_test.exs` - [x] `./bin/symphony --i-understand-that-this-will-be-running-without-the-usual-guardrails --port 40123` - [x] `curl -si http://127.0.0.1:40123/dashboard.css` - [x] `curl -si http://127.0.0.1:40123/vendor/phoenix/phoenix.js` - [x] `curl -si -X POST -d '' http://127.0.0.1:40123/api/v1/refresh` - [x] `curl -si -X POST -d '' http://127.0.0.1:40123/api/v1/state` --------- Co-authored-by: Codex <codex@openai.com>
6.8 KiB
Symphony Elixir
This directory contains the current Elixir/OTP implementation of Symphony, based on
SPEC.md at the repository root.
Warning
Symphony Elixir is prototype software intended for evaluation only and is presented as-is. We recommend implementing your own hardened version based on
SPEC.md.
Screenshot
How it works
- 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.
How to use it
- Make sure your codebase is set up to work well with agents: see Harness engineering.
- 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.
Prerequisites
We recommend using mise to manage Elixir/Erlang versions.
mise install
mise exec -- elixir --version
Run
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
Configuration
Pass a custom workflow file path to ./bin/symphony when starting the service:
./bin/symphony /path/to/custom/WORKFLOW.md
If 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 gpt-5.3-codex"
- 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.
Web dashboard
The observability UI now 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
Project Layout
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
Testing
make all
FAQ
Why Elixir?
Elixir 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.
What's the easiest way to set this up for my own codebase?
Launch codex in your repo, give it the URL to the Symphony repo, and ask it to set things up for
you.
License
This project is licensed under the Apache License 2.0.
