OpenAI:官网动态(RSS · 排除企业/客户案例)
精选
70AI 编辑部评分,满分 100

一个用于编排的开源规范:Symphony

2026-04-27 08:00· 100天前
AI 导读

Symphony 是一个用于 Codex 编排的开源规范,能够将问题跟踪器转化为持续运行的智能体系统。该系统通过自动化任务协调与执行,显著提升工程团队的产出效率,同时减少开发者在不同任务间频繁切换带来的认知负担。其核心在于以标准化、可扩展的方式,将日常开发流程转化为由智能体持续驱动的工作流。

推荐理由

OpenAI 把 Codex 的编排层抽成开源规范,等于告诉所有做 coding agent 的团队,底层调度逻辑不用自己造轮子了。做 AI 编程工具的值得花半小时看架构思路。

正文 · AI 翻译

六个月前,在开发一款内部效率工具时,我们的团队做出了一个当时颇具争议的决定:我们要构建一个完全没有人类编写代码的代码库。项目仓库中的每一行代码都必须由 Codex 生成。

为了实现这一目标,我们从零开始重新设计了工程工作流程。我们构建了一个对智能体友好的代码仓库,在自动化测试和防护措施上投入了大量精力,并将 Codex 视为一名正式的团队成员。我们在之前关于驾驭工程(harness engineering)的博文中记录了这段历程。

这个方案奏效了,但随后我们遇到了下一个瓶颈:上下文切换。

为了解决这个新问题,我们构建了一个名为 Symphony 的系统。Symphony 是一个智能体编排器,它将 Linear 这类项目管理看板转变为编码智能体的控制面板。每个待处理的任务都会分配一个智能体,这些智能体会持续运行,而人类则负责审查结果。

这篇文章将解释我们如何创建 Symphony——它使某些团队的落地拉取请求(pull request)数量增加了 500%——以及如何利用它将你自己的问题追踪器转变为一个始终在线的智能体编排器。

交互式编码智能体的天花板

即便编码智能体变得越来越易用,无论是通过网页应用还是命令行界面访问,它们本质上仍然是交互式工具。

随着 OpenAI 内部智能体工作规模的扩大,我们发现了一种新的负担。每位工程师会打开几个 Codex 会话,分配任务,审查输出,引导智能体,然后重复这一过程。实际上,大多数人一次能舒适地管理三到五个会话,之后上下文切换就会变得令人痛苦。超过这个数量,生产力就会下降。我们会忘记哪个会话在做什么,在多个终端之间跳转以引导智能体回到正轨,并调试那些中途停滞的长时间运行任务。

智能体速度很快,但我们的系统瓶颈在于:人类的注意力。我们实际上建立了一个由极其能干的初级工程师组成的团队,然后让人类工程师去对他们进行微观管理。这种方式无法规模化。

视角的转变

我们意识到自己一直在优化错误的方向。我们围绕编码会话和合并的 PR 来构建系统,但 PR 和会话本质上只是达成目的的手段。软件工作流很大程度上是按交付物来组织的:问题、任务、工单、里程碑。

于是我们问自己:如果我们不再直接监督智能体,而是让它们从任务追踪系统中拉取工作,会发生什么?

这个想法演变成了 Symphony,一份作为监督者来编排智能体工作的书面规范。

将问题追踪系统转变为智能体编排器

Symphony 始于一个简单的概念:任何未完成的任务都应该由智能体接手并完成。我们没有在多个标签页中管理 Codex 会话,而是将问题追踪系统作为控制平面。

在这种设置下,每个未关闭的 Linear 问题都映射到一个专用的智能体工作空间。Symphony 持续监控任务看板,确保每个活跃任务都有一个智能体持续运行,直到任务完成。如果智能体崩溃或停滞,Symphony 会重启它。如果出现新任务,Symphony 会接手并开始组织工作。

我们基于工单状态构建工作流,将任务管理器 Linear 用作一个状态机。

在实践中,Symphony 将工作与会话和拉取请求解耦。有些问题会在多个仓库中产生多个 PR;另一些则是纯粹的调查或分析,从不触及代码库。

一旦工作以这种方式被抽象化,工单就可以代表更大规模的工作单元。

我们经常使用 Symphony 来编排复杂的功能和基础设施迁移。例如,我们可能会提交一个任务,要求智能体分析代码库、Slack 或 Notion,并生成一份实施计划。一旦我们对计划满意,智能体就会生成一个任务树,将工作分解为多个阶段,并定义任务之间的依赖关系。

智能体只开始处理未被阻塞的任务,因此执行过程会针对这个 DAG(一系列执行步骤)自然且最优地并行展开。例如,我们将 React 升级标记为依赖于迁移到 Vite。正如预期的那样,智能体只有在迁移到 Vite 完成后才开始升级 React。

智能体也可以自行创建工作任务。在实施或审查过程中,它们常常会发现当前任务范围之外的改进点:比如性能问题、重构机会或更优的架构。每当这种情况出现,它们就会直接提交一个新的工单,供我们后续评估和排期——其中许多后续任务也会由智能体接手处理。在我们监督这一流程的同时,智能体始终保持条理清晰,推动工作持续向前。

这种工作方式极大地降低了启动模糊任务时的认知成本。如果智能体做错了什么,那仍然是有用的信息,而我们的成本几乎为零。我们可以非常低成本地为智能体提交工单,让它去进行原型探索,然后丢弃任何我们不喜欢的探索结果。

由于编排器运行在开发盒上且从不休眠,我们可以从任何地方添加任务,并知道会有智能体接手处理。例如,我们团队的一名工程师曾在一间舒适的小木屋里,通过糟糕的 WiFi 用手机上的 Linear 应用完成了三项重大变更。

这种工作方式带来了探索活动的增加

在观察使用 Symphony 的效果时,最明显的变化是产出。在 OpenAI 内部的一些团队中,我们看到前三个星期内合并的 PR 数量增加了 500%。在 OpenAI 之外,Linear 创始人 Karri Saarinen 也强调了随着我们发布 Symphony,创建工作区的数量出现了激增。然而,更深层次的转变在于团队对工作本身的思考方式。

当我们的工程师不再需要花时间监督 Codex 会话时,代码变更的经济性就彻底改变了。每次变更的感知成本下降,因为我们不再投入人力去驱动实施过程本身。

这改变了我们的行为。在 Symphony 中启动探索性任务变得轻而易举。尝试一个想法,探索一次重构,测试一个假设,然后只保留那些看起来有希望的结果。

这也拓宽了能够发起工作的人员范围。我们的产品经理和设计师现在可以直接将功能请求提交到 Symphony 中。他们不需要检出代码仓库或管理 Codex 会话。他们只需描述功能需求,就能收到一份审查包,其中包含该功能在实际产品中运行的视频演示。

Symphony 在大型单体仓库(比如 OpenAI 内部使用的这种)中同样表现出色,这类仓库中,PR 落地的最后一公里往往既缓慢又脆弱。该系统会监控 CI,在需要时自动变基,解决冲突,重试不稳定的检查项,并全程引导变更通过流水线。当工单进入合并阶段时,我们有很高的信心,变更无需人工看护就能顺利进入主分支。

实施 Symphony 之后,我们将更多工作委托给智能体,从而专注于更困难、更具探索性的任务。

进步伴随着新的、不同的问题。

在如此高的层级上运作需要权衡取舍。当我们从交互式引导智能体转向在工单层面分配任务时,我们失去了在任务执行过程中不断给予提示并在必要时纠正方向的能力。有时智能体产出的结果完全偏离了目标。这反而很有用——那些失败暴露了系统中的漏洞,帮助我们使其更加健壮。

我们没有手动修补结果,而是增加了护栏和技能,以便智能体下次能够成功。随着时间的推移,这促使我们为工具框架增加了新能力,例如运行端到端测试、通过 Chrome DevTools 驱动应用,以及管理 QA 冒烟测试。我们显著改进了文档,并明确了什么才是好的表现。

