opensymphony 3.0.0

A Rust implementation of the OpenAI Symphony orchestration design
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
# OpenHands Agent-Server Integration

## 1. Chosen integration surface

OpenSymphony integrates with the OpenHands SDK agent-server surface from Rust.

In scope:

- conversation create/get/send/run
- conversation interrupt, with pause as an older-server fallback only
- event search
- runtime event streaming over WebSocket
- workspace-aware conversation launch
- local supervised mode and external-server mode

Out of scope:

- `openhands serve`
- the web app Socket.IO protocol
- browser-oriented client integrations

## 2. Why OpenHands fits

OpenHands provides the execution-layer primitives Symphony needs:

- per-conversation workspace path
- provider-agnostic model configuration
- persistent conversations
- structured events
- background runs
- recoverable state over HTTP and WebSocket

OpenSymphony keeps all scheduling rules in Rust and uses OpenHands only as the
runtime substrate.

Operator snapshots label the requested execution scope separately from the
harness's effective containment. Trusted-host execution is reported honestly
and is not presented as workspace confinement.

## 3. Runtime modes

### Local supervised mode

The daemon launches one local OpenHands agent-server subprocess.

Use for:

- single-developer setup
- local experimentation
- CI smoke environments

Repository ownership:

- `tools/openhands-server/` owns the local package pin
- published `opensymphony` bundles those pinned files and materializes them into
  `~/.opensymphony/openhands-server` via `opensymphony install openhands`
- `run-local.sh` launches the pinned server
- the supervisor probes readiness before treating the server as usable

### External server mode

The daemon connects to an already-running agent-server at
`openhands.transport.base_url`.

Use for:

- pinned external integration tests
- future hosted deployments
- organization-managed runtime infrastructure

## 4. Startup contract

In supervised mode:

1. resolve launch command and environment
2. start the subprocess on loopback
3. probe readiness
4. create conversations with workflow-owned settings
5. reuse the server across issue runs

Rules:

- prefer real readiness probes over fixed sleeps
- do not silently adopt a foreign process as an owned child
- normalize local path-prefixed loopback targets before launch when needed

## 5. Workspace model

OpenSymphony uses one server with many issue-specific workspaces.

Each conversation request sets `workspace.working_dir` to the deterministic
issue workspace path. This preserves issue isolation without requiring a
separate server per issue.

For a repository-bound terminal task, that path is the published, verified
checkout generation, not the orchestrator repository or a staging directory.
The launch envelope records the checkout generation, target commit, instruction
provenance, harness/model profile, requested single-checkout scope, and the
truthful trusted-host process-`cwd` containment receipt. A compatible persisted
conversation may be reused only when its envelope matches; a mismatch is
rejected before attach.

For an eligible parent, `workspace.working_dir` is the non-Git parent execution
root. The conversation manifest binds the parent envelope to the OpenHands
conversation ID and records the complete relative checkout map with requested
`parent_multi_checkout` scope. Trusted local OpenHands runs report
`trusted_host`; the prompt and `cwd` do not claim filesystem sandboxing. The
full first prompt combines the generic parent lifecycle, optional verified
project-set integration instructions, parent task facts, the checkout map, and
repository instructions keyed by canonical repository ID.

When the local memory server issues a process-scoped worker grant, the grant
is also part of conversation compatibility. The agent-server create contract
does not provide a conversation MCP-config update operation, so a persisted
conversation with an old grant is superseded and recreated with the current
grant before it can receive another turn. The old conversation evidence is
retained until remote deletion succeeds. After daemon recovery, the
supervised grant registry is reconstructed, so OpenHands forces this
replacement path; ordinary in-process retries keep the existing conversation
and refreshed grant. A retained issue that reopens after an inactive, terminal,
or binding-superseded lifecycle receives the same one-time fresh-conversation
treatment, even though its checkout and repository may still be reusable.

