An open-source spec for Codex orchestration: Symphony. | OpenAI

Source: original

April 27, 2026

Engineering

An open-source spec for Codex orchestration: Symphony

By Alex Kotliarskyi, Victor Zhu, and Zach Brock

Loading…

Share

Six months ago, while working on an internal productivity tool, our team made a controversial (at the time) decision: we’d build our repo with no human-written code. Every line in our project repository had to be generated by Codex.

To make that work, we redesigned our engineering workflow from the ground up. We built an agent-friendly repository, invested heavily in automated tests and guardrails, and treated Codex as a full-fledged teammate. We documented that journey in our previous blog post on harness engineering.

And it worked, but then we ran into the next bottleneck: context switching.

To solve this new problem, we built a system called Symphony. Symphony ⁠(opens in a new window) is an agent orchestrator that turns a project-management board like Linear into a control plane for coding agents. Every open task gets an agent, agents run continuously, and humans review the results.

This post explains how we created Symphony—resulting in a 500% increase in landed pull requests on some teams—and how to use it to turn your own issue tracker into an always-on agent orchestrator.

The ceiling of interactive coding agents

Even as they get easier to use, coding agents—whether accessed through web apps or CLI—are still interactive tools.

As the scale of agentic work increased at OpenAI, we found a new kind of burden. Each engineer would open a few Codex sessions, assign tasks, review the output, steer the agent, and repeat. In practice, most people could comfortably manage three to five sessions at a time before context switching became painful. Beyond that, productivity dropped. We'd forget which session was doing what, jump between terminals to nudge agents back on track, and debug long-running tasks that stalled halfway through.

The agents were fast, but we had a system bottleneck: human attention. We had effectively built a team of extremely capable junior engineers, then assigned our human engineers to micromanaging them. That wasn’t going to scale.

A shift in perspective

We realized we were optimizing the wrong thing. We were orienting our system around coding sessions and merged PRs, when PRs and sessions are really a means to an end. Software workflows are largely organized around deliverables: issues, tasks, tickets, milestones.

So we asked ourselves what would happen if we stopped supervising agents directly and instead let them pull work from our task tracker.

That idea became Symphony, a written spec that functions as a supervisor to orchestrate agentic work.

Turning our issue tracker into an agent orchestrator

Symphony started with a simple concept: any open task should get picked up and completed by an agent. Instead of managing Codex sessions in multiple tabs, we made our issue tracker the control plane.

In this setup, each open Linear issue maps to a dedicated agent workspace. Symphony continuously watches the task board and ensures that every active task has an agent running in the loop until it’s done. If an agent crashes or stalls, Symphony restarts it. If new work appears, Symphony picks it up and starts organizing work.

We built our workflow based on ticket statuses, using the task manager Linear as a state machine.

Coding agents use Linear as a state machine to work alongside us.

In practice, Symphony decouples work from sessions and from pull requests. Some issues produce multiple PRs across repos; others are pure investigation or analysis that never touch the codebase.

Once work is abstracted this way, tickets can represent much larger units of work.

We regularly use Symphony to orchestrate complex features and infrastructure migrations. For example, we might file a task asking the agent to analyze the codebase, Slack, or Notion and produce an implementation plan. Once we’re happy with the plan, the agent generates a tree of tasks, breaking the work into stages and defining dependencies between tasks.

Agents only start working on tasks that aren’t blocked, so execution unfolds naturally and optimally in parallel for this DAG (a sequence of execution steps). For example, we marked the React upgrade as blocked on a migration to Vite. As expected, agents started upgrading React only after the migration to Vite was complete.

Agents can also create work themselves. During implementation or review, they often notice improvements that fall outside the scope of the current task: a performance issue, a refactoring opportunity, or a better architecture. When that happens, they simply file a new issue that we can evaluate and schedule later—many of these follow-up tasks also get picked up by agents. While we oversee this process, agents stay organized and keep work moving forward.

This way of working dramatically reduces the cognitive cost of kicking off ambiguous work. If the agent gets something wrong, that’s still useful information, and the cost to us is near zero. We can very cheaply file tickets for the agent to go prototype and explore, and throw away any explorations we don’t like.

Because the orchestrator runs on devboxes and never sleeps, we can add tasks from anywhere and know an agent will pick it up. For instance, one engineer on our team made three significant changes from the Linear app on his phone from a cozy cabin on shoddy wifi.

An increase in exploration from working this way

When observing the effects of working with Symphony, the most obvious change was output. Among some teams at OpenAI, we saw the number of landed PRs increase by 500% in the first three weeks. Outside of OpenAI, Linear founder Karri Saarinen highlighted a spike in workspaces created⁠(opens in a new window) as we released Symphony. However, the deeper shift is how teams think about work.

When our engineers no longer spend time supervising Codex sessions, the economics of code changes completely. The perceived cost of each change drops because we’re no longer investing human effort in driving the implementation itself.

That changed our behavior. It's become trivial to spin up speculative tasks in Symphony. Try an idea, explore a refactor, test a hypothesis, and only keep the results that look promising.

It also broadens who can initiate work. Our product manager and designer can now file feature requests directly into Symphony. They don’t need to check out the repo or manage a Codex session. They describe the feature and get back a review packet that includes a video walkthrough of the feature working inside the real product.

Symphony also shines in large monorepos (like the one we have at OpenAI) where the last mile of landing a PR is slow and fragile. The system watches CI, rebases when needed, resolves conflicts, retries flaky checks, and generally shepherds changes through the pipeline. By the time a ticket reaches Merging , we have high confidence the change will make it into the main branch without human babysitting.

Before and after grid of Symphony

After implementing Symphony, we delegate more work to agents and focus on harder, more exploratory tasks.

Progress comes with new, different problems

Operating at this level comes with tradeoffs. When we moved from steering agents interactively to assigning them work at the ticket level, we lost the ability to constantly nudge them mid-flight and course-correct when needed. Sometimes the agent produced something that completely missed the mark. That was useful—those failures revealed gaps in the system and helped us make it more robust.

Instead of patching the result manually, we added guardrails and skills so the agents could succeed the next time. Over time, this led us to add new capabilities to our harness, like running end-to-end tests, driving the app through Chrome DevTools, and managing QA smoke tests. We significantly improved our documentation and clarified what good looks like.

Not every task fits the Symphony style of work. Some problems still require engineers working directly with interactive Codex sessions, especially ambiguous problems or work that requires strong judgment and expertise. In practice, these are usually the most interesting and enjoyable tasks for our engineers to spend time on.

The difference is that Symphony can handle the bulk of routine implementation work. That lets engineers focus on a single hard problem at a time instead of constantly context-switching between smaller tasks.

We also learned that treating agents as rigid nodes in a state machine doesn’t work well. Models get smarter and can solve bigger problems than the box we try to fit them in. Our early versions of agentic work was only asking Codex to implement the task. That approach proved too limiting. Codex is perfectly capable of creating multiple PRs as well as reading review feedback and addressing it. So we gave it tools—gh CLI, skills to read CI logs, etc.—and now we can ask Codex to do more, like closing old PRs or pulling reports on completed vs. abandoned work. These types of tasks fell way outside the initial feature implementation box.

So we eventually moved toward giving agents objectives instead of strict transitions, much like a good manager would assign a goal to a direct report on their team. The power of models comes from their ability to reason, so give them tools and context and let them cook.

Using Symphony to build Symphony

When you open the Symphony repository,⁠(opens in a new window) the first thing you’ll notice is that Symphony is technically just a SPEC.md file—a definition of the problem and the intended solution. Rather than building a complex supervision system, we defined the problem and intended solutions, giving agents high-level steering.

Markdown

`

1

Symphony Service Specification

2

3

Status: Draft v1 (language-agnostic)

4

5

Purpose: Define a service that orchestrates coding agents to get project work done.

6

7

1. Problem Statement

8

9

Symphony is a long-running automation service that continuously reads work from an issue tracker

10

(Linear in this specification version), creates an isolated workspace for each issue, and runs a

11

coding agent session for that issue inside the workspace.

12

13

The service solves four operational problems:

14

15

16

17

workspace directories.

18

19

settings with their code.

20

21

22

Implementations are expected to document their trust and safety posture explicitly. This

23

specification does not require a single approval, sandbox, or operator-confirmation policy; some

24

implementations may target trusted environments with a high-trust configuration, while others may

25

require stricter approvals or sandboxing.

26

27

Important boundary:

28

29

30

31

using tools available in the workflow/runtime environment.

32

33

necessarily Done.

34

35

2. Goals and Non-Goals

36

37

2.1 Goals

38

39

40

41

42

43

44

45

46

47

48

2.2 Non-Goals

49

50

51

52

53

54

workflow prompt and agent tooling.)

55

56

57

implementations.

58

59

3. System Overview

60

61

3.1 Main Components

62

63

  1. Workflow Loader

64

65

66

67

68

  1. Config Layer

69

70

71

72

73

  1. Issue Tracker Client

74

75

76

77

78

79

  1. Orchestrator

80

81

82

83

84

85

  1. Workspace Manager

86

87

88

89

90

91

  1. Agent Runner

92

93

94

95

96

97

  1. Status Surface (optional)

98

99

operator-facing view).

100

101

  1. Logging

102

103

104

3.2 Abstraction Levels

105

106

Symphony is easiest to port when kept in these layers:

107

108

  1. Policy Layer (repo-defined)

109

110

111

112

  1. Configuration Layer (typed getters)

113

114

115

116

  1. Coordination Layer (orchestrator)

117

118

119

  1. Execution Layer (workspace + agent subprocess)

120

121

122

  1. Integration Layer (Linear adapter)

123

124

125

  1. Observability Layer (logs + optional status surface)

126

127

128

3.3 External Dependencies

129

130

131

132

133

134

135

136

4. Core Domain Model

137

138

4.1 Entities

139

140

4.1.1 Issue

141

142

Normalized issue record used by orchestration, prompt rendering, and observability output.

143

144

Fields:

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

4.1.2 Workflow Definition

170

171

Parsed WORKFLOW.md payload:

172

173

174

175

176

177

178

4.1.3 Service Config (Typed View)

179

180

Typed runtime values derived from WorkflowDefinition.config plus environment resolution.

181

182

Examples:

183

184

185

186

187

188

189

190

191

4.1.4 Workspace

192

193

Filesystem workspace assigned to one issue identifier.

194

195

Fields (logical):

196

197

198

possible if configured without path separators)

199

200

201

202

4.1.5 Run Attempt

203

204

One execution attempt for one issue.

205

206

Fields (logical):

207

208

209

210

211

212

213

214

215

216

4.1.6 Live Session (Agent Session Metadata)

217

218

State tracked while a coding-agent subprocess is running.

219

220

Fields:

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

4.1.7 Retry Entry

239

240

Scheduled retry state for an issue.

241

242

Fields:

243

244

245

246

247

248

249

250

251

4.1.8 Orchestrator Runtime State

252

253

Single authoritative in-memory state owned by the orchestrator.

254

255

Fields:

256

257

258

259

260

261

262

263

264

265

266

4.2 Stable Identifiers and Normalization Rules

267

268

269

270

271

272

273

274

275

276

277

278

279

280

5. Workflow Specification (Repository Contract)

281

282

5.1 File Discovery and Path Resolution

283

284

Workflow file path precedence:

285

286

  1. Explicit application/runtime setting (set by CLI startup path).

287

  1. Default: WORKFLOW.md in the current process working directory.

288

289

Loader behavior:

290

291

292

293

294

5.2 File Format

295

296

WORKFLOW.md is a Markdown file with optional YAML front matter.

297

298

Design note:

299

300

301

runtime settings, hooks, and tracker selection/config) without requiring out-of-band

302

service-specific configuration.

303

304

Parsing rules:

305

306

307

308

309

310

311

312

Returned workflow object:

313

314

315

316

317

5.3 Front Matter Schema

318

319

Top-level keys:

320

321

322

323

324

325

326

327

328

Unknown keys should be ignored for forward compatibility.

329

330

Note:

331

332

333

(for example server) without changing the core schema above.

334

335

apply dynamically or require restart.

336

337

13.7.

338

339

5.3.1 tracker (object)

340

341

Fields:

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

5.3.2 polling (object)

360

361

Fields:

362

363

364

365

366

367

5.3.3 workspace (object)

368

369

Fields:

370

371

372

373

374

375

discouraged).

376

377

5.3.4 hooks (object)

378

379

Fields:

380

381

382

383

384

385

386

agent.

387

388

389

390

exists.

391

392

393

394

395

396

397

398

399

400

401

5.3.5 agent (object)

402

403

Fields:

404

405

406

407

408

409

410

411

412

413

414

415

416

5.3.6 codex (object)

417

418

Fields:

419

420

For Codex-owned config values such as approval_policy, thread_sandbox, and

421

turn_sandbox_policy, supported values are defined by the targeted Codex app-server version.

422

Implementors should treat them as pass-through Codex config values rather than relying on a

423

hand-maintained enum in this spec. To inspect the installed Codex schema, run

424

codex app-server generate-json-schema --out <dir> and inspect the relevant definitions referenced

425

by v2/ThreadStartParams.json and v2/TurnStartParams.json. Implementations may validate these

426

fields locally if they want stricter startup checks.

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

5.4 Prompt Template Contract

447

448

The Markdown body of WORKFLOW.md is the per-issue prompt template.

449

450

Rendering requirements:

451

452

453

454

455

456

Template input variables:

457

458

459

460

461

462

463

464

Fallback prompt behavior:

465

466

467

(You are working on an issue from Linear.).

468

469

back to a prompt.

470

471

5.5 Workflow Validation and Error Surface

472

473

Error classes:

474

475

476

477

478

479

480

481

Dispatch gating behavior:

482

483

484

485

486

6. Configuration Specification

487

488

6.1 Source Precedence and Resolution Semantics

489

490

Configuration precedence:

491

492

  1. Workflow file path selection (runtime setting -> cwd default).

493

  1. YAML front matter values.

494

  1. Environment indirection via $VAR_NAME inside selected YAML values.

495

  1. Built-in defaults.

496

497

Value coercion semantics:

498

499

500

501

502

503

arbitrary shell command strings.

504

505

6.2 Dynamic Reload Semantics

506

507

Dynamic reload is required:

508

509

510

511

512

cadence, concurrency limits, active/terminal states, codex settings, workspace paths/hooks, and

513

prompt content for future runs).

514

515

execution, and agent launches.

516

517

changes.

518

519

require restart unless the implementation explicitly supports live rebind.

520

521

before dispatch) in case filesystem watch events are missed.

522

523

configuration and emit an operator-visible error.

524

525

6.3 Dispatch Preflight Validation

526

527

This validation is a scheduler preflight run before attempting to dispatch new work. It validates

528

the workflow/config needed to poll and launch workers, not a full audit of all possible workflow

529

behavior.

530

531

Startup validation:

532

533

534

535

536

Per-tick dispatch validation:

537

538

539

540

operator-visible error.

541

542

Validation checks:

543

544

545

546

547

548

549

550

6.4 Config Fields Summary (Cheat Sheet)

551

552

This section is intentionally redundant so a coding agent can implement the config layer quickly.

553

554

555

556

557

558

559

560

561

562

563

locally

564

565

cap applied across configured SSH hosts

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

for ephemeral local bind, and CLI --port overrides it

584

585

7. Orchestration State Machine

586

587

The orchestrator is the only component that mutates scheduling state. All worker outcomes are

588

reported back to it and converted into explicit state transitions.

589

590

7.1 Issue Orchestration States

591

592

This is not the same as tracker states (Todo, In Progress, etc.). This is the service's internal

593

claim state.

594

595

  1. Unclaimed

596

597

598

  1. Claimed

599

600

601

602

  1. Running

603

604

605

  1. RetryQueued

606

607

608

  1. Released

609

610

re-dispatch.

611

612

Important nuance:

613

614

615

616

617

618

coding-agent thread in the same workspace, up to agent.max_turns.

619

620

621

original task prompt that is already present in thread history.

622

623

(about 1 second) so it can re-check whether the issue remains active and needs another worker

624

session.

625

626

7.2 Run Attempt Lifecycle

627

628

A run attempt transitions through these phases:

629

630

  1. PreparingWorkspace

631

  1. BuildingPrompt

632

  1. LaunchingAgentProcess

633

  1. InitializingSession

634

  1. StreamingTurn

635

  1. Finishing

636

  1. Succeeded

637

  1. Failed

638

  1. TimedOut

639

  1. Stalled

640

  1. CanceledByReconciliation

641

642

Distinct terminal reasons are important because retry logic and logs differ.

643

644

7.3 Transition Triggers

645

646

647

648

649

650

651

652

653

654

655

656

turn loop.

657

658

659

660

661

662

663

664

665

666

667

668

669

670

671

672

673

674

675

7.4 Idempotency and Recovery Rules

676

677

678

679

680

681

682

683

8. Polling, Scheduling, and Reconciliation

684

685

8.1 Poll Loop

686

687

At startup, the service validates config, performs startup cleanup, schedules an immediate tick, and

688

then repeats every polling.interval_ms.

689

690

The effective poll interval should be updated when workflow config changes are re-applied.

691

692

Tick sequence:

693

694

  1. Reconcile running issues.

695

  1. Run dispatch preflight validation.

696

  1. Fetch candidate issues from tracker using active states.

697

  1. Sort issues by dispatch priority.

698

  1. Dispatch eligible issues while slots remain.

699

  1. Notify observability/status consumers of state changes.

700

701

If per-tick validation fails, dispatch is skipped for that tick, but reconciliation still happens

702

first.

703

704

8.2 Candidate Selection Rules

705

706

An issue is dispatch-eligible only if all are true:

707

708

709

710

711

712

713

714

715

716

717

Sorting order (stable intent):

718

719

  1. priority ascending (1..4 are preferred; null/unknown sorts last)

720

  1. created_at oldest first

721

  1. identifier lexicographic tie-breaker

722

723

8.3 Concurrency Control

724

725

Global limit:

726

727

728

729

Per-state limit:

730

731

732

733

734

The runtime counts issues by their current tracked state in the running map.

735

736

Optional SSH host limit:

737

738

739

that many concurrent agents at once.

740

741

742

8.4 Retry and Backoff

743

744

Retry entry creation:

745

746

747

748

749

Backoff formula:

750

751

752

753

754

755

Retry handling behavior:

756

757

  1. Fetch active candidate issues (not all issues).

758

  1. Find the specific issue by issue_id.

759

  1. If not found, release claim.

760

  1. If found and still candidate-eligible:

761

762

763

  1. If found but no longer active, release claim.

764

765

Note:

766

767

768

(including terminal transitions for currently running issues).

769

770

rather than performing terminal cleanup itself.

771

772

8.5 Active Run Reconciliation

773

774

Reconciliation runs every tick and has two parts.

775

776

Part A: Stall detection

777

778

779

780

781

782

783

784

Part B: Tracker state refresh

785

786

787

788

789

790

791

792

793

8.6 Startup Terminal Workspace Cleanup

794

795

When the service starts:

796

797

  1. Query tracker for issues in terminal states.

798

  1. For each returned issue identifier, remove the corresponding workspace directory.

799

  1. If the terminal-issues fetch fails, log a warning and continue startup.

800

801

This prevents stale terminal workspaces from accumulating after restarts.

802

803

9. Workspace Management and Safety

804

805

9.1 Workspace Layout

806

807

Workspace root:

808

809

810

bare relative names)

811

812

Per-issue workspace path:

813

814

815

816

Workspace persistence:

817

818

819

820

821

9.2 Workspace Creation and Reuse

822

823

Input: issue.identifier

824

825

Algorithm summary:

826

827

  1. Sanitize identifier to workspace_key.

828

  1. Compute workspace path under workspace root.

829

  1. Ensure the workspace path exists as a directory.

830

  1. Mark created_now=true only if the directory was created during this call; otherwise

831

created_now=false.

832

  1. If created_now=true, run after_create hook if configured.

833

834

Notes:

835

836

837

838

code generation) is implementation-defined and is typically handled via hooks.

839

840

9.3 Optional Workspace Population (Implementation-Defined)

841

842

The spec does not require any built-in VCS or repository bootstrap behavior.

843

844

Implementations may populate or synchronize the workspace using implementation-defined logic and/or

845

hooks (for example after_create and/or before_run).

846

847

Failure handling:

848

849

850

851

prepared directory.

852

853

explicitly chosen and documented.

854

855

9.4 Workspace Hooks

856

857

Supported hooks:

858

859

860

861

862

863

864

Execution contract:

865

866

867

cwd.

868

869

conforming default.

870

871

872

873

Failure semantics:

874

875

876

877

878

879

880

9.5 Safety Invariants

881

882

This is the most important portability constraint.

883

884

Invariant 1: Run the coding agent only in the per-issue workspace path.

885

886

887

888

889

Invariant 2: Workspace path must stay inside workspace root.

890

891

892

893

894

895

Invariant 3: Workspace key is sanitized.

896

897

898

899

900

10. Agent Runner Protocol (Coding Agent Integration)

901

902

This section defines the language-neutral contract for integrating a coding agent app-server.

903

904

Compatibility profile:

905

906

907

be extracted (for example session IDs, completion state, approval handling, and usage/rate-limit

908

telemetry).

909

910

911

meaning, especially for nested IDs, approval requests, user-input-required signals, and

912

token/rate-limit metadata.

913

914

10.1 Launch Contract

915

916

Subprocess launch parameters:

917

918

919

920

921

922

923

924

Notes:

925

926

927

928

929

Recommended additional process settings:

930

931

932

933

10.2 Session Startup Handshake

934

935

Reference: https://developers.openai.com/codex/app-server/

936

937

The client must send these protocol messages in order:

938

939

Illustrative startup transcript (equivalent payload shapes are acceptable if they preserve the same

940

semantics):

941

942


943

{"id":1,"method":"initialize","params":{"clientInfo":{"name":"symphony","version":"1.0"},"capabilities":{}}}

944

{"method":"initialized","params":{}}

945

{"id":2,"method":"thread/start","params":{"approvalPolicy":"<implementation-defined>","sandbox":"<implementation-defined>","cwd":"/abs/workspace"}}

946

{"id":3,"method":"turn/start","params":{"threadId":"<thread-id>","input":[{"type":"text","text":"<rendered prompt-or-continuation-guidance>"}],"cwd":"/abs/workspace","title":"ABC-123: Example","approvalPolicy":"<implementation-defined>","sandboxPolicy":{"type":"<implementation-defined>"}}}

947

948

949

  1. initialize request

950

951

952

953

954

necessary capability flag(s) here.

955

956

  1. initialized notification

957

  1. thread/start request

958

959

960

961

962

963

protocol mechanism supported by the targeted Codex app-server version.

964

  1. turn/start request

965

966

967

968

guidance for later turns on the same thread

969

970

971

972

973

the targeted app-server version

974

975

Session identifiers:

976

977

978

979

980

981

982

10.3 Streaming Turn Processing

983

984

The client reads line-delimited messages until the turn terminates.

985

986

Completion conditions:

987

988

989

990

991

992

993

994

Continuation processing:

995

996

997

on the same live threadId.

998

999

when the worker run is ending.

1000

1001

Line handling requirements:

1002

1003

1004

1005

1006

1007

1008

1009

1010

10.4 Emitted Runtime Events (Upstream to Orchestrator)

1011

1012

The app-server client emits structured events to the orchestrator callback. Each event should

1013

include:

1014

1015

1016

1017

1018

1019

1020

1021

Important emitted events may include:

1022

1023

1024

1025

1026

1027

1028

1029

1030

1031

1032

1033

1034

1035

1036

10.5 Approval, Tool Calls, and User Input Policy

1037

1038

Approval, sandbox, and user-input behavior is implementation-defined.

1039

1040

Policy requirements:

1041

1042

1043

posture.

1044

1045

implementation should either satisfy them, surface them to an operator, auto-resolve them, or

1046

fail the run according to its documented policy.

1047

1048

Example high-trust behavior:

1049

1050

1051

1052

1053

1054

Unsupported dynamic tool calls:

1055

1056

1057

be handled according to their extension contract.

1058

1059

failure response and continue the session.

1060

1061

1062

Optional client-side tool extension:

1063

1064

1065

1066

1067

using the protocol mechanism supported by the targeted Codex app-server version.

1068

1069

1070

linear_graphql extension contract:

1071

1072

1073

tracker auth for the current session.

1074

1075

1076

1077


1078

{

1079

"query": "single GraphQL query or mutation document",

1080

"variables": {

1081

"optional": "graphql variables object"

1082

}

1083

}

1084

1085

1086

1087

1088

1089

1090

1091

1092

1093

1094

not require the coding agent to read raw tokens from disk.

1095

1096

1097

1098

for debugging

1099

1100

1101

in-session.

1102

1103

Illustrative responses (equivalent payload shapes are acceptable if they preserve the same outcome):

1104

1105


1106

{"id":"<approval-id>","result":{"approved":true}}

1107

{"id":"<tool-call-id>","result":{"success":false,"error":"unsupported_tool_call"}}

1108

1109

1110

Hard failure on user input requirement:

1111

1112

1113

1114

1115

1116

1117

10.6 Timeouts and Error Mapping

1118

1119

Timeouts:

1120

1121

1122

1123

1124

1125

Error mapping (recommended normalized categories):

1126

1127

1128

1129

1130

1131

1132

1133

1134

1135

1136

1137

10.7 Agent Runner Contract

1138

1139

The Agent Runner wraps workspace + prompt + app-server client.

1140

1141

Behavior:

1142

1143

  1. Create/reuse workspace for issue.

1144

  1. Build prompt from workflow template.

1145

  1. Start app-server session.

1146

  1. Forward app-server events to orchestrator.

1147

  1. On any error, fail the worker attempt (the orchestrator will retry).

1148

1149

Note:

1150

1151

1152

1153

11. Issue Tracker Integration Contract (Linear-Compatible)

1154

1155

11.1 Required Operations

1156

1157

An implementation must support these tracker adapter operations:

1158

1159

  1. fetch_candidate_issues()

1160

1161

1162

  1. fetch_issues_by_states(state_names)

1163

1164

1165

  1. fetch_issue_states_by_ids(issue_ids)

1166

1167

1168

11.2 Query Semantics (Linear)

1169

1170

Linear-specific requirements for tracker.kind == "linear":

1171

1172

1173

1174

1175

1176

1177

1178

1179

1180

1181

1182

Important:

1183

1184

1185

fields/types required by this specification.

1186

1187

A non-Linear implementation may change transport details, but the normalized outputs must match the

1188

domain model in Section 4.

1189

1190

11.3 Normalization Rules

1191

1192

Candidate issue normalization should produce fields listed in Section 4.1.1.

1193

1194

Additional normalization details:

1195

1196

1197

1198

1199

1200

1201

11.4 Error Handling Contract

1202

1203

Recommended error categories:

1204

1205

1206

1207

1208

1209

1210

1211

1212

1213

1214

Orchestrator behavior on tracker errors:

1215

1216

1217

1218

1219

1220

11.5 Tracker Writes (Important Boundary)

1221

1222

Symphony does not require first-class tracker write APIs in the orchestrator.

1223

1224

1225

agent using tools defined by the workflow prompt.

1226

1227

1228

Human Review) rather than tracker terminal state Done.

1229

1230

the agent toolchain rather than orchestrator business logic.

1231

1232

12. Prompt Construction and Context Assembly

1233

1234

12.1 Inputs

1235

1236

Inputs to prompt rendering:

1237

1238

1239

1240

1241

1242

12.2 Rendering Rules

1243

1244

1245

1246

1247

1248

1249

12.3 Retry/Continuation Semantics

1250

1251

attempt should be passed to the template because the workflow prompt may provide different

1252

instructions for:

1253

1254

1255

1256

1257

1258

12.4 Failure Semantics

1259

1260

If prompt rendering fails:

1261

1262

1263

1264

1265

13. Logging, Status, and Observability

1266

1267

13.1 Logging Conventions

1268

1269

Required context fields for issue-related logs:

1270

1271

1272

1273

1274

Required context for coding-agent session lifecycle logs:

1275

1276

1277

1278

Message formatting requirements:

1279

1280

1281

1282

1283

1284

1285

13.2 Logging Outputs and Sinks

1286

1287

The spec does not prescribe where logs must go (stderr, file, remote sink, etc.).

1288

1289

Requirements:

1290

1291

1292

1293

1294

operator-visible warning through any remaining sink.

1295

1296

13.3 Runtime Snapshot / Monitoring Interface (Optional but Recommended)

1297

1298

If the implementation exposes a synchronous runtime snapshot (for dashboards or monitoring), it

1299

should return:

1300

1301

1302

1303

1304

1305

1306

1307

1308

1309

1310

1311

Recommended snapshot error modes:

1312

1313

1314

1315

1316

13.4 Optional Human-Readable Status Surface

1317

1318

A human-readable status surface (terminal output, dashboard, etc.) is optional and

1319

implementation-defined.

1320

1321

If present, it should draw from orchestrator state/metrics only and must not be required for

1322

correctness.

1323

1324

13.5 Session Metrics and Token Accounting

1325

1326

Token accounting rules:

1327

1328

1329

1330

1331

1332

1333

1334

payload.

1335

1336

1337

way.

1338

1339

1340

Runtime accounting:

1341

1342

1343

1344

elapsed time derived from running entries (for example started_at) when producing a

1345

snapshot/status view.

1346

1347

or cancellation/termination).

1348

1349

1350

Rate-limit tracking:

1351

1352

1353

1354

1355

13.6 Humanized Agent Event Summaries (Optional)

1356

1357

Humanized summaries of raw agent protocol events are optional.

1358

1359

If implemented:

1360

1361

1362

1363

`

The reference implementation is written in Elixir—because when code is effectively free, you can finally pick languages for their strengths, like Elixir's concurrency—but the core idea can be expressed in a simple Markdown document. We encourage you to point your favorite coding agent at the spec and have it implement its own version.

The first version of Symphony was just a Codex session running in tmux, polling Linear and spawning sub-agents for new tasks. It worked, but it wasn’t particularly reliable. The second version lived inside our main project repository, which was built with agents in mind. We had already built the agent harness to give agents the skills and context to do high quality work in this repo, so Symphony simply connects it all.

Once the basic functionality existed, we used Symphony to build Symphony.

When we internally demoed the system managing tasks and attaching its proof-of-work video, the reaction was overwhelmingly positive: our Symphony project channel grew, and teams across the organization started using it organically. Internal product market fit is a prerequisite for launching externally at OpenAI. Based on the usage we saw at OpenAI, it became clear we should share Symphony beyond company walls.

So we extracted the idea into a standalone SPEC.md and asked Codex to implement it. For the reference implementation, we chose Elixir, a relatively niche language with excellent primitives for orchestrating and supervising concurrent processes. Codex built the Elixir implementation in one shot, and we kept iterating on both spec and implementation from there. To polish the spec, we even asked Codex to implement it in several other languages—TypeScript, Go, Rust, Java, Python—and use the results to identify ambiguities and simplify the system. It succeeded in every language.

Through the process of building Symphony, we removed a lot of incidental complexity, like dependencies on specific repositories or Linear MCP. Symphony no longer depends on our internal repositories or workflows. The core approach became simple:

For every open task, guarantee that an agent is running in its own workspace.

In addition to helping with the active work, the development workflow is now something agents know and follow. The development workflow—work on an issue, check out a repo, put it in progress so the PM knows it's being worked on, add the PR, move it to the Review status, attach videos, etc.—is now captured in a simple WORKFLOW.md file. All of this is a process that humans followed, but it was never documented. Rather than relying on this implicit set of steps, we now document it, and Symphony ensures agents follow it. This lets us build agents that work alongside us. If we decide that agents should also attach self-reflection to finished work, we'll add that to the WORKFLOW.md, and Symphony will guide the agents to that step.

We also got to use Codex in app server mode⁠(opens in a new window), a built-in headless mode for Codex. This mode allowed us to run Codex and talk to it programmatically via a well documented JSON-RPC API for things like starting a thread or reacting to turns. It’s more convenient and scalable than trying to interact with Codex via CLI or live tmux sessions.

Codex App Server was a perfect fit for our use case: we take advantage of the harness Codex provides while having knobs and hooks to plug into. For example, to avoid exposing the Linear access token to subagents, we use dynamic tool calls⁠(opens in a new window) to expose the raw linear_graphql function that executes arbitrary requests against Linear, without relying on MCP or exposing the access token to containers.

What’s next

Symphony is an intentionally minimal orchestration layer. We’re open sourcing it to demonstrate the power of Codex App Server when paired with different workflow tools, like Linear. As such, we don't plan to maintain Symphony as a standalone product. Think of it as a reference implementation. Similar to how many developers pointed their coding agents at the harness engineering post to scaffold their repositories, we hope you point your favorite coding agent at the Symphony spec⁠(opens in a new window) and repository⁠(opens in a new window) to build your own versions tailored to your environments.

The power comes from Codex and its app server. Symphony was a way to connect Codex to Linear, two things we already used, to solve the work management problem. As coding agents become better at reasoning and following instructions, we suspect the bottleneck at other companies will shift from writing code toward managing agentic work, too. The exciting part is that the barrier to experimenting with these coding agent systems is now surprisingly low. You can just build things with Codex.

Community shoutouts

We're thrilled to see the engineering community using Symphony in the weeks since release, garnering over 15K GitHub stars⁠(opens in a new window) as of April 23.