并非所有任务都适合 Symphony 的工作方式。有些问题仍然需要工程师直接使用交互式 Codex 会话,尤其是那些模糊不清的问题,或者需要强大判断力和专业知识的任务。在实践中,这些通常是我们工程师最愿意花时间处理、也最有趣的任务。

区别在于,Symphony 能够处理大部分常规实现工作。这让工程师可以一次专注于一个难题,而不是在多个小任务之间不断切换上下文。

我们还了解到,将智能体视为状态机中的刚性节点效果并不理想。模型会变得更智能,能够解决比我们试图将其塞入的框架更大的问题。我们早期版本的智能体工作只要求 Codex 执行任务。这种方法被证明过于局限。Codex 完全有能力创建多个 PR,以及阅读审查反馈并对其进行处理。因此,我们为它提供了工具——gh CLI、读取 CI 日志的技能等——现在我们可以要求 Codex 做更多事情,比如关闭旧的 PR,或者拉取已完成与已放弃工作的对比报告。这些类型的任务完全超出了最初的功能实现框架。

因此,我们最终转向为智能体设定目标,而不是严格的转换规则,这很像一位优秀的管理者会为团队中的直接下属分配一个目标。模型的能力来自于它们的推理能力,所以给它们工具和上下文,然后让它们自由发挥。

使用 Symphony 构建 Symphony

当你打开 Symphony 仓库时,首先会注意到的是,Symphony 在技术上只是一个 SPEC.md 文件——一个对问题和预期解决方案的定义。我们没有构建一个复杂的监督系统,而是定义了问题和预期的解决方案,为智能体提供高层级的指导。

Markdown