If a recovered trigger-pending run receives `409 Conflict`, the adapter waits
for the active turn to stop, refreshes the recovered event baseline, and retries
`POST /run` on the same conversation;
the conflict alone does not prove that the recovered prompt was accepted.
Untrusted conversation envelopes are preserved as evidence but are never used
to retire a remote conversation. Codex evidence uses the workspace metadata
directory, while OpenHands evidence uses its configured persistence directory.
Malformed superseded-conversation evidence is a lifecycle error and is never
treated as an empty list that could overwrite prior evidence.
Condenser tool-matching recovery follows the same evidence rule: it preserves
the old manifest before creating a replacement, and clears that evidence only
after remote retirement succeeds.
When a newly created conversation fails the verified-checkout workspace
barrier before its primary manifest is written, its pending ownership record
is cleared only after remote retirement succeeds or equivalent superseded
evidence is durable; if both operations fail, the pending record remains for
restart recovery and later cleanup.
When the configured harness changes, daemon startup applies the same
run/envelope binding checks to pending OpenHands ownership before deciding
whether to initialize the OpenHands client and managed server. A valid pending
owner is promoted for recovery; stale or incompatible ownership is ignored.

## 6. Conversation model

Each issue gets a stable conversation manifest under:

```text
<issue_workspace>/.opensymphony/conversation.json
```

That manifest records enough data for:

- reattachment
- restart recovery
- debug reuse
- rehydration

The persisted OpenHands state directory is derived from the workflow-owned
conversation persistence settings inside the issue workspace.

In managed local mode, the OpenHands server's global conversation registry is
also scoped per target repo. OpenSymphony sets `OH_CONVERSATIONS_PATH` to:

```text
<tool_dir>/workspace/conversations/repos/<repo-key>/active
```

Before managed server startup, known terminal issue conversations from existing
workspace manifests move into the sibling `archived` store, and current Linear
candidate issues move into `active` from `archived` or the legacy flat store.
This startup migration is an isolated compatibility shim for pre repo-scoped
stores and can be removed after existing installs have aged out. It prevents the
managed server from eagerly resuming every historical conversation across all
repos during normal orchestration. `opensymphony debug <issue-id>` locates the
requested conversation in active or archived storage and starts the managed
server against that store before attaching.

The default managed-local startup window is 180 seconds. Debug-launched managed
servers suppress raw agent-server stderr during readiness so restored
conversation state does not flood the operator terminal before the debug
transcript renderer takes over.

## 7. Runtime contract

The internal `opensymphony_openhands` module owns:

- typed request/response models
- authenticated REST requests when needed
- WebSocket attach/reconcile/reconnect behavior
- ready-state detection
- issue session launch and reuse

The local `opensymphony run` worker keeps OpenHands attach and event-poll futures
off its shared routing stack. Attach runs in a cancellation-owned task, so an
aborted issue run also aborts its in-flight attach before it can report launch.

Harness interrupt uses `POST /api/conversations/{id}/interrupt` as the primary
mid-turn stop request. If an older agent-server returns a missing-route status
for that endpoint, the adapter falls back to `POST /api/conversations/{id}/pause`
and records that fallback in acknowledgement diagnostics. The adapter reports
acknowledgement only after it reattaches/reconciles runtime state and observes a
stopped conversation status such as `paused`, or after the configured readiness
timeout path records a timeout diagnostic.

For reused conversations that are already `queued` or `running`, the runtime now
surfaces launch metadata immediately after a successful attach so the
orchestrator can keep tracking the live worker while it waits for the previous
turn to settle. The same early launch reporting now also applies when a reused
conversation only reveals its active prior turn later through a `/run`
`409 Conflict`, after the attach-time mirror looked idle.

The orchestration layer should not need to know OpenHands wire details.

### Subscription credential adapter

The pinned `openhands-sdk==1.24.0` exposes
`LLM.subscription_login(vendor="openai", model=..., auth_method=...)` for
ChatGPT/Codex subscription use. OpenSymphony's feature-gated
`openai_subscription` workflow mode does not implement an undocumented OAuth
flow itself. It expects the documented SDK browser or device-code login path, or
a hosted credential broker, to own refresh credentials and provide a short-lived
access token through the configured environment reference.

