opensymphony 1.4.0

A Rust implementation of the OpenAI Symphony orchestration design
Documentation
# 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.

![OpenSymphony TUI showing issue state, recent events, workspace details, and conversation activity](docs/images/opensymphony-diffs.png)

## 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` contains `.opensymphony*` so local OpenSymphony state stays out of version control.

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.

### 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).

### Project Memory

OpenSymphony can preserve completed-issue knowledge as you build. Live memory
capture reads Linear evidence and discovers matching GitHub PRs by default,
writes private issue capsules under `.opensymphony/memory/`, indexes them in
DuckDB, and can sync selected knowledge into public topic docs:

```bash
opensymphony memory capture COE-123 --dry-run
opensymphony memory capture COE-123
opensymphony memory brief COE-123
opensymphony memory related --area openhands-runtime
opensymphony memory sync-docs --issues COE-123 --dry-run
opensymphony linear archive --issues COE-123 --dry-run
```

See [Project Memory](docs/memory.md) for archive guards, YAML import/backfill,
source schema, 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.

Optional troubleshooting and validation:

```bash
cd /path/to/target-repo
opensymphony doctor
```

To inspect the command surface, run:
```
opensymphony --help
```

## 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:

| 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

```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.

### 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