OpenAI Symphony — 把 issue tracker 变成 coding agent 的 control plane
OpenAI 内部生产力工具团队(Alex Kotliarskyi、Victor Zhu、Zach Brock 三人署名)发的一篇工程博客。讲他们怎么从”内部 repo 全部由 Codex 写”这个实验,长出一套叫 Symphony 的 agent 编排方案,并且把它开源了。
故事起点:上一篇博客的延伸
六个月前他们做了一个当时被认为”激进”的决定——内部某个生产力工具的 repo 不允许任何人类手写代码,每一行都必须由 Codex 生成。为了让这件事能跑起来,他们重写了工程流:把 repo 改成对 agent 友好(agent-friendly),重投自动化测试和护栏(guardrails),把 Codex 当成正式队友。这部分经验已经写在他们之前那篇 harness engineering 博客里。
Codex = OpenAI 的 coding agent,可以理解为对标 Claude Code / Cursor 的 OpenAI 自家产品。
第一篇博客解决的是”让 agent 把活干好”。这一篇要解决的是下一个瓶颈:context switching(上下文切换)——人开始管不动这群 agent 了。
真正的瓶颈是人的注意力
随着 agentic 工作量上升,每个工程师典型的一天变成:开几个 Codex session、派活、看输出、纠偏、再派活,循环。实测下来,一个人最多能舒服地同时管 3 到 5 个 session,再多就开始忘记哪个 session 在做什么、在终端之间反复跳来纠偏 agent、调一个跑了一半卡住的长任务。
作者一句话点题:
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.
agent 跑得很快,但系统瓶颈卡在人的注意力上。等于雇了一群极强的初级工程师,又把高级工程师拿去当微观管理者,这显然不可扩展。
视角切换:不再围着 session 转,改围着 ticket 转
他们的关键认知拐点不是技术性的,是优化目标错了:以前他们围着 coding session 和 merged PR 在优化,但 session 和 PR 只是手段,不是真正的工作单位。真正的工作单位是 deliverable——issue、task、ticket、milestone。
于是他们问自己一个问题:如果不再直接监督 agent,而是让 agent 自己从 task tracker 里拉活,会怎么样?这个问题的答案就是 Symphony。
Symphony 的核心机制
Symphony 不是一个传统意义上的”调度框架”,是一份spec(规范文档)——后面会专门讲。它做的事概念上极简:
任何一个 open task,都应该被一个 agent 接手并跑到底。
落到实现:
- 每个 Linear(一款 SaaS 项目管理工具)里 open 状态的 issue → 自动分配一个独立 workspace(工作目录)
- 每个 workspace 里跑一个 agent,一直跑到任务完成
- agent 崩了 / 卡了 → Symphony 自动重启
- 新 ticket 出现 → Symphony 接住开始干
- ticket 的状态机就是 Linear 现成的 status(Todo / In Progress / Review / Done 等等),不另造一套
一句话理解:把 Linear 的 issue tracker 当成 agent 的 control plane(控制面)。
这一步看似简单,但带来一个重要的解耦:工作和 session 解耦,工作和 PR 也解耦。一个 issue 可能产生多个 PR、跨多个 repo;也可能完全不出 PR(纯调研、纯分析)。一旦解耦了,ticket 这个工作单位就可以放大很多——他们用 Symphony 编排过完整的功能开发和基础设施迁移。
Agent 自己生成 task tree(DAG 依赖)
更进一步:Symphony 让 agent 自己拆任务、自己定依赖关系。
举的例子:开一个 ticket 让 agent 先去看 codebase / Slack / Notion,产出一个实施计划。批准计划后,agent 生成一棵 task 树,把工作切成阶段,并且在 task 之间标依赖。
agent 只会接没被阻塞的 task,所以执行会自然按 DAG(directed acyclic graph,有向无环图——一种依赖关系图)并行展开。原文给的具体例子:他们标记了 “React 升级” 这个 task 被 “迁移到 Vite” 阻塞——结果 agent 真的乖乖等 Vite 迁移完才开始升 React。
Vite 和 React 都是前端工具,这里只是用作”有依赖的两件事”的具体例子。
agent 自己也能 file 新 issue。在干活过程中如果发现”这里有个性能问题”或”这块该重构了”,但跟当前 ticket 不直接相关,就开一个新 ticket 扔进 Linear。很多 follow-up ticket 之后又会被 agent 接手做掉。
数据:500% 增长,附带条件
最显眼的数字:
- OpenAI 内部某些团队在 Symphony 上线头三周 landed PR 数量增长 500%
- Linear 创始人 Karri Saarinen 反馈 Symphony 发布期间 Linear workspace 创建数有明显 spike
- 开源后到 4 月 23 日为止,Symphony 在 GitHub 拿了 15K+ stars
⚠️ 注意原文措辞的限定:是”some teams”在”first three weeks”的增长,没披露基线,不构成可外推的对比指标。当成”内部体感很好”读,不当成”行业基准”读。
工作方式的二阶变化
更深层的变化作者认为比 PR 数量更重要:当工程师不再花时间监督 Codex session,代码改动的经济模型变了——每次改动的 perceived cost(感知成本)下降,因为人力不再投入到执行本身。
具体表现:
- 探索性任务变便宜:开一个 ticket 让 agent 去 prototype 一个想法、试一个 refactor、验一个 hypothesis,跑出来不喜欢就扔掉,成本接近零
- 谁能发起工作的边界扩展了:PM 和 designer 现在能直接 file feature ticket,不需要 checkout repo、不需要管 Codex session。他们写需求,回来就拿到一个 review 包,里面带功能在真实产品里跑起来的 video walkthrough
- 大 monorepo 的最后一公里被吃掉了:在像 OpenAI 这种大单一仓库里,从写完代码到 PR landed 这一段最慢最易碎——CI watching、rebase、解冲突、retry flaky check 这些杂事。Symphony 把这些 shepherd 工作(牧羊式陪着走完)也接管了,到 ticket 进入 Merging 状态时人对它能进 main 已经有高置信度。
新问题:失去了 mid-flight 的纠偏能力
权衡也暴露出来了。从”实时纠偏 agent”变成”在 ticket 层级派活”,作者承认丢掉了一个重要能力——飞行中校正。有时候 agent 干出来的东西完全偏题。
他们的应对不是回去恢复实时监督,而是把失败转化成系统性能力——加 guardrail(护栏)和 skill(技能模块),让 agent 下次不会再犯同一个错。一段时间后,他们的 harness 长出了:跑 end-to-end 测试、用 Chrome DevTools 驱动应用、管 QA smoke test 等能力,并且显著加强了文档——把”什么算做对了”写清楚。
最关键的认知调整:从 state machine 到 objective
这一段是全文最值得记的工程教训。
他们早期把 agent 当成 state machine(状态机)里的固定节点——这个状态触发那个 transition,那个 transition 调用这个 agent。这种思路扛不住模型变强:模型一旦能解决更大的问题,他们之前给它框的盒子就太小了。
具体例子:早期他们只让 Codex 实现 task。但 Codex 完全可以创建多个 PR、读 review feedback 并响应、关掉过期 PR、拉”已完成 vs 已弃置 task”的报告——这些都远超 “实现 task” 这个最初的盒子。
最终他们的转向是:
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.
从”严格 transition”切到”给目标”,类比好经理给下属布置目标而不是布置每一步动作。模型的力量来自推理能力——给工具、给上下文,让它自己做。 这句话作者直接说成了文章的口号。
Symphony 实质上只是一份 SPEC.md
打开 Symphony 的开源 repo,第一眼看到的是:Symphony 技术上就是一份 SPEC.md 文件——对问题的定义和预期解的描述。他们没有写一个复杂的监督系统,而是把问题和解定义清楚了,给 agent 高层指导,让 agent 自己去实现具体的 service。
SPEC.md 是 language-agnostic(不绑定语言)的服务规范。原文用了非常长的篇幅把整份 spec 贴了出来(几乎占了博客一半篇幅)——核心信息其实可以浓缩成两层结构:
1. 这个服务到底要解决什么(Problem Statement)
一个长跑的自动化服务,持续从 issue tracker 读活,给每个 issue 起一个隔离的 workspace,跑一个 coding agent session 在里面干。它解决四个运营问题:
- 把”执行 issue”变成可重复的守护进程流程,而不是手写脚本
- 隔离 agent 执行(每个 issue 一个工作目录,agent 命令只在自己的目录里跑)
- 把 workflow policy 放进 repo(用
WORKFLOW.md),团队可以版本化它 - 提供足够的可观测性来运维和 debug 多个并发 agent run
2. 什么不在范围内(Non-Goals)
刻意不做的事情清单(同样重要):
- 不做 rich web UI、不做多租户控制面
- 不规定具体的 dashboard 或 terminal UI 实现
- 不是通用 workflow 引擎、不是分布式 job scheduler
- 不内置”怎么编辑 ticket / PR / 评论”这种业务逻辑——这些放在 workflow prompt 和 agent tooling 里
- 不强制特定的 sandbox 或审批策略
抽象分层(这是 spec 给 porting 的人的指引——想自己实现一个 Symphony?按这几层切):
| 层 | 内容 |
|---|---|
| Policy Layer | repo 里的 WORKFLOW.md prompt,团队特定规则 |
| Configuration Layer | front matter 解析成 typed config |
| Coordination Layer | orchestrator 主循环,issue 资格判定、并发、重试、reconciliation |
| Execution Layer | 文件系统 lifecycle、workspace 准备、coding-agent 子进程协议 |
| Integration Layer | Linear adapter 一类的 tracker API 适配 |
| Observability Layer | 日志 + 可选状态展示 |
整份 spec 还细到给 issue / workspace / run attempt / live session / retry entry 等数据结构的字段定义、命名规则(比如 workspace_key 怎么从 identifier sanitize 出来)、Liquid 风格 prompt template 的渲染规则(unknown variable / unknown filter 必须 fail rendering)。信息很密但不需要逐字读——要实现 Symphony 时再当查找文档查就行。
WORKFLOW.md:把隐性流程文档化
Symphony 体系里第二份关键文件是
WORKFLOW.md,由各团队自己 own,跟代码一起进 repo
版本化。
这件事概念上很重要:以前团队的开发流程——“接 issue → checkout repo → 把 status 改成 In Progress → PR 关联回 issue → 改成 Review → 附上 video”等等——是一套人在 follow 但从来没文档化过的隐性步骤。Symphony 强迫团队把这套流程显式写进 WORKFLOW.md,然后 agent 就 follow 这份文档干。如果哪天团队决定”完成的活也要附上 self-reflection”,改 WORKFLOW.md 就行。
把隐性人类流程显式化变成 prompt 这件事本身价值就很大——以前文档是给新人看的、可读可不读;现在文档是给 agent 执行的,没文档就没工作流。
Codex App Server 与 dynamic tool calls
技术细节里两个值得记的点:
Codex App Server(headless 模式):Codex 自带的一个 headless(无界面)模式,对外暴露 JSON-RPC API(一种 JSON 格式的远程过程调用协议),可以编程式启动一个 thread、对每个 turn 做反应。比起去 hack CLI 或 tmux session 里读输出,这个模式更稳更好接。Symphony 完全建立在这个之上。
dynamic tool calls(动态工具调用):他们想让 agent
能用 Linear,但不希望把 Linear access token 暴露给 subagent
的容器。做法是用 dynamic tool calls 暴露一个原始的
linear_graphql 函数,agent 调这个函数发 GraphQL 请求,token
留在主进程里——绕开了 MCP(Anthropic 提的 Model Context Protocol,一种
agent 工具调用规范),也避免了 token 泄漏。
这是一个很实用的安全模式:不把 capability 整体下放给 agent,只下放发起请求的能力。
开源定位:reference implementation
OpenAI 明确表态:
- Symphony 是一个故意做得最小的编排层
- 他们开源的目的是展示 Codex App Server + 第三方 workflow 工具(如 Linear)配合的能力
- 他们不打算把 Symphony 当独立产品维护——把它当 reference implementation(参考实现)就好
- 类比是上一篇 harness engineering 博客:很多开发者把那篇 post 喂给自己的 coding agent,然后让 agent 帮自己 scaffold(搭框架)。Symphony 也希望被这样用——把 spec 喂给你的 coding agent,让 agent 帮你做一个适配你环境的 Symphony
社区已经动起来了:
@adacaleeeb让 agent 在 Elixir ERP 应用里照 Symphony repo 仿了一份@_junhoyeo用 Go + charmcli stack 写了一个 TUI 版@sapsaldog84fork 了一份兼容 Anthropic 的 Claude Code + GitHub Issues,brew 可装@mksglu用 Claude Code Agent SDK 重建了一份叫 hatice
拧巴的地方 / 我的怀疑
读完以后有几处值得标一下:
“500% landed PRs”没披露基线——
some teams+first three weeks这种限定语很滑。从 1 → 5 也是 500%,从 100 → 500 也是 500%。读这段时把它当作”内部体感强烈正向”读,不当作可外推的工程基准。“engineer 在 cabin 用手机 Linear 改 3 个东西”这种叙事——读起来像 marketing copy,技术含量低。可以理解为想说”工作可以脱离开发环境发起”,但跟博客其余部分的工程严肃性有落差。
“我们不维护成产品”vs”15K stars + OpenAI 公司层面 push” 这两件事放一起略微拧巴——既想要广泛影响力又想撇清产品责任。如果你打算用 Symphony,这个边界要自己拿主意:你是在用一份长期会被维护的工具,还是一份永远停在 v1 的 spec?
state machine vs objectives 的二分有点过度浪漫化——实际工程里两者通常是混合的(高层 objective + 低层 state machine)。原文这段读起来像把”我们以前做错了”写成宣言,但具体边界(哪些情况该用 strict transition、哪些该给 objective)没展开。
一句话能立住的金句
The power of models comes from their ability to reason, so give them tools and context and let them cook.
这是这篇文章如果只能记一句的那一句。