# OpenSymphony
OpenSymphony is a Rust implementation of the [OpenAI Symphony](https://github.com/openai/symphony) specification for orchestrating AI coding agents. It connects to [Linear](https://linear.app) for issue tracking and uses [OpenHands](https://github.com/OpenHands/OpenHands) as the agent runtime.

## 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 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
- **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](docs/migration-1.0.0.md) 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)
- Python 3.12+ with `uv` for OpenHands server
- Linear API key (for tracker integration)
- LLM API key (any LiteLLM-compatible provider: OpenAI, Anthropic, Fireworks, etc.)
For platform-specific Rust and Python/`uv` setup steps, see [Prerequisites](docs/prerequisites.md).
### Installation
```bash
cargo install opensymphony
```
OpenSymphony manages a local OpenHands agent-server; install the pinned runtime like this:
```bash
opensymphony install openhands
```
To refresh the installed CLI later, run:
```bash
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.
### Required Environment
Before running `opensymphony run`, add the required tracker and OpenHands secret
values to your shell startup file, such as `~/.zshrc` or `~/.bashrc`:
```bash
export LINEAR_API_KEY="lin_api_..."
export OH_SECRET_KEY='any-random-key'
```
Use your real Linear API key for `LINEAR_API_KEY`. `OH_SECRET_KEY` can be any
random secret string for the local OpenHands runtime.
Also export your provider settings for the OpenHands conversation agent:
```bash
export LLM_MODEL="openai/accounts/fireworks/models/glm-5p1"
export LLM_API_KEY="fw-..."
export LLM_BASE_URL="https://api.fireworks.ai/inference/v1"
```
These `LLM_*` variables are required unless your target repo's `WORKFLOW.md`
has been customized to resolve the LLM configuration some other way.
### Bootstrap A Target Repo
Bootstrap the target repository in place:
```bash
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](https://github.com/OpenHands/extensions/tree/main/plugins/pr-review), 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.
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:
```bash
cd /path/to/target-repo
opensymphony run
```
For real-time monitoring while the orchestrator is running, run the TUI in a separate terminal window:
```bash
opensymphony tui
```
### Further Details
For generated files, environment variables, `config.yaml`, and the template
repo details behind `init`, see [Configuration](docs/configuration.md).
For alternate config paths, `debug`, `rehydrate`, packaging, and local operator
workflows, see [Operations](docs/operations.md).
Optional troubleshooting and validation:
```bash
cd /path/to/target-repo
opensymphony doctor
```
To inspect the command surface, run:
```bash
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.

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:
```bash
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](docs/memory.md) 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 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.
## Architecture
```mermaid
flowchart TB
operator["Operator / CLI / TUI"]
subgraph daemon["OpenSymphony Daemon"]
direction TB
orchestrator["Orchestrator Scheduler"]
workspace["Workspace Manager"]
control["Read-only Control Plane API<br/>GET /healthz, /api/v1/snapshot, /api/v1/events"]
runtime["OpenHands Runtime Client<br/>(REST + WebSocket)"]
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["OpenHands Agent Runtime"]
graphql["GraphQL Helper + Query Assets"]
agent --> issue_ws
agent --> graphql
end
linear["Linear"]
openhands["OpenHands Agent-Server"]
operator --> control
workspace --> issue_ws
runtime --> openhands
openhands --> 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:
| `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
```yaml
openhands:
transport:
base_url: http://127.0.0.1:8000
```
### External Local Mode
For debugging or CI with a manually managed server:
```yaml
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:
```yaml
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](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 OpenHands conversation that OpenSymphony used for that issue:
```bash
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 OpenHands 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 starts a local
server for the session. For the most predictable behavior, prefer the
orchestrator-managed server and avoid leaving unrelated standalone `openhands`
CLI sessions bound to the same port.
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
```bash
# Unit tests
cargo test
# Static validation
opensymphony doctor
# Live tests (requires 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
- [Architecture](docs/architecture.md) - High-level design and component interactions
- [Configuration](docs/configuration.md) - Target repo bootstrap and runtime config
- [Deployment Modes](docs/deployment-modes.md) - Local vs hosted deployment
- [Operations](docs/operations.md) - Doctor, rehydration, diagnostics, and local ops
- [Testing](docs/testing-and-operations.md) - Test strategy and validation layers
- [Migration Guide](docs/migration-1.0.0.md) - Breaking changes and upgrade steps for 1.0.0
- [AGENTS.md](AGENTS.md) - Repository guidelines for coding agents
- [Development Guide](docs/DEVELOPMENT.md) - Contributing and development details
## 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.14.0`
- `openhands-sdk==1.14.0`
- Rust stable toolchain
The managed local OpenHands bundle is sourced from `tools/openhands-server/`
and provisioned with `opensymphony install openhands`.
## License
[LICENSE](LICENSE)
## Acknowledgments
- [OpenAI Symphony](https://github.com/openai/symphony) - The specification this implements
- [OpenHands](https://github.com/OpenHands/OpenHands) - The agent runtime
- [FrankenTUI](https://github.com/Dicklesworthstone/frankentui) - Terminal UI framework