1# Symphony 服务规范 2状态:草案 v1(语言无关) 4目的:定义一种编排编码智能体以完成项目工作的服务。 6## 1. 问题陈述 8Symphony 是一个长期运行的自动化服务,它持续从问题跟踪器(本规范版本中为 Linear)读取工作,为每个问题创建隔离的工作空间,并在该工作空间内为该问题运行一个编码智能体会话。 10该服务解决了四个运营问题: 12- 将问题执行转变为可重复的守护进程工作流,而非手动脚本。 13- 将智能体执行隔离在按问题划分的工作空间中,使智能体命令仅在该工作空间目录内运行。 14- 将工作流策略保留在仓库内(WORKFLOW.md),使团队能够将智能体提示词和运行时设置与代码一起进行版本控制。 15- 提供足够的可观测性,以操作和调试多个并发的智能体运行。 17实现方案应明确记录其信任与安全策略。本规范不要求单一的审批、沙箱或操作员确认策略;某些实现可能针对高信任配置的受信任环境,而其他实现可能需要更严格的审批或沙箱机制。 19重要边界: 21- Symphony 是一个调度器/运行器和跟踪器读取器。 22- 工单写入(状态转换、评论、PR 链接)通常由编码智能体使用工作流/运行时环境中可用的工具执行。 23- 成功的运行可能以工作流定义的交接状态(例如“人工审核”)结束,而不一定是“已完成”。 25## 2. 目标与非目标 27### 2.1 目标 29- 按固定周期轮询问题跟踪器,并以有界并发度分派工作。 30- 维护一个单一的权威编排器状态,用于分派、重试和协调。 31- 创建确定性的按问题工作空间,并在多次运行之间保留它们。 32- 当问题状态变化使其不符合条件时,停止正在进行的运行。 33- 通过指数退避从瞬时故障中恢复。 34- 从仓库拥有的 WORKFLOW.md 契约加载运行时行为。 35- 暴露操作员可见的可观测性(至少是结构化日志)。 36- 支持无需持久化数据库的重启恢复。 38### 2.2 非目标 40- 丰富的 Web UI 或多租户控制平面。 41- 规定特定的仪表盘或终端 UI 实现。 42- 通用工作流引擎或分布式任务调度器。 43- 内置的编辑工单、PR 或评论的业务逻辑。(该逻辑存在于工作流提示词和智能体工具中。) 44- 强制要求超出编码智能体和主机操作系统所提供的强沙箱控制。 45- 为所有实现强制要求单一的默认审批、沙箱或操作员确认姿态。 47## 3. 系统概述 49### 3.1 主要组件 511. 工作流加载器 52 - 读取 WORKFLOW.md。 53 - 解析 YAML 前置元数据和提示词主体。 54 - 返回 {config, prompt_template}。 562. 配置层 57 - 为工作流配置值提供类型化的 getter 方法。 58 - 应用默认值和环境变量间接引用。 59 - 执行编排器在分派前使用的验证。 613. 问题跟踪器客户端 62 - 获取处于活动状态的候选问题。 63 - 获取特定问题 ID 的当前状态(协调)。 64 - 在启动清理期间获取处于终态的问题。 65 - 将跟踪器负载标准化为稳定的问题模型。 674. 编排器 68 - 拥有轮询节拍。 69 - 拥有内存中的运行时状态。 70 - 决定哪些问题需要分派、重试、停止或释放。 71 - 跟踪会话指标和重试队列状态。 735. 工作空间管理器 74 - 将问题标识符映射到工作空间路径。 75 - 确保按问题划分的工作空间目录存在。 76 - 运行工作空间生命周期钩子。 77 - 清理处于终态问题的工作空间。 796. 智能体运行器 80 - 创建工作空间。 81 - 从问题和工作流模板构建提示词。 82 - 启动编码智能体应用服务器客户端。 83 - 将智能体更新流式传输回编排器。 857. 状态界面(可选) 86 - 呈现人类可读的运行时状态(例如终端输出、仪表盘或其他面向操作员的视图)。 888. 日志记录 89 - 将结构化运行时日志发送到一个或多个配置的目标端。 91### 3.2 抽象层级 93Symphony 在保持以下分层时最易于移植: 951. 策略层(仓库定义) 96 - WORKFLOW.md 提示词主体。 97 - 团队特定的工单处理、验证和交接规则。 992. 配置层(类型化 getter) 100 - 将前置元数据解析为类型化的运行时设置。 101 - 处理默认值、环境令牌和路径标准化。 1033. 协调层(编排器) 104 - 轮询循环、问题资格、并发、重试、协调。 1064. 执行层(工作空间 + 智能体子进程) 107 - 文件系统生命周期、工作空间准备、编码智能体协议。 1095. 集成层(Linear 适配器) 110 - 跟踪器数据的 API 调用和标准化。 1126. 可观测性层(日志 + 可选状态界面) 113 - 操作员对编排器和智能体行为的可见性。 115### 3.3 外部依赖 117- 问题跟踪器 API(本规范版本中为 Linear,对应 tracker.kind: linear)。 118- 用于工作空间和日志的本地文件系统。 119- 可选的工作空间填充工具(例如 Git CLI,如果使用)。 120- 支持通过 stdio 进行类似 JSON-RPC 的应用服务器模式的编码智能体可执行文件。 121- 用于问题跟踪器和编码智能体的主机环境认证。 123## 4. 核心领域模型 125### 4.1 实体 127#### 4.1.1 问题 129由编排、提示词渲染和可观测性输出使用的标准化问题记录。 131字段: 133- id(字符串) 134 - 稳定的跟踪器内部 ID。 135- identifier(字符串) 136 - 人类可读的工单键(例如:ABC-123)。 137- title(字符串) 138- description(字符串或 null) 139- priority(整数或 null) 140 - 在分派排序中,数字越小优先级越高。 141- state(字符串) 142 - 当前跟踪器状态名称。 143- branch_name(字符串或 null) 144 - 跟踪器提供的分支元数据(如果可用)。 145- url(字符串或 null) 146- labels(字符串列表) 147 - 标准化为小写。 148- blocked_by(阻塞器引用列表) 149 - 每个阻塞器引用包含: 150 - id(字符串或 null) 151 - identifier(字符串或 null) 152 - state(字符串或 null) 153- created_at(时间戳或 null) 154- updated_at(时间戳或 null) 156#### 4.1.2 工作流定义 158解析后的 WORKFLOW.md 负载: 160- config(映射) 161 - YAML 前置元数据根对象。 162- prompt_template(字符串) 163 - 前置元数据之后的 Markdown 主体,已修剪。 165#### 4.1.3 服务配置(类型化视图) 167从 WorkflowDefinition.config 加上环境解析派生的类型化运行时值。 169示例: 171- 轮询间隔 172- 工作空间根目录 173- 活动状态和终态问题状态 174- 并发限制 175- 编码智能体可执行文件/参数/超时 176- 工作空间钩子 178#### 4.1.4 工作空间 180分配给一个问题标识符的文件系统工作空间。 182字段(逻辑): 184- path(工作空间路径;当前运行时通常使用绝对路径,但如果配置中不包含路径分隔符,也可以使用相对根目录) 185- workspace_key(经过清理的问题标识符) 186- created_now(布尔值,用于控制 after_create 钩子的执行) 188#### 4.1.5 运行尝试 190针对一个问题的一次执行尝试。 192字段(逻辑): 194- issue_id 195- issue_identifier 196- attempt(整数或 null,首次运行为 null,重试/继续运行 >=1) 197- workspace_path 198- started_at 199- status 200- error(可选) 202#### 4.1.6 实时会话(智能体会话元数据) 204在编码智能体子进程运行期间跟踪的状态。 206字段: 208- session_id(字符串) 209- thread_id(字符串) 210- turn_id(字符串) 211- codex_app_server_pid(字符串或 null) 212- last_codex_event(字符串/枚举或 null) 213- last_codex_timestamp(时间戳或 null) 214- last_codex_message(摘要负载) 215- codex_input_tokens(整数) 216- codex_output_tokens(整数) 217- codex_total_tokens(整数) 218- last_reported_input_tokens(整数) 219- last_reported_output_tokens(整数) 220- last_reported_total_tokens(整数) 221- turn_count(整数) 222 - 在当前工作器生命周期内启动的编码智能体轮次数量。 224#### 4.1.7 重试条目 226为问题安排的重试状态。 228字段: 230- issue_id 231- identifier(用于状态界面/日志的尽力而为的人类可读 ID) 232- attempt(整数,重试队列中从 1 开始) 233- due_at_ms(单调时钟时间戳) 234- timer_handle(运行时特定的定时器引用) 235- error(字符串或 null) 237#### 4.1.8 编排器运行时状态 239由编排器拥有的单一权威内存状态。 241字段: 243- poll_interval_ms(当前有效的轮询间隔) 244- max_concurrent_agents(当前有效的全局并发限制) 245- running(映射 issue_id -> 运行条目) 246- claimed(已保留/运行中/重试中的问题 ID 集合) 247- retry_attempts(映射 issue_id -> RetryEntry) 248- completed(问题 ID 集合;仅用于记账,不用于分派门控) 249- codex_totals(聚合令牌数 + 运行时秒数) 250- codex_rate_limits(来自智能体事件的最新速率限制快照) 252### 4.2 稳定标识符与标准化规则 254- 问题 ID 255 - 用于跟踪器查找和内部映射键。 256- 问题标识符 257 - 用于人类可读的日志和工作空间命名。 258- 工作空间键 259 - 从 issue.identifier 派生,将任何不在 [A-Za-z0-9.-] 范围内的字符替换为 _。 260 - 使用清理后的值作为工作空间目录名。 261- 标准化问题状态 262 - 比较状态前先转换为小写。 263- 会话 ID 264 - 由编码智能体的 thread_id 和 turn_id 以 - 连接组成。 266## 5. 工作流规范(仓库契约) 268### 5.1 文件发现与路径解析 270工作流文件路径优先级: 2721. 显式的应用程序/运行时设置(由 CLI 启动路径设置)。 2732. 默认值:当前进程工作目录中的 WORKFLOW.md。 275加载器行为: 277- 如果无法读取文件,返回 missing_workflow_file 错误。 278- 工作流文件预期由仓库拥有并进行版本控制。 280### 5.2 文件格式 282WORKFLOW.md 是一个带有可选 YAML 前置元数据的 Markdown 文件。 284设计说明: 286- WORKFLOW.md 应足够自包含,以描述和运行不同的工作流(提示词、运行时设置、钩子和跟踪器选择/配置),而无需带外服务特定的配置。 288解析规则: 290- 如果文件以 --- 开头,则将直到下一个 --- 的行解析为 YAML 前置元数据。 291- 剩余行成为提示词主体。 292- 如果缺少前置元数据,则将整个文件视为提示词主体,并使用空配置映射。 293- YAML 前置元数据必须解码为映射/对象;非映射的 YAML 视为错误。 294- 提示词主体在使用前进行修剪。 296返回的工作流对象: 298- config:前置元数据根对象(不嵌套在 config 键下)。 299- prompt_template:修剪后的 Markdown 主体。 301### 5.3 前置元数据模式 303顶层键: 305- tracker 306- polling 307- workspace 308- hooks 309- agent 310- codex 312为向前兼容,应忽略未知键。 314注意: 316- 工作流前置元数据是可扩展的。可选扩展可以定义额外的顶层键(例如 server),而无需更改上述核心模式。 318- 扩展应记录其字段模式、默认值、验证规则以及更改是动态应用还是需要重启。 319- 常见扩展:server.port(整数)启用第 13.7 节中描述的可选 HTTP 服务器。 321#### 5.3.1 tracker(对象) 323字段: 325- kind(字符串) 326 - 分派必需。 327 - 当前支持的值:linear 328- endpoint(字符串) 329 - 当 tracker.kind == "linear" 时的默认值:https://api.linear.app/graphql 330- api_key(字符串) 331 - 可以是字面令牌或 $VAR_NAME。 332 - 当 tracker.kind == "linear" 时的规范环境变量:LINEAR_API_KEY。 333 - 如果 $VAR_NAME 解析为空字符串,则将密钥视为缺失。 334- project_slug(字符串) 335 - 当 tracker.kind == "linear" 时分派必需。 336- active_states(字符串列表) 337 - 默认值:Todo, In Progress 338- terminal_states(字符串列表) 339 - 默认值:Closed, Cancelled, Canceled, Duplicate, Done 341#### 5.3.2 polling(对象) 343字段: 345- interval_ms(整数或字符串整数) 346 - 默认值:30000 347 - 更改应在运行时重新应用,并影响未来的节拍调度,无需重启。 349#### 5.3.3 workspace(对象) 351字段: 353- root(路径字符串或 $VAR) 354 - 默认值:<系统临时目录>/symphony_workspaces 355 - ~ 和包含路径分隔符的字符串会被展开。 356 - 不带路径分隔符的裸字符串按原样保留(允许相对根目录,但不推荐)。 358#### 5.3.4 hooks(对象) 360字段: 362- after_create(多行 shell 脚本字符串,可选) 363 - 仅在工作空间目录新创建时运行。 364 - 失败会中止工作空间创建。 365- before_run(多行 shell 脚本字符串,可选) 366 - 在每次智能体尝试之前、工作空间准备之后、启动编码智能体之前运行。 367 - 失败会中止当前尝试。 368- after_run(多行 shell 脚本字符串,可选) 369 - 在每次智能体尝试之后(成功、失败、超时或取消),且工作空间存在时运行。 370 - 失败会被记录但忽略。 371- before_remove(多行 shell 脚本字符串,可选) 372 - 在工作空间删除之前,如果目录存在则运行。 373 - 失败会被记录但忽略;清理仍会继续。 374- timeout_ms(整数,可选) 375 - 默认值:60000 376 - 适用于所有工作空间钩子。 377 - 非正值应视为无效并回退到默认值。 378 - 更改应在运行时重新应用,以影响未来的钩子执行。 380#### 5.3.5 agent(对象) 382字段: 384- max_concurrent_agents(整数或字符串整数) 385 - 默认值:10 386 - 更改应在运行时重新应用,并影响后续的分派决策。 387- max_retry_backoff_ms(整数或字符串整数) 388 - 默认值:300000(5 分钟) 389 - 更改应在运行时重新应用,并影响未来的重试调度。 390- max_concurrent_agents_by_state(映射 state_name -> 正整数) 391 - 默认值:空映射。 392 - 状态键在查找时标准化(小写)。 393 - 无效条目(非正数或非数字)将被忽略。 395#### 5.3.6 codex(对象) 397字段: 399对于 Codex 拥有的配置值,例如 approval_policy、thread_sandbox 和 turn_sandbox_policy,支持的值由目标 Codex 应用服务器版本定义。实现者应将其视为透传的 Codex 配置值,而不是依赖本规范中手动维护的枚举。要检查已安装的 Codex 模式,请运行 codex app-server generate-json-schema --out <dir> 并检查 v2/ThreadStartParams.json 和 v2/TurnStartParams.json 中引用的相关定义。如果实现希望进行更严格的启动检查,可以在本地验证这些字段。 401- command(字符串 shell 命令) 402 - 默认值:codex app-server 403 - 运行时通过在工作空间目录中执行 bash -lc 来启动此命令。 404 - 启动的进程必须通过 stdio 使用兼容的应用服务器协议进行通信。 405- approval_policy(Codex AskForApproval 值) 406 - 默认值:实现定义。 407- thread_sandbox(Codex SandboxMode 值) 408 - 默认值:实现定义。 409- turn_sandbox_policy(Codex SandboxPolicy 值) 410 - 默认值:实现定义。 411- turn_timeout_ms(整数) 412 - 默认值:3600000(1 小时) 413- read_timeout_ms(整数) 414 - 默认值:5000 415- stall_timeout_ms(整数) 416 - 默认值:300000(5 分钟) 417 - 如果 <= 0,则禁用停滞检测。 419### 5.4 提示词模板契约 421WORKFLOW.md 的 Markdown 主体是按问题划分的提示词模板。 423渲染要求: 425- 使用严格的模板引擎(兼容 Liquid 的语义即可)。 426- 未知变量必须导致渲染失败。 427- 未知过滤器必须导致渲染失败。 429模板输入变量: 431- issue(对象) 432 - 包含所有标准化的问题字段,包括标签和阻塞器。 433- attempt(整数或 null) 434 - 首次尝试时为 null/不存在。 435 - 重试或继续运行时为整数。 437回退提示词行为: 439- 如果工作流提示

