opensymphony 2.4.0

A Rust implementation of the OpenAI Symphony orchestration design
Documentation

OpenSymphony

OpenSymphony is a Rust implementation of the OpenAI Symphony specification for orchestrating AI coding agents. It connects to Linear for issue tracking and can run issues through either the managed OpenHands agent-server or the local Codex app-server harness.

OpenSymphony desktop app showing the task graph, run detail, changed files, and diff inspector

What is OpenSymphony?

OpenSymphony automates software development workflows by:

  1. Polling Linear for issues in active states (Todo, In Progress, etc.)
  2. Creating isolated workspaces for each issue with lifecycle hooks
  3. Dispatching AI agents via OpenHands or Codex to work on issues autonomously
  4. Managing retries, reconciliation, and cleanup based on issue state changes
  5. Providing a terminal UI (FrankenTUI) for monitoring and operator control

Key Features

  • Hierarchy-aware scheduling: Parent issues wait for sub-issues to complete
  • WebSocket-first runtime: Real-time agent updates with REST reconciliation
  • Per-issue workspaces: Deterministic, isolated directories with lifecycle hooks
  • GraphQL-only Linear integration: Agent-side Linear reads and writes through checked-in helper/query assets
  • Conversation reuse policies: Default per-issue reuse with optional fresh-per-run resets
  • Harness selection: Default OpenHands agent-server execution, plus local Codex app-server support for ChatGPT subscription-backed runs
  • Local-first MVP: Trusted-machine deployment with optional hosted mode

OpenSymphony 1.0.0 is the compatibility boundary for the GraphQL-only Linear rewrite. See Migration Guide if you are upgrading an older setup.

Packaging note: crates.io exposes a single public package, opensymphony. Internally, the repo still keeps clear subsystem boundaries under crates/opensymphony-*, but those directories are now internal module trees, not separately published crates.

Quick Start

Prerequisites

  • Rust toolchain (stable)
  • Linear API key (for tracker integration)
  • For OpenHands: Python 3.13.12 with uv, plus an LLM API key for an OpenAI-compatible/LiteLLM provider
  • For Codex: a Codex CLI with app-server support and a working ChatGPT login

For platform-specific Rust and Python/uv setup steps, see Prerequisites.

Installation

cargo install opensymphony

For OpenHands runs, install the pinned local OpenHands agent-server runtime:

opensymphony install openhands

For Codex runs, install or select a Codex CLI that supports app-server mode:

codex --version
codex app-server --help
codex login status

To refresh the installed CLI later, run:

opensymphony update

When you run opensymphony update from a target-repo root that already has WORKFLOW.md and config.yaml, it also refreshes the template-managed .agents/skills/ tree without rerunning the full init flow.

Common Environment

Before running opensymphony run, add your Linear key to your shell startup file, such as ~/.zshrc or ~/.bashrc:

export LINEAR_API_KEY="lin_api_..."

Use your real Linear API key for LINEAR_API_KEY.

OpenHands Runtime Environment

OpenHands is the default harness. For the managed local OpenHands runtime, also set a local agent-server secret and provider credentials:

export OH_SECRET_KEY='any-random-key'
export LLM_MODEL="openai/accounts/fireworks/models/glm-5p1"
export LLM_API_KEY="fw-..."
export LLM_BASE_URL="https://api.fireworks.ai/inference/v1"

OH_SECRET_KEY can be any random secret string for the local OpenHands runtime. The LLM_* variables are required for API-key OpenHands runs unless your target repo's WORKFLOW.md has been customized to resolve the LLM configuration some other way.

Codex Runtime Environment

For local Codex app-server runs, authenticate the Codex CLI with ChatGPT:

codex login status
codex login --device-auth

If ChatGPT blocks device-code login, enable Security and login -> Enable device code authorization for Codex in ChatGPT settings, then retry the login.

Then select the Codex harness for OpenSymphony:

export OPENSYMPHONY_HARNESS="codex_app_server"
export OPENSYMPHONY_MODEL="gpt-5.5"
export OPENSYMPHONY_MODEL_PROFILE="codex-chatgpt-local-keychain"
export OPENSYMPHONY_CODEX_BIN="$(command -v codex)"

OPENSYMPHONY_CODEX_BIN is optional when codex is already on PATH. In Codex mode, OpenSymphony uses the operator-owned Codex CLI login; it does not need LLM_API_KEY/LLM_BASE_URL for the Codex worker.

The model configuration panel in the alpha web and desktop shells records model strings, API-compatible endpoint metadata, subscription bootstrap metadata, and stored credential references. Desktop profiles persist through the local native settings boundary. The web shell persists profiles when browser or embedding host storage is available, and reports a session-only fallback in the model panel when durable storage is unavailable. Raw API keys and OAuth refresh material remain owned by the selected keychain, OpenHands auth directory, or hosted secret store.

Bootstrap A Target Repo

Bootstrap the target repository in place:

cd /path/to/target-repo
opensymphony init

