Skip to main content

如何编排 Codex 去自动做工单: Symphony

· 6 min read

OpenAI 把 Codex 编排做成了语言无关的规范。

  1. 大要素:SPEC.md(规范) + elixir/(参考实现) + WORKFLOW.md(仓内配置)
  2. 定位升级:从「管 agent」升级到「管工单」
  3. 主循环:拉 tracker → 建 per-issue workspace → 跑 Codex app-server
  4. 边界:低密预览版,只跑受信环境,无内置沙箱
  5. 设计巧思:host 侧注入 tracker 凭据,主动从 Codex 子进程剔除
  6. License:Apache-2.0,可绕过 Elixir 按规范自实现

Symphony 不是产品,是规范

OpenAI 这次发的不是一个新 agent runtime,而是一份规范外加一个参考实现——Symphony 把「如何编排 Codex 去干工单」拆成了两件可以分开看的东西。

  • SPEC.md:语言无关、RFC-2119 风格的规范。它定义域模型(Issue / Workspace / RunAttempt / LiveSession / RetryEntry)、定义抽象分层(Policy / Config / Coordination / Execution / Integration / Observability),但不告诉你用哪种语言、用哪种 tracker、用什么沙箱策略。
  • elixir/:Elixir/OTP 参考实现。它证明规范可以照着实现出来,但 README 开头就警告「prototype software intended for evaluation only, presented as-is」——不要直接拿这份实现当生产框架。

这种「先 spec 后 impl」的拆分很像 OpenAPI/Swagger。规范是契约,参考实现是说服读者的最小证明,真正的实现由各团队按自己的栈、安全策略、tracker 接入方式自己写。

WORKFLOW.md:把编排策略塞进仓库

Symphony 最值得抄的一点是把编排策略装进仓库的 WORKFLOW.md,跟着代码一起发版。

  • YAML front matter 是 typed config:tracker 类型(tracker.kind: linear)、workspace 根目录、并发上限、Codex 启动命令、超时阈值都在这一层。
  • Markdown body 是 Codex session 的 prompt 模板,{{ issue.identifier }}{{ issue.title }} 这类占位符在每轮 dispatch 时由 Symphony 渲染后下发。
---
tracker:
kind: linear
provider:
project_slug: "..."
workspace:
root: ~/code/workspaces
hooks:
after_create: |
git clone git@github.com:your-org/your-repo.git .
agent:
max_concurrent_agents: 10
max_turns: 20
codex:
command: codex app-server
---

You are working on an issue from the configured tracker {{ issue.identifier }}.

Title: {{ issue.title }} Body: {{ issue.description }}

直接结果:「怎么管 coding agent」这件事跟着代码一起 review、一起发版本、一起回滚,而不是藏在某个长工序列里。

主循环:拉工单 → 开 workspace → 跑 Codex

抽象层看下来,Execution Layer 是这样的:

每个 issue 一个独立 workspace,Codex 以 app-server mode 在里面跑(JSON-RPC over stdio),Symphony 作为 orchestrator 收流式事件、维护运行时状态。tracker 适配器顺手暴露 provider-native 工具给 Codex——Linear 拿到 linear_graphql、GitHub 拿到 github_api、Jira 拿到 jira_rest——这样 Codex 自己能写评论、改状态,不用 Symphony 在外面再代理一层。

凭证隔离是设计重点

tracker 的 API key 通过 host 侧的环境变量注入,Symphony 在 fork 出 Codex 子进程时主动剔除这些 token 变量。Codex 拿不到原始凭据,要写 tracker 就走 Symphony 暴露的代理工具。凭证泄漏面比传统 bot 收敛一截——agent 没有第二个 tracker 登录入口。

状态机比想象中克制

Symphony 故意不做「通用工作流引擎」,规范里的边界划得很小:

  • 终止态:issue 一旦进 Done / Closed / Cancelled / Duplicate,Symphony 立刻停掉对应 agent、清理 workspace。这意味着你可以手动通过改工单状态中止任何正在跑的 task——不需要单独的 kill 通道。
  • Blocked 态:Codex 上报「需要 operator 审批 / MCP elicitation」时,Symphony 把这个 issue 标记为 blocked,暴露在运行时状态、JSON API、dashboard 里等人介入。
  • Retry:指数退避;retry queue 在内存里;进程重启后清空,让 tracker 自己重新成为 source of truth。

非目标里更值得看:规范没有强制多租户控制平面,没有规定 UI 形态,也没有强加审批/沙箱策略——安全这件事由各家实现自己背书。

信任边界:先认清它是 Draft

打开 README 第一眼是 > [!WARNING] Symphony is a low-key engineering preview for testing in trusted environments. 把它当 demo 跑没问题。

SPEC.md 状态写的是 Draft v1,RFC-2119 用得很标准(MUST / SHOULD / MAY 分得很清楚),但照着写实现之前先想清楚几件事:

  • 不要把 token 显式写进仓库——WORKFLOW.md 引用环境变量靠 host 侧 secret ref,不是文件本身。
  • 默认 codex.approval_policyrejectthread_sandboxworkspace-write,别自己改成 never 之类的危险值。
  • 进程重启会丢 retry queue。这是设计选择不是 bug——意味着你得有上游 tracker 当 source of truth,否则别省这一步。

怎么参与:自己写一份也行

README 给两条路。

第一条:按 SPEC.md 自己实现。 README 鼓励的方式——「Tell your favorite coding agent to build Symphony in a programming language of your choice」。SPEC.md 是语言无关的,所以 Go / Rust / TypeScript 都能起一份。

第二条:用 Elixir 参考实现。 v* tag 提供 Burrito 打的单文件 release,四个平台都有:

chmod +x ./symphony-v0.0.1-macos_arm64
./symphony-v0.0.1-macos_arm64 ./WORKFLOW.md

单文件里已经打了 Erlang/OTP + Elixir + Symphony,目标机器上只需要 codexgit 在 PATH 里。嫌 Elixir 维护成本高,自己起一份 Go 实现也没问题——这就是规范先行的好处。

References

  1. Open source Codex orchestration: Symphony —— OpenAI, 官方博客
  2. openai/symphony —— Apache-2.0,截至 2026-07-27 仓库 26k Star
  3. Harness engineering —— OpenAI