参考实现是用 Elixir 编写的——因为当代码成本几乎为零时,你终于可以根据语言的优势来选择它,比如 Elixir 的并发能力——但核心思想可以用一份简单的 Markdown 文档来表达。我们鼓励你把你最喜欢的编程智能体指向这份规范,让它实现自己的版本。

Symphony 的第一个版本只是一个运行在 tmux 中的 Codex 会话,轮询 Linear 并为新任务生成子智能体。它能工作,但不太可靠。第二个版本位于我们的主项目仓库内,该仓库在设计时就考虑到了智能体。我们已经构建了智能体框架,为智能体提供在此仓库中高质量工作所需的技能和上下文,因此 Symphony 只是将所有内容连接起来。

一旦基本功能就绪,我们就用 Symphony 来构建 Symphony。

当我们内部演示该系统管理任务并附上其工作量证明视频时,反响非常积极:我们的 Symphony 项目频道壮大了,整个组织的团队都开始自发使用它。内部产品市场契合度是在 OpenAI 外部发布产品的先决条件。根据我们在 OpenAI 内部看到的使用情况,很明显我们应该在公司外部分享 Symphony。

于是我们将这个想法提炼成一份独立的 SPEC.md 文件,并让 Codex 去实现它。对于参考实现,我们选择了 Elixir,这是一种相对小众的语言,但拥有出色的原语来编排和监督并发进程。Codex 一次性构建了 Elixir 实现,我们在此基础上不断迭代规范和实现。为了打磨规范,我们甚至让 Codex 用其他几种语言——TypeScript、Go、Rust、Java、Python——来实现它,并利用结果来识别歧义并简化系统。它在每种语言上都成功了。

在构建 Symphony 的过程中,我们消除了大量附带复杂性,例如对特定仓库或 Linear MCP 的依赖。Symphony 不再依赖于我们的内部仓库或工作流程。核心方法变得简单:

对于每个未完成的任务,确保有一个智能体在其自己的工作空间中运行。

除了协助实际工作外,开发工作流如今也成为智能体能够了解并遵循的流程。开发工作流——处理某个问题、检出代码仓库、将其标记为进行中以便项目经理知晓正在处理、添加拉取请求、将其移至“审核”状态、附上视频等——现在都记录在一个简单的 WORKFLOW.md 文件中。所有这些原本是人类遵循的流程,但从未被文档化。现在我们不再依赖这套隐式的步骤,而是将其文档化,Symphony 确保智能体遵循它。这让我们能够构建与我们并肩工作的智能体。如果我们决定智能体还应在完成的工作中附上自我反思,我们会将其添加到 WORKFLOW.md 中,Symphony 将引导智能体执行该步骤。

我们还得以在应用服务器模式下使用 Codex,这是 Codex 内置的一种无头模式。该模式允许我们运行 Codex,并通过文档完善的 JSON-RPC API 以编程方式与之交互,例如启动线程或响应对话轮次。与通过 CLI 或实时 tmux 会话与 Codex 交互相比,这种方式更便捷、更具可扩展性。

Codex 应用服务器非常适合我们的用例:我们利用 Codex 提供的框架,同时拥有可调节和可接入的控制点。例如,为了避免向子智能体暴露 Linear 访问令牌,我们使用动态工具调用来暴露原始的 linear_graphql 函数,该函数可对 Linear 执行任意请求,而无需依赖 MCP 或将访问令牌暴露给容器。

下一步计划

Symphony 是一个刻意保持精简的编排层。我们将其开源,以展示 Codex 应用服务器与不同工作流工具(如 Linear)结合时的强大能力。因此,我们不打算将 Symphony 作为独立产品来维护。请将其视为一个参考实现。就像许多开发者将他们的编码智能体指向框架工程文章来搭建代码仓库一样,我们希望你能将你最喜欢的编码智能体指向 Symphony 规范文档和代码仓库,构建适合你自己环境的定制版本。

其强大之处源于 Codex 及其应用服务器。Symphony 是一种将 Codex 与我们已使用的 Linear 连接起来的方式,用以解决工作管理问题。随着编码智能体在推理和遵循指令方面能力日益增强,我们推测其他公司的瓶颈也将从编写代码转向管理智能体工作。令人兴奋的是,如今尝试这些编码智能体系统的门槛已低得出奇。你只需用 Codex 就能构建东西。

社区致谢

我们非常高兴地看到,自发布以来的数周内,工程社区一直在使用 Symphony,截至 4 月 23 日,该项目已在 GitHub 上获得超过 15,000 颗星。

我让一个智能体在我的 Elixir ERP 应用中制作一个 OpenAI Symphony 仓库的版本。它一次性生成了一个完整的仪表盘,包含 GenServer 和自定义智能体,用于在生产环境中捕获 bug,并启动智能体进行修复和提交。还添加了一个用户请求功能,与同一系统集成。🤯🤯🤯

OpenAI 发布了 Symphony,这是一个项目级的 AI 编码智能体编排器。我用 @charmcli 栈的 TUI 实现了它的 Go 版本 🎸 github.com/junhoyeo/contr…

@marmaduke091

🎵 OpenAI 推出 Symphony “Symphony 将项目工作转化为隔离的、自主的实现运行,使团队能够管理工作,而非监督编码智能体。” 来看看吧,看起来很酷:github.com/openai/symphony

OpenAI 的 Symphony 是一个很棒的编码智能体编排器——但仅限 @OpenAI Codex。我将其分支出来,使其能与 @AnthropicAI 的 Claude Code 及 GitHub Issues 配合使用。开源,可通过 Homebrew 安装。sapsaldog.com/posts/symphony…

使用 Claude Code 运行 OpenAI Symphony

OpenAI 发布了 Symphony——一个将项目工作转化为自主实现运行的规范。管理工作,而非智能体。我借鉴了这个想法,并用 @AnthropicAI 的 Claude Code 重新构建。hatice 是我对 Symphony 规范的实现,由 Claude Code Agent SDK 驱动。

