Skip to content

markoinla/symphony

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

254 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

Symphony Elixir screenshot

How it works

  1. Polls Linear for candidate work
  2. Creates a workspace per issue
  3. Launches Codex in App Server mode inside the workspace
  4. Sends a workflow prompt to Codex
  5. Refreshes new external Linear comments between turns for active sessions
  6. Keeps Codex working on the issue until the work is done

During app-server sessions, Symphony serves client-side Linear tools:

  • linear_graphql for raw GraphQL access
  • linear_create_comment for supported issue replies
  • linear_update_comment for supported workpad/comment edits

Continuation turns prepend a compact New Linear comments since last turn section when Symphony detects fresh external comments while the issue remains active.

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

  1. Make sure your codebase is set up to work well with agents: see Harness engineering.
  2. Get a new personal token in Linear via Settings → Security & access → Personal API keys, and set it as the LINEAR_API_KEY environment variable.
  3. Copy this directory's WORKFLOW.md to your repo.
  4. Optionally copy the commit, push, pull, land, and linear skills to your repo.
    • The linear skill expects Symphony's linear_graphql app-server tool for raw Linear GraphQL operations such as comment editing or upload flows.
  5. Customize the copied WORKFLOW.md file 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.
  6. Follow the instructions below to install the required runtime dependencies and start the service.

Deploy with Docker

Option A: Install Script (recommended for fresh servers)

One command to set up everything on Ubuntu 22.04/24.04:

curl -fsSL https://raw.githubusercontent.com/markoinla/symphony/main/install.sh | sudo bash

This installs Docker, Node.js, Claude Code CLI, and GitHub CLI, then starts Symphony with Postgres. Workflow files are installed to ~/.symphony/workflows/ where you can edit them — changes are picked up automatically.

After install:

  1. Open http://<your-server-ip>:4000/setup and create your admin account (email + password)
  2. Settings → Connect Linear OAuth
  3. Projects → Create a project (name, GitHub repo, Linear org/project slug)

To add more users:

docker exec symphony-app-1 mix symphony.create_user user@example.com secretpassword --name "Full Name"

Workflows

Default workflow files are installed to ~/.symphony/workflows/:

~/.symphony/workflows/
├── WORKFLOW.md
├── ENRICHMENT.md
├── EPIC_SPLITTER.md
├── MENTION.md
├── REVIEW.md
└── TRIAGE.md

Edit these files to customize agent behavior — Symphony hot-reloads changes automatically. To add a new workflow, drop any .md file with valid YAML front matter into the directory.

Management commands

Update — pulls latest images and restarts services:

sudo bash /opt/symphony/install.sh --update

Reset authentication — removes all user accounts so you can re-run /setup:

sudo bash /opt/symphony/install.sh --reset-password

Uninstall — stops all services and removes data:

sudo bash /opt/symphony/install.sh --uninstall

View logs:

cd /opt/symphony && docker compose -f docker-compose.prod.yml logs -f

Option B: Manual Docker Compose

Prerequisites

On your server, install and authenticate:

  • Docker and Docker Compose
  • Claude CLI — installed and logged in (claude auth login)
  • GitHub CLI — installed and logged in (gh auth login)

Quick start

git clone https://github.com/markoinla/symphony.git
cd symphony
echo "HOST_HOME=$HOME" > .env
docker compose up -d

Open http://localhost:4000:

  1. Visit /setup to create your admin account (email + password)
  2. Settings → Connect Linear OAuth
  3. Projects → Create a project (name, GitHub repo, Linear org/project slug)
  4. Create an issue in Linear — Symphony picks it up

To update: docker compose pull && docker compose up -d

Option C: Deploy with Dokploy

  1. Add GHCR registry in Dokploy → Settings → Registry:
    • URL: ghcr.io, Username: your GitHub username, Password: GitHub PAT with read:packages scope
  2. Create service → Docker Compose → GitHub → markoinla/symphony, branch main, path ./docker-compose.yml
  3. Environment variables: HOST_HOME=/home/youruser
  4. Deploy, then configure Linear OAuth and projects in the dashboard

Pushes to main auto-build the Docker image via GitHub Actions and trigger a Dokploy redeploy.

Development (without Docker)

Prerequisites

We recommend using mise to manage Elixir/Erlang versions.

mise install
mise exec -- elixir --version

Run

git clone https://github.com/markoinla/symphony.git
cd symphony
mise trust
mise install
mise exec -- mix setup
mise exec -- mix build
mise exec -- ./bin/symphony ./WORKFLOW.md

Configuration

Pass one or more workflow file paths to ./bin/symphony when starting the service:

./bin/symphony /path/to/custom/WORKFLOW.md
./bin/symphony /path/to/WORKFLOW.md /path/to/ENRICHMENT.md
./bin/symphony --workflows /path/to/workflows/