When that reference is present, OpenSymphony constructs the documented Codex LLM
shape for conversation creation: `model` normalized to `openai/<codex-model>`,
the ChatGPT Codex backend base URL, Codex headers, `store=false`, and streaming
enabled. The launch profile and manifests persist only environment-variable
names, auth-directory references, and credential hashes; raw access tokens and
refresh material are not persisted.

Credential bootstrap controls such as `auth_directory_env`, `auth_method`,
`open_browser`, and `force_login` are metadata for the SDK login step or a
future hosted credential broker. They are intentionally not forwarded as
agent-server conversation payload fields because the pinned SDK does not
document such fields on the conversation creation contract.

Codex app-server subscription readiness is separate from this OpenHands
auth-directory metadata. Local Codex runs report subscription readiness through
Codex CLI login checks, while the OpenHands auth-directory field remains scoped
to OpenHands SDK subscription bootstrap.

API-key OpenHands configuration remains independent. Existing workflows that use
`LLM_MODEL`, `LLM_API_KEY`, and `LLM_BASE_URL` continue through the default
`api_key` credential mode and do not need the subscription feature.

## 8. Tooling note

OpenSymphony no longer forwards workflow-owned Linear bridge configuration into
conversation creation.

Agent-side Linear operations now live entirely in the target repo’s checked-in
GraphQL helper assets and use `LINEAR_API_KEY` directly.

## 9. Validation

Important checks:

- `cargo test --test live_pinned_server`
- `cargo test --test issue_session_runner`
- `cargo test --test doctor`

When validating a local setup, confirm that:

- the configured OpenHands target is reachable
- a temp conversation can be created
- the WebSocket stream reaches readiness
- restart recovery reuses the stored conversation manifest correctly

## 10. Migration note

OpenSymphony 1.0.0 removed workflow-owned `openhands.mcp`. Older repos should
remove that block and rely on the repo-local Linear GraphQL helper assets
copied by `opensymphony init`.

<!-- BEGIN OPENSYMPHONY MANAGED MEMORY SYNC -->

## Current model

- COE-252 contributed: PR #10: Implement foundation workflow and scheduler contracts
- COE-253 contributed: PR #19: COE-253: OpenHands Runtime Adapter (merge `911b0b4`)
- COE-254 contributed: PR #6: COE-254: bootstrap tracker, workspace, and orchestration core
- COE-255 contributed: PR #4: COE-255: add control plane and FrankenTUI slice
- COE-256 contributed: PR #1: COE-257: tighten hosted deployment guidance
- COE-258 contributed: PR #83: Add memory init and mapped docs sync

## Important invariants

- Preserve the behavior described in the recent captured changes unless current code and tests show it has changed.
- Use capsule source refs to inspect the original PR or Linear issue when context is ambiguous.

## Operational flow

- No generated diagram requested for this sync.

## Known gotchas

- No area-specific gotchas were inferred from the selected memory.

## Recent changes