来源:OpenAI:官网动态(RSS · 排除企业/客户案例) · openai.com

一个用于编排的开源规范:Symphony

OpenAI:官网动态(RSS · 排除企业/客户案例)·2026-04-27 08:00·100天前
AI 导读

Symphony 是一个用于 Codex 编排的开源规范,能够将问题跟踪器转化为持续运行的智能体系统。该系统通过自动化任务协调与执行,显著提升工程团队的产出效率,同时减少开发者在不同任务间频繁切换带来的认知负担。其核心在于以标准化、可扩展的方式,将日常开发流程转化为由智能体持续驱动的工作流。

正文 · AI 翻译

六个月前,在开发一款内部效率工具时,我们的团队做出了一个当时颇具争议的决定:我们要构建一个完全没有人类编写代码的代码库。项目仓库中的每一行代码都必须由 Codex 生成。

为了实现这一目标,我们从零开始重新设计了工程工作流程。我们构建了一个对智能体友好的代码仓库,在自动化测试和防护措施上投入了大量精力,并将 Codex 视为一名正式的团队成员。我们在之前关于驾驭工程(harness engineering)的博文中记录了这段历程。

这个方案奏效了,但随后我们遇到了下一个瓶颈:上下文切换。

为了解决这个新问题,我们构建了一个名为 Symphony 的系统。Symphony 是一个智能体编排器,它将 Linear 这类项目管理看板转变为编码智能体的控制面板。每个待处理的任务都会分配一个智能体,这些智能体会持续运行,而人类则负责审查结果。

这篇文章将解释我们如何创建 Symphony——它使某些团队的落地拉取请求(pull request)数量增加了 500%——以及如何利用它将你自己的问题追踪器转变为一个始终在线的智能体编排器。

交互式编码智能体的天花板

即便编码智能体变得越来越易用,无论是通过网页应用还是命令行界面访问,它们本质上仍然是交互式工具。

随着 OpenAI 内部智能体工作规模的扩大,我们发现了一种新的负担。每位工程师会打开几个 Codex 会话,分配任务,审查输出,引导智能体,然后重复这一过程。实际上,大多数人一次能舒适地管理三到五个会话,之后上下文切换就会变得令人痛苦。超过这个数量,生产力就会下降。我们会忘记哪个会话在做什么,在多个终端之间跳转以引导智能体回到正轨,并调试那些中途停滞的长时间运行任务。

智能体速度很快,但我们的系统瓶颈在于:人类的注意力。我们实际上建立了一个由极其能干的初级工程师组成的团队,然后让人类工程师去对他们进行微观管理。这种方式无法规模化。

视角的转变

我们意识到自己一直在优化错误的方向。我们围绕编码会话和合并的 PR 来构建系统,但 PR 和会话本质上只是达成目的的手段。软件工作流很大程度上是按交付物来组织的:问题、任务、工单、里程碑。

于是我们问自己:如果我们不再直接监督智能体,而是让它们从任务追踪系统中拉取工作,会发生什么?

这个想法演变成了 Symphony,一份作为监督者来编排智能体工作的书面规范。

将问题追踪系统转变为智能体编排器

Symphony 始于一个简单的概念:任何未完成的任务都应该由智能体接手并完成。我们没有在多个标签页中管理 Codex 会话,而是将问题追踪系统作为控制平面。

在这种设置下,每个未关闭的 Linear 问题都映射到一个专用的智能体工作空间。Symphony 持续监控任务看板,确保每个活跃任务都有一个智能体持续运行,直到任务完成。如果智能体崩溃或停滞,Symphony 会重启它。如果出现新任务,Symphony 会接手并开始组织工作。

我们基于工单状态构建工作流,将任务管理器 Linear 用作一个状态机。

在实践中,Symphony 将工作与会话和拉取请求解耦。有些问题会在多个仓库中产生多个 PR;另一些则是纯粹的调查或分析,从不触及代码库。

一旦工作以这种方式被抽象化,工单就可以代表更大规模的工作单元。

我们经常使用 Symphony 来编排复杂的功能和基础设施迁移。例如,我们可能会提交一个任务,要求智能体分析代码库、Slack 或 Notion,并生成一份实施计划。一旦我们对计划满意,智能体就会生成一个任务树,将工作分解为多个阶段,并定义任务之间的依赖关系。

智能体只开始处理未被阻塞的任务,因此执行过程会针对这个 DAG(一系列执行步骤)自然且最优地并行展开。例如,我们将 React 升级标记为依赖于迁移到 Vite。正如预期的那样,智能体只有在迁移到 Vite 完成后才开始升级 React。

智能体也可以自行创建工作任务。在实施或审查过程中,它们常常会发现当前任务范围之外的改进点:比如性能问题、重构机会或更优的架构。每当这种情况出现,它们就会直接提交一个新的工单,供我们后续评估和排期——其中许多后续任务也会由智能体接手处理。在我们监督这一流程的同时,智能体始终保持条理清晰,推动工作持续向前。

这种工作方式极大地降低了启动模糊任务时的认知成本。如果智能体做错了什么,那仍然是有用的信息,而我们的成本几乎为零。我们可以非常低成本地为智能体提交工单,让它去进行原型探索,然后丢弃任何我们不喜欢的探索结果。

由于编排器运行在开发盒上且从不休眠,我们可以从任何地方添加任务,并知道会有智能体接手处理。例如,我们团队的一名工程师曾在一间舒适的小木屋里,通过糟糕的 WiFi 用手机上的 Linear 应用完成了三项重大变更。

这种工作方式带来了探索活动的增加

在观察使用 Symphony 的效果时,最明显的变化是产出。在 OpenAI 内部的一些团队中,我们看到前三个星期内合并的 PR 数量增加了 500%。在 OpenAI 之外,Linear 创始人 Karri Saarinen 也强调了随着我们发布 Symphony,创建工作区的数量出现了激增。然而,更深层次的转变在于团队对工作本身的思考方式。

当我们的工程师不再需要花时间监督 Codex 会话时,代码变更的经济性就彻底改变了。每次变更的感知成本下降,因为我们不再投入人力去驱动实施过程本身。

这改变了我们的行为。在 Symphony 中启动探索性任务变得轻而易举。尝试一个想法,探索一次重构,测试一个假设,然后只保留那些看起来有希望的结果。

这也拓宽了能够发起工作的人员范围。我们的产品经理和设计师现在可以直接将功能请求提交到 Symphony 中。他们不需要检出代码仓库或管理 Codex 会话。他们只需描述功能需求,就能收到一份审查包,其中包含该功能在实际产品中运行的视频演示。

Symphony 在大型单体仓库(比如 OpenAI 内部使用的这种)中同样表现出色,这类仓库中,PR 落地的最后一公里往往既缓慢又脆弱。该系统会监控 CI,在需要时自动变基,解决冲突,重试不稳定的检查项,并全程引导变更通过流水线。当工单进入合并阶段时,我们有很高的信心,变更无需人工看护就能顺利进入主分支。

实施 Symphony 之后,我们将更多工作委托给智能体,从而专注于更困难、更具探索性的任务。

进步伴随着新的、不同的问题。

在如此高的层级上运作需要权衡取舍。当我们从交互式引导智能体转向在工单层面分配任务时,我们失去了在任务执行过程中不断给予提示并在必要时纠正方向的能力。有时智能体产出的结果完全偏离了目标。这反而很有用——那些失败暴露了系统中的漏洞,帮助我们使其更加健壮。

我们没有手动修补结果,而是增加了护栏和技能,以便智能体下次能够成功。随着时间的推移,这促使我们为工具框架增加了新能力,例如运行端到端测试、通过 Chrome DevTools 驱动应用,以及管理 QA 冒烟测试。我们显著改进了文档,并明确了什么才是好的表现。

并非所有任务都适合 Symphony 的工作方式。有些问题仍然需要工程师直接使用交互式 Codex 会话,尤其是那些模糊不清的问题,或者需要强大判断力和专业知识的任务。在实践中,这些通常是我们工程师最愿意花时间处理、也最有趣的任务。

区别在于,Symphony 能够处理大部分常规实现工作。这让工程师可以一次专注于一个难题,而不是在多个小任务之间不断切换上下文。

我们还了解到,将智能体视为状态机中的刚性节点效果并不理想。模型会变得更智能,能够解决比我们试图将其塞入的框架更大的问题。我们早期版本的智能体工作只要求 Codex 执行任务。这种方法被证明过于局限。Codex 完全有能力创建多个 PR,以及阅读审查反馈并对其进行处理。因此,我们为它提供了工具——gh CLI、读取 CI 日志的技能等——现在我们可以要求 Codex 做更多事情,比如关闭旧的 PR,或者拉取已完成与已放弃工作的对比报告。这些类型的任务完全超出了最初的功能实现框架。

因此,我们最终转向为智能体设定目标,而不是严格的转换规则,这很像一位优秀的管理者会为团队中的直接下属分配一个目标。模型的能力来自于它们的推理能力,所以给它们工具和上下文,然后让它们自由发挥。

使用 Symphony 构建 Symphony

当你打开 Symphony 仓库时,首先会注意到的是,Symphony 在技术上只是一个 SPEC.md 文件——一个对问题和预期解决方案的定义。我们没有构建一个复杂的监督系统,而是定义了问题和预期的解决方案,为智能体提供高层级的指导。

Markdown

1# Symphony 服务规范 2状态:草案 v1(语言无关) 4目的:定义一种编排编码智能体以完成项目工作的服务。 6## 1. 问题陈述 8Symphony 是一个长期运行的自动化服务,它持续从问题跟踪器(本规范版本中为 Linear)读取工作,为每个问题创建隔离的工作空间,并在该工作空间内为该问题运行一个编码智能体会话。 10该服务解决了四个运营问题: 12- 将问题执行转变为可重复的守护进程工作流,而非手动脚本。 13- 将智能体执行隔离在按问题划分的工作空间中,使智能体命令仅在该工作空间目录内运行。 14- 将工作流策略保留在仓库内(WORKFLOW.md),使团队能够将智能体提示词和运行时设置与代码一起进行版本控制。 15- 提供足够的可观测性,以操作和调试多个并发的智能体运行。 17实现方案应明确记录其信任与安全策略。本规范不要求单一的审批、沙箱或操作员确认策略;某些实现可能针对高信任配置的受信任环境,而其他实现可能需要更严格的审批或沙箱机制。 19重要边界: 21- Symphony 是一个调度器/运行器和跟踪器读取器。 22- 工单写入(状态转换、评论、PR 链接)通常由编码智能体使用工作流/运行时环境中可用的工具执行。 23- 成功的运行可能以工作流定义的交接状态(例如“人工审核”)结束,而不一定是“已完成”。 25## 2. 目标与非目标 27### 2.1 目标 29- 按固定周期轮询问题跟踪器,并以有界并发度分派工作。 30- 维护一个单一的权威编排器状态,用于分派、重试和协调。 31- 创建确定性的按问题工作空间,并在多次运行之间保留它们。 32- 当问题状态变化使其不符合条件时,停止正在进行的运行。 33- 通过指数退避从瞬时故障中恢复。 34- 从仓库拥有的 WORKFLOW.md 契约加载运行时行为。 35- 暴露操作员可见的可观测性(至少是结构化日志)。 36- 支持无需持久化数据库的重启恢复。 38### 2.2 非目标 40- 丰富的 Web UI 或多租户控制平面。 41- 规定特定的仪表盘或终端 UI 实现。 42- 通用工作流引擎或分布式任务调度器。 43- 内置的编辑工单、PR 或评论的业务逻辑。(该逻辑存在于工作流提示词和智能体工具中。) 44- 强制要求超出编码智能体和主机操作系统所提供的强沙箱控制。 45- 为所有实现强制要求单一的默认审批、沙箱或操作员确认姿态。 47## 3. 系统概述 49### 3.1 主要组件 511. 工作流加载器 52 - 读取 WORKFLOW.md。 53 - 解析 YAML 前置元数据和提示词主体。 54 - 返回 {config, prompt_template}。 562. 配置层 57 - 为工作流配置值提供类型化的 getter 方法。 58 - 应用默认值和环境变量间接引用。 59 - 执行编排器在分派前使用的验证。 613. 问题跟踪器客户端 62 - 获取处于活动状态的候选问题。 63 - 获取特定问题 ID 的当前状态(协调)。 64 - 在启动清理期间获取处于终态的问题。 65 - 将跟踪器负载标准化为稳定的问题模型。 674. 编排器 68 - 拥有轮询节拍。 69 - 拥有内存中的运行时状态。 70 - 决定哪些问题需要分派、重试、停止或释放。 71 - 跟踪会话指标和重试队列状态。 735. 工作空间管理器 74 - 将问题标识符映射到工作空间路径。 75 - 确保按问题划分的工作空间目录存在。 76 - 运行工作空间生命周期钩子。 77 - 清理处于终态问题的工作空间。 796. 智能体运行器 80 - 创建工作空间。 81 - 从问题和工作流模板构建提示词。 82 - 启动编码智能体应用服务器客户端。 83 - 将智能体更新流式传输回编排器。 857. 状态界面(可选) 86 - 呈现人类可读的运行时状态(例如终端输出、仪表盘或其他面向操作员的视图)。 888. 日志记录 89 - 将结构化运行时日志发送到一个或多个配置的目标端。 91### 3.2 抽象层级 93Symphony 在保持以下分层时最易于移植: 951. 策略层(仓库定义) 96 - WORKFLOW.md 提示词主体。 97 - 团队特定的工单处理、验证和交接规则。 992. 配置层(类型化 getter) 100 - 将前置元数据解析为类型化的运行时设置。 101 - 处理默认值、环境令牌和路径标准化。 1033. 协调层(编排器) 104 - 轮询循环、问题资格、并发、重试、协调。 1064. 执行层(工作空间 + 智能体子进程) 107 - 文件系统生命周期、工作空间准备、编码智能体协议。 1095. 集成层(Linear 适配器) 110 - 跟踪器数据的 API 调用和标准化。 1126. 可观测性层(日志 + 可选状态界面) 113 - 操作员对编排器和智能体行为的可见性。 115### 3.3 外部依赖 117- 问题跟踪器 API(本规范版本中为 Linear,对应 tracker.kind: linear)。 118- 用于工作空间和日志的本地文件系统。 119- 可选的工作空间填充工具(例如 Git CLI,如果使用)。 120- 支持通过 stdio 进行类似 JSON-RPC 的应用服务器模式的编码智能体可执行文件。 121- 用于问题跟踪器和编码智能体的主机环境认证。 123## 4. 核心领域模型 125### 4.1 实体 127#### 4.1.1 问题 129由编排、提示词渲染和可观测性输出使用的标准化问题记录。 131字段: 133- id(字符串) 134 - 稳定的跟踪器内部 ID。 135- identifier(字符串) 136 - 人类可读的工单键(例如:ABC-123)。 137- title(字符串) 138- description(字符串或 null) 139- priority(整数或 null) 140 - 在分派排序中,数字越小优先级越高。 141- state(字符串) 142 - 当前跟踪器状态名称。 143- branch_name(字符串或 null) 144 - 跟踪器提供的分支元数据(如果可用)。 145- url(字符串或 null) 146- labels(字符串列表) 147 - 标准化为小写。 148- blocked_by(阻塞器引用列表) 149 - 每个阻塞器引用包含: 150 - id(字符串或 null) 151 - identifier(字符串或 null) 152 - state(字符串或 null) 153- created_at(时间戳或 null) 154- updated_at(时间戳或 null) 156#### 4.1.2 工作流定义 158解析后的 WORKFLOW.md 负载: 160- config(映射) 161 - YAML 前置元数据根对象。 162- prompt_template(字符串) 163 - 前置元数据之后的 Markdown 主体,已修剪。 165#### 4.1.3 服务配置(类型化视图) 167从 WorkflowDefinition.config 加上环境解析派生的类型化运行时值。 169示例: 171- 轮询间隔 172- 工作空间根目录 173- 活动状态和终态问题状态 174- 并发限制 175- 编码智能体可执行文件/参数/超时 176- 工作空间钩子 178#### 4.1.4 工作空间 180分配给一个问题标识符的文件系统工作空间。 182字段(逻辑): 184- path(工作空间路径;当前运行时通常使用绝对路径,但如果配置中不包含路径分隔符,也可以使用相对根目录) 185- workspace_key(经过清理的问题标识符) 186- created_now(布尔值,用于控制 after_create 钩子的执行) 188#### 4.1.5 运行尝试 190针对一个问题的一次执行尝试。 192字段(逻辑): 194- issue_id 195- issue_identifier 196- attempt(整数或 null,首次运行为 null,重试/继续运行 >=1) 197- workspace_path 198- started_at 199- status 200- error(可选) 202#### 4.1.6 实时会话(智能体会话元数据) 204在编码智能体子进程运行期间跟踪的状态。 206字段: 208- session_id(字符串) 209- thread_id(字符串) 210- turn_id(字符串) 211- codex_app_server_pid(字符串或 null) 212- last_codex_event(字符串/枚举或 null) 213- last_codex_timestamp(时间戳或 null) 214- last_codex_message(摘要负载) 215- codex_input_tokens(整数) 216- codex_output_tokens(整数) 217- codex_total_tokens(整数) 218- last_reported_input_tokens(整数) 219- last_reported_output_tokens(整数) 220- last_reported_total_tokens(整数) 221- turn_count(整数) 222 - 在当前工作器生命周期内启动的编码智能体轮次数量。 224#### 4.1.7 重试条目 226为问题安排的重试状态。 228字段: 230- issue_id 231- identifier(用于状态界面/日志的尽力而为的人类可读 ID) 232- attempt(整数,重试队列中从 1 开始) 233- due_at_ms(单调时钟时间戳) 234- timer_handle(运行时特定的定时器引用) 235- error(字符串或 null) 237#### 4.1.8 编排器运行时状态 239由编排器拥有的单一权威内存状态。 241字段: 243- poll_interval_ms(当前有效的轮询间隔) 244- max_concurrent_agents(当前有效的全局并发限制) 245- running(映射 issue_id -> 运行条目) 246- claimed(已保留/运行中/重试中的问题 ID 集合) 247- retry_attempts(映射 issue_id -> RetryEntry) 248- completed(问题 ID 集合;仅用于记账,不用于分派门控) 249- codex_totals(聚合令牌数 + 运行时秒数) 250- codex_rate_limits(来自智能体事件的最新速率限制快照) 252### 4.2 稳定标识符与标准化规则 254- 问题 ID 255 - 用于跟踪器查找和内部映射键。 256- 问题标识符 257 - 用于人类可读的日志和工作空间命名。 258- 工作空间键 259 - 从 issue.identifier 派生,将任何不在 [A-Za-z0-9.-] 范围内的字符替换为 _。 260 - 使用清理后的值作为工作空间目录名。 261- 标准化问题状态 262 - 比较状态前先转换为小写。 263- 会话 ID 264 - 由编码智能体的 thread_id 和 turn_id 以 - 连接组成。 266## 5. 工作流规范(仓库契约) 268### 5.1 文件发现与路径解析 270工作流文件路径优先级: 2721. 显式的应用程序/运行时设置(由 CLI 启动路径设置)。 2732. 默认值:当前进程工作目录中的 WORKFLOW.md。 275加载器行为: 277- 如果无法读取文件,返回 missing_workflow_file 错误。 278- 工作流文件预期由仓库拥有并进行版本控制。 280### 5.2 文件格式 282WORKFLOW.md 是一个带有可选 YAML 前置元数据的 Markdown 文件。 284设计说明: 286- WORKFLOW.md 应足够自包含,以描述和运行不同的工作流(提示词、运行时设置、钩子和跟踪器选择/配置),而无需带外服务特定的配置。 288解析规则: 290- 如果文件以 --- 开头,则将直到下一个 --- 的行解析为 YAML 前置元数据。 291- 剩余行成为提示词主体。 292- 如果缺少前置元数据,则将整个文件视为提示词主体,并使用空配置映射。 293- YAML 前置元数据必须解码为映射/对象;非映射的 YAML 视为错误。 294- 提示词主体在使用前进行修剪。 296返回的工作流对象: 298- config:前置元数据根对象(不嵌套在 config 键下)。 299- prompt_template:修剪后的 Markdown 主体。 301### 5.3 前置元数据模式 303顶层键: 305- tracker 306- polling 307- workspace 308- hooks 309- agent 310- codex 312为向前兼容,应忽略未知键。 314注意: 316- 工作流前置元数据是可扩展的。可选扩展可以定义额外的顶层键(例如 server),而无需更改上述核心模式。 318- 扩展应记录其字段模式、默认值、验证规则以及更改是动态应用还是需要重启。 319- 常见扩展:server.port(整数)启用第 13.7 节中描述的可选 HTTP 服务器。 321#### 5.3.1 tracker(对象) 323字段: 325- kind(字符串) 326 - 分派必需。 327 - 当前支持的值:linear 328- endpoint(字符串) 329 - 当 tracker.kind == "linear" 时的默认值:https://api.linear.app/graphql 330- api_key(字符串) 331 - 可以是字面令牌或 $VAR_NAME。 332 - 当 tracker.kind == "linear" 时的规范环境变量:LINEAR_API_KEY。 333 - 如果 $VAR_NAME 解析为空字符串,则将密钥视为缺失。 334- project_slug(字符串) 335 - 当 tracker.kind == "linear" 时分派必需。 336- active_states(字符串列表) 337 - 默认值:Todo, In Progress 338- terminal_states(字符串列表) 339 - 默认值:Closed, Cancelled, Canceled, Duplicate, Done 341#### 5.3.2 polling(对象) 343字段: 345- interval_ms(整数或字符串整数) 346 - 默认值:30000 347 - 更改应在运行时重新应用,并影响未来的节拍调度,无需重启。 349#### 5.3.3 workspace(对象) 351字段: 353- root(路径字符串或 $VAR) 354 - 默认值:<系统临时目录>/symphony_workspaces 355 - ~ 和包含路径分隔符的字符串会被展开。 356 - 不带路径分隔符的裸字符串按原样保留(允许相对根目录,但不推荐)。 358#### 5.3.4 hooks(对象) 360字段: 362- after_create(多行 shell 脚本字符串,可选) 363 - 仅在工作空间目录新创建时运行。 364 - 失败会中止工作空间创建。 365- before_run(多行 shell 脚本字符串,可选) 366 - 在每次智能体尝试之前、工作空间准备之后、启动编码智能体之前运行。 367 - 失败会中止当前尝试。 368- after_run(多行 shell 脚本字符串,可选) 369 - 在每次智能体尝试之后(成功、失败、超时或取消),且工作空间存在时运行。 370 - 失败会被记录但忽略。 371- before_remove(多行 shell 脚本字符串,可选) 372 - 在工作空间删除之前,如果目录存在则运行。 373 - 失败会被记录但忽略;清理仍会继续。 374- timeout_ms(整数,可选) 375 - 默认值:60000 376 - 适用于所有工作空间钩子。 377 - 非正值应视为无效并回退到默认值。 378 - 更改应在运行时重新应用,以影响未来的钩子执行。 380#### 5.3.5 agent(对象) 382字段: 384- max_concurrent_agents(整数或字符串整数) 385 - 默认值:10 386 - 更改应在运行时重新应用,并影响后续的分派决策。 387- max_retry_backoff_ms(整数或字符串整数) 388 - 默认值:300000(5 分钟) 389 - 更改应在运行时重新应用,并影响未来的重试调度。 390- max_concurrent_agents_by_state(映射 state_name -> 正整数) 391 - 默认值:空映射。 392 - 状态键在查找时标准化(小写)。 393 - 无效条目(非正数或非数字)将被忽略。 395#### 5.3.6 codex(对象) 397字段: 399对于 Codex 拥有的配置值,例如 approval_policy、thread_sandbox 和 turn_sandbox_policy,支持的值由目标 Codex 应用服务器版本定义。实现者应将其视为透传的 Codex 配置值,而不是依赖本规范中手动维护的枚举。要检查已安装的 Codex 模式,请运行 codex app-server generate-json-schema --out <dir> 并检查 v2/ThreadStartParams.json 和 v2/TurnStartParams.json 中引用的相关定义。如果实现希望进行更严格的启动检查,可以在本地验证这些字段。 401- command(字符串 shell 命令) 402 - 默认值:codex app-server 403 - 运行时通过在工作空间目录中执行 bash -lc 来启动此命令。 404 - 启动的进程必须通过 stdio 使用兼容的应用服务器协议进行通信。 405- approval_policy(Codex AskForApproval 值) 406 - 默认值:实现定义。 407- thread_sandbox(Codex SandboxMode 值) 408 - 默认值:实现定义。 409- turn_sandbox_policy(Codex SandboxPolicy 值) 410 - 默认值:实现定义。 411- turn_timeout_ms(整数) 412 - 默认值:3600000(1 小时) 413- read_timeout_ms(整数) 414 - 默认值:5000 415- stall_timeout_ms(整数) 416 - 默认值:300000(5 分钟) 417 - 如果 <= 0,则禁用停滞检测。 419### 5.4 提示词模板契约 421WORKFLOW.md 的 Markdown 主体是按问题划分的提示词模板。 423渲染要求: 425- 使用严格的模板引擎(兼容 Liquid 的语义即可)。 426- 未知变量必须导致渲染失败。 427- 未知过滤器必须导致渲染失败。 429模板输入变量: 431- issue(对象) 432 - 包含所有标准化的问题字段,包括标签和阻塞器。 433- attempt(整数或 null) 434 - 首次尝试时为 null/不存在。 435 - 重试或继续运行时为整数。 437回退提示词行为: 439- 如果工作流提示

参考实现是用 Elixir 编写的——因为当代码成本几乎为零时,你终于可以根据语言的优势来选择它,比如 Elixir 的并发能力——但核心思想可以用一份简单的 Markdown 文档来表达。我们鼓励你把你最喜欢的编程智能体指向这份规范,让它实现自己的版本。

Symphony 的第一个版本只是一个运行在 tmux 中的 Codex 会话,轮询 Linear 并为新任务生成子智能体。它能工作,但不太可靠。第二个版本位于我们的主项目仓库内,该仓库在设计时就考虑到了智能体。我们已经构建了智能体框架,为智能体提供在此仓库中高质量工作所需的技能和上下文,因此 Symphony 只是将所有内容连接起来。

一旦基本功能就绪,我们就用 Symphony 来构建 Symphony。

当我们内部演示该系统管理任务并附上其工作量证明视频时,反响非常积极:我们的 Symphony 项目频道壮大了,整个组织的团队都开始自发使用它。内部产品市场契合度是在 OpenAI 外部发布产品的先决条件。根据我们在 OpenAI 内部看到的使用情况,很明显我们应该在公司外部分享 Symphony。

于是我们将这个想法提炼成一份独立的 SPEC.md 文件,并让 Codex 去实现它。对于参考实现,我们选择了 Elixir,这是一种相对小众的语言,但拥有出色的原语来编排和监督并发进程。Codex 一次性构建了 Elixir 实现,我们在此基础上不断迭代规范和实现。为了打磨规范,我们甚至让 Codex 用其他几种语言——TypeScript、Go、Rust、Java、Python——来实现它,并利用结果来识别歧义并简化系统。它在每种语言上都成功了。

在构建 Symphony 的过程中,我们消除了大量附带复杂性,例如对特定仓库或 Linear MCP 的依赖。Symphony 不再依赖于我们的内部仓库或工作流程。核心方法变得简单:

对于每个未完成的任务,确保有一个智能体在其自己的工作空间中运行。

除了协助实际工作外,开发工作流如今也成为智能体能够了解并遵循的流程。开发工作流——处理某个问题、检出代码仓库、将其标记为进行中以便项目经理知晓正在处理、添加拉取请求、将其移至“审核”状态、附上视频等——现在都记录在一个简单的 WORKFLOW.md 文件中。所有这些原本是人类遵循的流程,但从未被文档化。现在我们不再依赖这套隐式的步骤,而是将其文档化,Symphony 确保智能体遵循它。这让我们能够构建与我们并肩工作的智能体。如果我们决定智能体还应在完成的工作中附上自我反思,我们会将其添加到 WORKFLOW.md 中,Symphony 将引导智能体执行该步骤。

我们还得以在应用服务器模式下使用 Codex,这是 Codex 内置的一种无头模式。该模式允许我们运行 Codex,并通过文档完善的 JSON-RPC API 以编程方式与之交互,例如启动线程或响应对话轮次。与通过 CLI 或实时 tmux 会话与 Codex 交互相比,这种方式更便捷、更具可扩展性。

Codex 应用服务器非常适合我们的用例:我们利用 Codex 提供的框架,同时拥有可调节和可接入的控制点。例如,为了避免向子智能体暴露 Linear 访问令牌,我们使用动态工具调用来暴露原始的 linear_graphql 函数,该函数可对 Linear 执行任意请求,而无需依赖 MCP 或将访问令牌暴露给容器。

下一步计划

Symphony 是一个刻意保持精简的编排层。我们将其开源,以展示 Codex 应用服务器与不同工作流工具(如 Linear)结合时的强大能力。因此,我们不打算将 Symphony 作为独立产品来维护。请将其视为一个参考实现。就像许多开发者将他们的编码智能体指向框架工程文章来搭建代码仓库一样,我们希望你能将你最喜欢的编码智能体指向 Symphony 规范文档和代码仓库,构建适合你自己环境的定制版本。

其强大之处源于 Codex 及其应用服务器。Symphony 是一种将 Codex 与我们已使用的 Linear 连接起来的方式,用以解决工作管理问题。随着编码智能体在推理和遵循指令方面能力日益增强,我们推测其他公司的瓶颈也将从编写代码转向管理智能体工作。令人兴奋的是,如今尝试这些编码智能体系统的门槛已低得出奇。你只需用 Codex 就能构建东西。

社区致谢

我们非常高兴地看到,自发布以来的数周内,工程社区一直在使用 Symphony,截至 4 月 23 日,该项目已在 GitHub 上获得超过 15,000 颗星。

我让一个智能体在我的 Elixir ERP 应用中制作一个 OpenAI Symphony 仓库的版本。它一次性生成了一个完整的仪表盘,包含 GenServer 和自定义智能体,用于在生产环境中捕获 bug,并启动智能体进行修复和提交。还添加了一个用户请求功能,与同一系统集成。🤯🤯🤯

OpenAI 发布了 Symphony,这是一个项目级的 AI 编码智能体编排器。我用 @charmcli 栈的 TUI 实现了它的 Go 版本 🎸 github.com/junhoyeo/contr…

@marmaduke091

🎵 OpenAI 推出 Symphony “Symphony 将项目工作转化为隔离的、自主的实现运行,使团队能够管理工作,而非监督编码智能体。” 来看看吧,看起来很酷:github.com/openai/symphony

OpenAI 的 Symphony 是一个很棒的编码智能体编排器——但仅限 @OpenAI Codex。我将其分支出来,使其能与 @AnthropicAI 的 Claude Code 及 GitHub Issues 配合使用。开源,可通过 Homebrew 安装。sapsaldog.com/posts/symphony…

使用 Claude Code 运行 OpenAI Symphony

OpenAI 发布了 Symphony——一个将项目工作转化为自主实现运行的规范。管理工作,而非智能体。我借鉴了这个想法,并用 @AnthropicAI 的 Claude Code 重新构建。hatice 是我对 Symphony 规范的实现,由 Claude Code Agent SDK 驱动。

来源:OpenAI:官网动态(RSS · 排除企业/客户案例)· openai.com