opensymphony 2.9.5

A Rust implementation of the OpenAI Symphony orchestration design
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
# 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 can run issues through either the managed [OpenHands](https://github.com/OpenHands/OpenHands) agent-server or the local Codex app-server harness.

![OpenSymphony desktop task graph showing Completed, Current, and Backlog panes with selected-task critical path](docs/images/os-tg-ancestry-critical-path.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 or Codex to work on issues autonomously
4. **Managing retries, reconciliation, and cleanup** based on issue state changes
5. **Providing a desktop operator dashboard** for task graph, run detail, and memory navigation

### 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
- **Dependency-aware Task Graph**: Shows dispatchable work, roadmap backlog, and selected-task critical paths
- **Harness selection**: Default OpenHands agent-server execution, plus local Codex app-server support for ChatGPT subscription-backed runs
- **Code Graph**: Tree-sitter-backed symbols, diagnostics, and source-cited structural context for agents
- **Operational Knowledge Graph**: Builds agent-queriable and human-navigable memory from completed work

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)
- 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: the [Codex CLI]https://developers.openai.com/codex/cli with a working ChatGPT login

For platform-specific Rust and Python/`uv` setup steps, see [Prerequisites](docs/prerequisites.md).

### Installation

```bash
cargo install opensymphony
```

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

```bash
opensymphony install openhands
```

For Codex runs, install the [Codex CLI](https://developers.openai.com/codex/cli)
and sign in with ChatGPT before selecting the Codex harness below.

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.

### Common Environment

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

```bash
export LINEAR_API_KEY="lin_api_..."
```

Create a personal Linear API key from
[Settings -> Account -> Security & access](https://linear.app/settings/account/security),
then use it for `LINEAR_API_KEY`. For advanced deployments, a Linear OAuth app
created under `Settings -> API -> OAuth applications` can use OAuth tokens
instead; Linear's [request limits](https://linear.app/developers/rate-limiting)
are higher for OAuth apps (5,000 requests/hour) than personal API keys (2,500
requests/hour). See Linear's
[OAuth 2.0 authentication](https://linear.app/developers/oauth-2-0-authentication)
docs for that path.

### OpenHands Runtime Environment

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

```bash
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:

```bash
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:

```bash
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_MODEL`/`LLM_API_KEY`/`LLM_BASE_URL`, and it does not launch the
managed OpenHands server for Codex-only routing.

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:

```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.
The generated workflow records a target branch marker for feature PRs and syncs.
The default is `develop`; non-interactive setup can choose a repository-specific
branch after the shared target-repo template has the same marker-aware
`WORKFLOW.md` and `pull`/`push`/`land` guidance:

```bash
opensymphony init --non-interactive --target-branch main
opensymphony init --non-interactive --target-branch release/next
```

Fresh init fetches target-repo assets from `kumanday/OpenSymphony-template`.
Until that template sync lands, treat the examples above as the intended
post-sync form; otherwise fresh repos can record the marker while copied skills
still use older branch guidance.

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.
When `--target-branch` or `--code-review` is present, `update` enters workflow
settings mode instead: it patches the managed `WORKFLOW.md` markers, rewrites
known legacy branch-control phrases when the target branch changes, and skips
the CLI reinstall, template skill refresh, and memory bootstrap.

```bash
opensymphony update --target-branch develop
opensymphony update --target-branch main
opensymphony update --target-branch release/next
opensymphony update --target-branch release/next --code-review openhands
```

`--code-review openhands` records the marker and attempts to enable an existing
OpenHands review workflow through `gh workflow` when
`.github/workflows/ai-pr-review.yml` is already present. It does not install or
repair a missing workflow file; `--code-review codex` and `--code-review none`
record the marker and attempt to disable that workflow. If `gh` is unavailable,
unauthorized, or cannot access Actions, `update` warns and leaves the workflow
state unchanged; verify or adjust the workflow state manually.

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

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

```bash
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.
When no installed or local bundle verifies, the launcher falls back to a local
source build, checks Rust/Cargo, Node/npm, source archive extraction, and
platform desktop/Tauri prerequisites first, and prints the exact manual
commands when it cannot install prerequisites automatically. `--dry-run` is
read-only: it verifies an existing cached or local bundle and reports when a
normal run would need to build from source.
Use `--install-path <dir>` or `OPENSYMPHONY_DESKTOP_INSTALL_PATH` to choose a
different install root; the launcher still creates the versioned bundle beneath
that root. On a cache miss, the launcher downloads the release index from the
versioned GitHub release matching the running CLI and installs the compatible
asset, so an empty desktop cache produces a real install attempt instead of a
missing-manifest dead end. Set `OPENSYMPHONY_DESKTOP_RELEASE_INDEX_URL` to
point at a private or test index. When a cached bundle is already installed,
the launcher checks the latest release index before launch, prompts
`Update before launch? [Y/n]` in an interactive terminal when a newer
compatible bundle is available, and updates by default in non-interactive runs.
Pass `--no-update` to launch the cached bundle without checking first.
The release-index and update prompt contract is defined in
[Desktop App Installer And Auto-Update Spec](docs/specs/desktop-app-installer-auto-update-spec.md).

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

![OpenSymphony knowledge graph atlas showing knowledge area clouds](docs/images/os-kg-atlas.png)

![OpenSymphony knowledge graph with a selected memory capsule](docs/images/os-kg-memory-capsule.png)

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 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](docs/installer-and-distribution.md).

## Architecture

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

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

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

- [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
- [Installer and Distribution Strategy]docs/installer-and-distribution.md - Future signed installer shape and DuckDB packaging boundaries
- [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.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](LICENSE)

## Acknowledgments

- [OpenAI Symphony]https://github.com/openai/symphony - The specification this implements
- [OpenHands]https://github.com/OpenHands/OpenHands - Managed local agent-server runtime
- [Codex app-server]https://github.com/openai/codex/tree/main/codex-rs/app-server - Local app-server harness runtime