If no path is passed, Symphony defaults to ./WORKFLOW.md. When multiple workflow files are provided, Symphony starts one orchestrator per workflow while sharing the same TaskSupervisor, SQLite store, PubSub, and dashboard.

Optional flags:

  • --workflows expands a directory of *.md workflow files (or accepts an explicit workflow file)
  • --logs-root tells Symphony to write logs under a different directory (default: ./log)
  • --port also 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
  filter_by: project
  project_slug: "..."
  picked_up_label_name: "symphony"
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_policy defaults to {"reject":{"sandbox_approval":true,"rules":true,"mcp_elicitations":true}}
    • codex.thread_sandbox defaults to workspace-write
    • codex.turn_sandbox_policy defaults to a workspaceWrite policy rooted at the current issue workspace
  • Supported codex.approval_policy values depend on the targeted Codex app-server version. In the current local Codex schema, string values include untrusted, on-failure, on-request, and never, and object-form reject is also supported.
  • Supported codex.thread_sandbox values: read-only, workspace-write, danger-full-access.
  • When codex.turn_sandbox_policy is set explicitly, Symphony passes the map through to Codex unchanged. Compatibility then depends on the targeted Codex app-server version rather than local Symphony validation.
  • agent.max_turns caps 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_create to bootstrap a fresh workspace. For a Git-backed repo, you can run git clone ... . there, along with any other setup commands you need.
  • If a hook needs mise exec inside a freshly cloned workspace, trust the repo config and fetch the project dependencies in hooks.after_create before invoking mise later from other hooks.
  • tracker.api_key reads from LINEAR_API_KEY when unset or when value is $LINEAR_API_KEY.
  • tracker.filter_by defaults to project. Set tracker.filter_by: label together with tracker.label_name to poll issues by label instead of project slug.
  • tracker.picked_up_label_name optionally adds that Linear label when Symphony successfully picks up an issue and prepares its workspace.
  • For path values, ~ is expanded to the home directory.
  • For env-backed path values, use $VAR. workspace.root resolves $VAR before path handling, while codex.command stays a shell command string and any $VAR expansion there happens in the launched shell.
tracker:
  api_key: $LINEAR_API_KEY
  filter_by: label
  label_name: enrich
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.md is missing or has invalid YAML at startup, Symphony does not boot.
  • If a later reload fails, Symphony keeps running with the last known good workflow for that file and logs the reload error until the file is fixed.
  • server.port or CLI --port enables the optional Phoenix observability service, which serves the React dashboard SPA at / plus the JSON and SSE API under /api/v1/*.
  • The Dashboard URL setting (configurable in the dashboard Settings page) sets the external base URL used in Linear session links, e.g. http://my-server:4000.

Web dashboard

The observability UI now runs as a React SPA on a minimal Phoenix stack:

  • Vite + React serves the dashboard experience from a static bundle under /
  • TanStack Query and SSE drive the live dashboard and session views
  • JSON API endpoints power dashboard, history, projects, settings, and session data under /api/v1/*
  • Bandit serves both the API and the built SPA assets

Development:

mix phx.server
cd dashboard && npm run dev

Production:

mix assets.build
mix release

Project Layout

  • lib/: application code and Mix tasks
  • test/: ExUnit coverage for runtime behavior
  • WORKFLOW.md: in-repo workflow contract used by local runs
  • ../.codex/: repository-local Codex skills and setup helpers

Testing

mix compile --warnings-as-errors && mix format --check-formatted && mix lint

Run the real external end-to-end test only when you want Symphony to create disposable Linear resources and launch a real codex app-server session:

cd elixir
export LINEAR_API_KEY=...
SYMPHONY_RUN_LIVE_E2E=1 mix test test/symphony_elixir/live_e2e_test.exs

Optional environment variables:

  • SYMPHONY_LIVE_LINEAR_TEAM_KEY defaults to SYME2E
  • SYMPHONY_LIVE_SSH_WORKER_HOSTS uses those SSH hosts when set, as a comma-separated list

The E2E test runs two live scenarios:

  • one with a local worker
  • one with SSH workers

If SYMPHONY_LIVE_SSH_WORKER_HOSTS is unset, the SSH scenario uses docker compose to start two disposable SSH workers on localhost:<port>. The live test generates a temporary SSH keypair, mounts the host ~/.codex/auth.json into each worker, verifies that Symphony can talk to them over real SSH, then runs the same orchestration flow against those worker addresses. This keeps the transport representative without depending on long-lived external machines.

Set SYMPHONY_LIVE_SSH_WORKER_HOSTS to target real SSH hosts instead.

The live test creates a temporary Linear project and issue, writes a temporary WORKFLOW.md, runs a real agent turn, verifies the workspace side effect, requires Codex to comment on and close the Linear issue, then marks the project completed so the run remains visible in Linear.

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.


Updated at: 2026-03-26T05:08Z

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors