← All Articles

OpenAI Symphony — 把 issue tracker 变成 coding agent 的 control plane

Alex Kotliarskyi, Victor Zhu, Zach Brock · Original

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 的 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% 增长,附带条件

最显眼的数字:

⚠️ 注意原文措辞的限定:是”some teams”在”first three weeks”的增长,没披露基线,不构成可外推的对比指标。当成”内部体感很好”读,不当成”行业基准”读。

工作方式的二阶变化

更深层的变化作者认为比 PR 数量更重要:当工程师不再花时间监督 Codex session,代码改动的经济模型变了——每次改动的 perceived cost(感知成本)下降,因为人力不再投入到执行本身。

具体表现:

新问题:失去了 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 在里面干。它解决四个运营问题:

2. 什么不在范围内(Non-Goals)

刻意不做的事情清单(同样重要):

抽象分层(这是 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 明确表态:

社区已经动起来了:

拧巴的地方 / 我的怀疑

读完以后有几处值得标一下:

  1. “500% landed PRs”没披露基线——some teams + first three weeks 这种限定语很滑。从 1 → 5 也是 500%,从 100 → 500 也是 500%。读这段时把它当作”内部体感强烈正向”读,不当作可外推的工程基准。

  2. “engineer 在 cabin 用手机 Linear 改 3 个东西”这种叙事——读起来像 marketing copy,技术含量低。可以理解为想说”工作可以脱离开发环境发起”,但跟博客其余部分的工程严肃性有落差。

  3. “我们不维护成产品”vs”15K stars + OpenAI 公司层面 push” 这两件事放一起略微拧巴——既想要广泛影响力又想撇清产品责任。如果你打算用 Symphony,这个边界要自己拿主意:你是在用一份长期会被维护的工具,还是一份永远停在 v1 的 spec?

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

这是这篇文章如果只能记一句的那一句。