- COE-252: Foundation and Contracts
- COE-253: OpenHands Runtime Adapter
- COE-254: Tracker, Workspaces, and Orchestration
- COE-255: Observability and FrankenTUI
- COE-256: Validation and Local Operations
- COE-258: Bootstrap workspace and crate boundaries
- COE-259: Workflow loader and typed config
- COE-260: Domain model and orchestrator state machine
- COE-261: Local agent-server supervisor
- COE-262: REST client and conversation contract
- COE-263: Workspace manager and lifecycle hooks
- COE-264: Linear read adapter and issue normalization
- COE-265: WebSocket event stream, reconciliation, and recovery
- COE-266: Issue session runner
- COE-267: Linear MCP write surface
- COE-268: Orchestrator scheduler, retries, and reconciliation
- COE-269: Control-plane API and snapshot store
- COE-270: Repository harness and generated context artifacts
- COE-271: FrankenTUI operator client
- COE-272: Fake OpenHands server and protocol contract suite
- COE-273: Live local end-to-end suite
- COE-274: CLI packaging, doctor, and local operations docs
- COE-275: Remote agent-server mode and auth hardening
- COE-277: Implement hierarchy-aware task selection
- COE-280: Support workflow-owned OpenHands auth, provider, and launcher overrides at runtime
- COE-281: Support path-bearing OpenHands base URLs and MCP config at runtime
- COE-282: Support workflow-owned OpenHands conversation reuse policy at runtime
- COE-284: Add orchestrator run command to CLI and make it installable
- COE-287: Add opensymphony debug command for conversational session debugging
- COE-293: OpenHands agent has no filesystem tools - only FinishTool and ThinkTool
- COE-294: Detect LLM config changes and rehydrate conversations with updated env vars
- COE-382: Add supply-chain and security audits to CI
- COE-383: Decompose oversized session and TUI modules into focused submodules
- COE-384: Expand error-path tests for Linear client and workspace hooks
- COE-385: Resolve runtime tracking TODO in OpenHands session runner
- COE-386: Wire cargo-llvm-cov coverage reporting and regression floor into CI
- COE-387: Audit tracing spans and diagnostics for secret leakage
- COE-399: Linear Read Coverage And Task Graph Cache
- COE-400: OpenHands Event Normalization And Runtime Mirror
- COE-401: Web App Entry And Deployment Modes
- COE-405: Linear Milestone, Issue, And Sub-Issue Mutations
- COE-407: Browser Transport And Remote Stream Protocols
- COE-408: Harness Adapter And Capability Model
- COE-411: Task Graph Editor And Runtime Overlay UI
- COE-412: Runtime Timeline And Terminal/Log Association
- COE-414: Diff, Validation, Approval, And Run Action Views
- COE-419: Hosted Auth Placeholders And Web Parity
- COE-423: Model And Credential Settings
- COE-425: OpenHands Subscription Credential Adapter
- COE-426: Codex App-Server Prototype And Benchmarks
- COE-428: Model Configuration UI And Routing Metadata
- COE-429: Codex Approvals And Cross-Harness Routing
- COE-434: Long-running harness liveness and scheduler/runtime ownership contract
- COE-435: Long-running run observability fixtures and client-facing diagnostics
- COE-473: Desktop task graph dependency and run detail parity
- COE-475: ChatGPT OAuth For Codex Harness
- COE-476: Codex Production Harness Enablement
- COE-478: Harden model profile storage and validation follow-ups
- COE-479: Codex Debug Session Resume
- COE-486: Harness Interrupt Contract And Run Diagnostics
- COE-487: Desktop Run Detail TUI Parity
- COE-488: Lazy Desktop Launcher Command
- COE-489: OpenHands Agent-Server Interrupt Adapter
- COE-490: Codex App-Server Turn Interrupt Adapter
- COE-491: Desktop Run Detail Action Wiring And Cleanup
- COE-492: Merging Supersedes Human Review Polling
- COE-493: Desktop Operations Integration Hardening
- COE-504: Linear Polling And Rate-Limit Recovery
- COE-505: Add scheduler-side Codex stdio interrupt channel

## Source refs

- COE-252
- COE-253
- COE-254
- COE-255
- COE-256
- COE-258
- COE-259
- COE-260
- COE-261
- COE-262
- COE-263
- COE-264
- COE-265
- COE-266
- COE-267
- COE-268
- COE-269
- COE-270
- COE-271
- COE-272
- COE-273
- COE-274
- COE-275
- COE-277
- COE-280
- COE-281
- COE-282
- COE-284
- COE-287
- COE-293
- COE-294
- COE-382
- COE-383
- COE-384
- COE-385
- COE-386
- COE-387
- COE-399
- COE-400
- COE-401
- COE-405
- COE-407
- COE-408
- COE-411
- COE-412
- COE-414
- COE-419
- COE-423
- COE-425
- COE-426
- COE-428
- COE-429
- COE-434
- COE-435
- COE-473
- COE-475
- COE-476
- COE-478
- COE-479
- COE-486
- COE-487
- COE-488
- COE-489
- COE-490
- COE-491
- COE-492
- COE-493
- COE-504
- COE-505

<!-- END OPENSYMPHONY MANAGED MEMORY SYNC -->