opensymphony init guides the bootstrap flow, customizes WORKFLOW.md, and can optionally scaffold automated code review via the OpenHands PR Review Plugin, including GitHub setup through gh when it is installed and authorized for the target repo. It also ensures .gitignore ignores local OpenSymphony runtime state. If AGENTS.md already exists during first-time setup, init leaves it alone and writes the starter guidance to AGENTS-example.md for review. It also initializes .opensymphony/memory/memory.yaml, the shared policy and learned structure file required for default-on memory auto-capture. At the end of a successful bootstrap, init prompts whether to commit and push the generated OpenSymphony files so shared skills and, when selected, AI PR Review setup are in the remote repository before story work begins.

For an existing target repo, opensymphony update is the lighter-weight maintenance path: it refreshes changed or new template-owned skill files under .agents/skills/ without touching WORKFLOW.md, AGENTS.md, or the broader bootstrap files. When run from an OpenSymphony target repo, update also initializes or repairs the memory config and .gitignore policy if needed.

Running the Orchestrator

Then start from the target repository:

cd /path/to/target-repo
opensymphony run

For real-time monitoring while the orchestrator is running, run the TUI in a separate terminal window:

opensymphony tui

To launch the alpha desktop shell without adding Tauri or npm dependencies to the normal Cargo install path, use the lazy desktop launcher:

opensymphony app
# or
opensymphony desktop

The launcher verifies a versioned desktop bundle under ~/.opensymphony/desktop/<version>/ before starting it. Early local bundles can be materialized with --bundle-dir <path> or OPENSYMPHONY_DESKTOP_BUNDLE_DIR; the bundle must include opensymphony-desktop-manifest.json with version, platform, arch, relative executable, and sha256 fields.

Further Details

For generated files, environment variables, config.yaml, and the template repo details behind init, see Configuration.

For alternate config paths, debug, rehydrate, packaging, and local operator workflows, see Operations.

Optional troubleshooting and validation:

cd /path/to/target-repo
opensymphony doctor

To inspect the command surface, run:

opensymphony --help

Project Memory

OpenSymphony can preserve completed-issue knowledge as you build. When memory.auto_capture is enabled in config.yaml (the default), opensymphony run captures terminal issue transitions from Linear and matching GitHub PR narrative, writes private memory under .opensymphony/memory/, and syncs stable learned topics into public docs. Repos initialized or updated with this release get the required memory config automatically.

OpenSymphony memory graph

The generated issue capsules are Markdown files, so .opensymphony/memory/ can also be opened as an Obsidian vault. That gives operators a graph view of issue, milestone, and documentation-topic relationships while keeping private capture artifacts out of the public docs.

Manual commands remain available for setup repair, backfill, inspection, and guarded archival:

opensymphony memory init
opensymphony memory capture COE-123
opensymphony memory brief COE-123
opensymphony memory related --area openhands-runtime
opensymphony memory sync-docs --since-last-sync
opensymphony linear archive --issues COE-123

See Project Memory for archive guards, YAML import/backfill, source schema, automation flags, and the distinction between CLI commands and template-managed agent skills.

The memory index uses DuckDB's bundled build by default so local installs do not need a separate DuckDB system package. That choice adds compile time and binary size, but keeps the memory database portable for local-first operator workflows. Repository developers on macOS/Homebrew can use cargo check-system-duckdb, cargo test-system-duckdb, and cargo clippy-system-duckdb to build against a system DuckDB installation with --no-default-features --features duckdb-prebuilt. The expected Homebrew DuckDB version is 1.5.3, pinned after installation. That avoids both bundled source compilation and per-workspace download caches. The portable fallback aliases cargo check-dev, cargo test-dev, and cargo clippy-dev set DUCKDB_DOWNLOAD_LIB=1 for the aliased command so they reuse a downloaded prebuilt libduckdb during iterative development. See Installer and Distribution Strategy.

Architecture

flowchart TB
    operator["Operator / CLI / TUI"]

    subgraph daemon["OpenSymphony Daemon"]
        direction TB
        orchestrator["Orchestrator Scheduler"]
        workspace["Workspace Manager"]
        control["Gateway + Control API<br/>GET /healthz, /api/v1/snapshot, /api/v1/capabilities"]
        runtime["Harness Runtime Client<br/>OpenHands REST/WebSocket or Codex stdio"]
        linear_read["Linear Read Adapter"]

        orchestrator --> workspace
        orchestrator --> runtime
        orchestrator --> linear_read
        orchestrator --> control
    end

    subgraph execution["Execution Environment"]
        direction TB
        issue_ws["Per-issue Workspace"]
        agent["Agent Runtime"]
        graphql["GraphQL Helper + Query Assets"]

        agent --> issue_ws
        agent --> graphql
    end

    linear["Linear"]
    openhands["OpenHands Agent-Server"]
    codex["Codex App-Server"]

    operator --> control
    workspace --> issue_ws
    runtime --> openhands
    runtime --> codex
    openhands --> agent
    codex --> agent
    linear_read -->|read issues| linear
    graphql -->|agent-side writes| linear

Internal Boundaries

OpenSymphony keeps explicit internal subsystem boundaries while shipping as one installable crates.io package:

Internal module tree Responsibility
opensymphony_orchestrator Poll loop, scheduling, retries, state machine
opensymphony_linear GraphQL client for orchestrator-side Linear reads
opensymphony_memory Issue capsules, DuckDB memory index, docs sync, archive eligibility
opensymphony_openhands REST/WebSocket client for agent runtime
opensymphony_workspace Workspace lifecycle, hooks, containment
opensymphony_control Control plane API and snapshot derivation
opensymphony_tui FrankenTUI operator client
opensymphony_cli CLI entrypoints: init, run, debug, memory, linear archive, daemon (demo), tui, doctor, rehydrate

Deployment Modes

Local Supervised Mode (MVP)

The default mode for individual developers:

  • One OpenHands server subprocess managed by the daemon
  • Host filesystem access (process-level isolation)
  • Loopback-only binding
  • No auth by default
openhands:
  transport:
    base_url: http://127.0.0.1:8000

External Local Mode

For debugging or CI with a manually managed server:

openhands:
  transport:
    base_url: http://127.0.0.1:8000
    session_api_key_env: OPENHANDS_API_KEY

Hosted Remote Mode (Future)

For organizational deployment with stronger isolation:

openhands:
  transport:
    base_url: https://agent-server.example.com
    session_api_key_env: OPENHANDS_API_KEY
  websocket:
    auth_mode: header

See docs/deployment-modes.md for full details.

Workspace Lifecycle

Each issue gets a deterministic workspace:

<workspace_root>/<issue_identifier>/
├── .opensymphony/
│   ├── issue.json              # Issue metadata
│   ├── conversation.json       # Conversation registry and launch profile
│   └── openhands/
│       └── create-conversation-request.json
├── .opensymphony.after_create.json  # Hook receipt
├── <repo_files>                # Cloned repository
└── logs/                       # Execution logs

Debugging Sessions

Use opensymphony debug <issue-id> to reopen the harness conversation that OpenSymphony used for that issue:

cd /path/to/target-repo
opensymphony debug COE-284

The command resolves the issue reference to its managed workspace, reads .opensymphony/conversation.json, and resumes the same conversation_id from the original working directory. The conversation registry persists the issue reference, stable harness conversation ID, timestamps, transport details, and the launch profile that created the session so a missing-but-recoverable thread can be rehydrated without losing continuity.

When the workflow uses the local supervised OpenHands server, opensymphony debug targets the same configured base URL as the orchestrator. If a ready server is already listening there, the debug command reuses it; otherwise it waits through the configured startup window before starting a local server for the session. The default managed-local startup window is 180 seconds so agent-server has enough time to import the pinned environment and scan its active persistence store on slower local machines. For the most predictable behavior, prefer the orchestrator-managed server and avoid leaving unrelated standalone openhands CLI sessions bound to the same port. Stop opensymphony run with Ctrl-C so the managed OpenHands process tree can be cleaned up; Ctrl-Z only suspends the orchestrator and can leave the port bound.

Managed local OpenHands conversations are scoped by target repository under <tool_dir>/workspace/conversations/repos/<repo-key>/. The orchestrator starts OpenHands with OH_CONVERSATIONS_PATH pointing at that repo's active/ store, so older archived work is not eagerly loaded during normal runs. Before startup, known terminal issue conversations from existing workspace manifests are moved into archived/, and current Linear candidate issues are moved into active/ from the legacy flat store or archived/. This legacy-store migration is a temporary compatibility shim for earlier OpenSymphony versions and can be removed after existing installs have aged out. Linear archive operations move matching issue conversations into archived/; opensymphony debug <issue-id> searches both stores and starts the managed server against the store that contains the requested conversation.

Lifecycle Hooks

  • after_create: Clone repository, setup environment
  • before_run: Pre-execution checks
  • after_run: Post-execution cleanup
  • before_remove: Final cleanup before workspace deletion

Testing

# Unit tests
cargo test

# Faster iterative development mode with prebuilt libduckdb
cargo test-dev

# Static validation
opensymphony doctor

# Live tests for OpenHands server
OPENSYMPHONY_LIVE_OPENHANDS=1 cargo test --test live_local_suite -- --ignored --nocapture --test-threads=1

# Smoke test
./scripts/smoke_local.sh

# Live E2E test
OPENSYMPHONY_LIVE_OPENHANDS=1 ./scripts/live_e2e.sh

Documentation

Safety and Security

Local Mode: The MVP runs with process-level isolation on trusted developer machines. Agent code executes on the host filesystem. This is suitable for:

  • Solo development on trusted repositories
  • Local experimentation
  • CI on controlled runners

Hosted Mode (future): Will provide stronger isolation with container-backed workspaces and mandatory auth.

Version Pinning

OpenSymphony pins exact versions for reproducibility:

  • openhands-agent-server==1.24.0
  • openhands-sdk==1.24.0
  • Python 3.13.12
  • Rust stable toolchain

The managed local OpenHands bundle is sourced from tools/openhands-server/ and provisioned with opensymphony install openhands.

License

LICENSE

Acknowledgments