1. 从 Linear 工单到自主编码 Agent:Symphony 调度器到底解决什么问题
如果你已经在用 Linear 管理开发任务,大概率经历过这样的循环:看板上躺着一堆 Todo,你手动挑一个,打开编辑器,把 issue 描述复制给 Codex 或 Claude Code,等它跑完,再手动改状态、提 PR。任务一多,光是「派活」这件事就消耗掉大量注意力。
Symphony 想做的事情很直接:把「派活」这一步自动化。它是一个长跑型调度服务,定期轮询 Linear 看板上的可执行任务,为每个 issue 创建独立 workspace,在里面启动 Codex Agent 执行,Agent 完成后自动提交 PR 并回写 Linear 状态。你只需要把任务放进看板,剩下的调度、隔离、执行、跟进由 Symphony 接管。
适合谁用?三类人值得关注:一是团队已经用 Linear 做任务管理,想让常规开发任务自动流转;二是想研究多 Agent 并发调度的工程实践;三是愿意自己调整 prompt 策略和沙箱边界,把 Agent 当成「按工单交付的团队成员」而不是「一次性脚本」。
核心检索词先明确:Symphony 是 OpenAI 开源的多 Agent 任务调度服务,WORKFLOW.md 是它的编排契约文件,Linear 是任务输入源,Codex 是执行层。这四个词串起来就是本文要跑通的最小闭环。
我试过把几个常规重构任务丢进去跑,整体感受是:Symphony 本身不做代码修改,它只做调度和看板读取,具体怎么改 ticket、怎么发评论、怎么提 PR,全部写在 WORKFLOW.md 的 prompt 模板里,由 Codex Agent 自己执行。这个设计意味着——你的 prompt 质量直接决定 Agent 的产出质量。
Symphony 用 Elixir/OTP 编写,参考实现包含 Orchestrator(调度器)、Issue Tracker Client(Linear 适配)、Workspace Manager(per-issue 目录)、Agent Runner(Codex app-server)四个核心组件。它解决的核心问题是:把 issue 执行变成可重复的 daemon 流程,在每个 issue 独立的 workspace 中隔离 Agent 执行,团队把 Agent prompt 规则版本化在 WORKFLOW.md 里,并提供足够多的可观测性来同时运营多个并发 Agent。
需要提前说明的是,Symphony 目前是低层次的工程预览版,主要供在受信环境中测试使用。它本身不做 sandbox 控制,依赖 Codex 加操作系统层面的安全机制。部署前需要确认 Linear API Key 的权限范围、Codex app-server 的审批策略、workspace 目录的隔离是否充分。
2. 前置准备:Linear API Key、Codex 环境与 WORKFLOW.md 编排契约
在跑通最小调度闭环之前,需要把三样东西准备好:Linear 侧的 API Key 和自定义状态、Codex 侧的 app-server 环境、以及仓库根目录的 WORKFLOW.md 文件。这一章把每一步拆开讲清楚。
2.1 申请 Linear Personal API Key
进入 Linear 的 Settings → Security & access → Personal API keys,创建一个新的 key。创建后立刻复制保存,页面刷新后就看不到了。设置环境变量:
export LINEAR_API_KEY="lin_api_***"这个 key 会被 Symphony 用来轮询看板、读取 issue 详情、回写状态。权限范围建议只给必要的项目读写权限,不要用管理员级别的 key。
2.2 配置 Linear 自定义状态
Symphony 的参考实现依赖几个非标准的 Linear 状态,需要在 Team Settings → Workflow 中手动创建:
| 状态名 | 作用 |
|---|---|
| Rework | Agent 自评未通过,需要重做 |
| Human Review | 等待人工审核 |
| Merging | 准备合入 |
这三个状态是 Symphony 调度逻辑的锚点。如果缺失,Agent 完成任务后无法正确流转,issue 会卡在中间态。创建时注意状态类型要选对,Rework 和 Human Review 属于 Started 类别,Merging 也归入 Started。
2.3 准备 Codex app-server 环境
Symphony 通过codex app-server命令启动 Agent。确认本地 Codex CLI 已安装且能正常运行:
codex --version codex app-server --help如果 Codex 需要走统一的 API 网关来管理多模型账号和用量,可以在环境变量里配置 Base URL 和 Key。比如用 TaoToken 作为统一接入层时,Codex 的配置可以写成:
# ~/.codex/config.toml model = "gpt-5" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY"然后在 shell 里导出 key:
export TAOTOKEN_API_KEY="sk-***"这样 Codex app-server 启动时会自动读取这个 provider 配置。如果你同时跑多个 Agent,统一网关的好处是 Key 轮换和用量统计都在一处管理,不用每个 Agent 单独配账号。
2.4 编写 WORKFLOW.md
WORKFLOW.md 是 Symphony 的编排契约,用 YAML front matter 配置运行时,Markdown body 作为 Codex Agent 的 prompt 模板。在仓库根目录创建这个文件,最小可用配置如下:
--- tracker: kind: linear project_slug: "your-project-slug" api_key: $LINEAR_API_KEY workspace: root: ~/code/workspaces hooks: after_create: | git clone git@github.com:your-org/your-repo.git . cd your-repo && npm install agent: max_concurrent_agents: 5 max_turns: 20 codex: command: codex app-server approval_policy: on-request thread_sandbox: workspace-write --- You are working on a Linear issue {{ issue.identifier }}. Title: {{ issue.title }} Body: {{ issue.description }} Follow the repository conventions in README.md and CONTRIBUTING.md. When done, commit your changes, push a branch, and open a PR. Update the Linear issue status to Human Review.关键配置项对照:
| 字段 | 作用 | 建议值 |
|---|---|---|
| tracker.kind | 看板类型 | linear |
| tracker.project_slug | Linear 项目标识 | 从项目 URL 提取 |
| workspace.root | workspace 根目录 | ~/code/workspaces |
| hooks.after_create | 创建 workspace 后执行 | git clone + 装依赖 |
| agent.max_concurrent_agents | 最大并发 Agent 数 | 从 3 开始试 |
| agent.max_turns | 单次会话最大轮次 | 20 |
| codex.approval_policy | 审批策略 | on-request |
| codex.thread_sandbox | 沙箱模式 | workspace-write |
project_slug的获取方式:在 Linear 里右键项目 → 复制 URL,URL 中类似linear.app/your-org/project/your-project-slug的最后一段就是 slug。
环境变量支持$VAR形式,会被自动替换。~会自动展开为 home 目录。所以api_key: $LINEAR_API_KEY和root: ~/code/workspaces都能正常工作。
hooks.after_create支持任意 shell 命令,常用于 git clone 拉代码、装依赖、创建配置文件。注意这个 hook 在每个 issue 的 workspace 创建后执行一次,所以 clone 的是完整仓库副本,Agent 在里面改代码不会影响主仓库。
3. 可复制配置:WORKFLOW.md 完整片段与 Linear 触发设置
这一章给出可以直接复制粘贴的完整配置,包括 WORKFLOW.md 的进阶版本、Linear 侧的触发条件设置、以及 Codex 的 provider 配置。目标是让你复制完就能启动。
3.1 完整 WORKFLOW.md 配置
--- tracker: kind: linear project_slug: "agent-sandbox" api_key: $LINEAR_API_KEY active_states: - Todo - Rework terminal_states: - Done - Closed - Cancelled - Duplicate workspace: root: $SYMPHONY_WORKSPACE_ROOT hooks: after_create: | git clone git@github.com:your-org/your-repo.git . cd your-repo npm ci cp .env.example .env before_run: | git fetch origin git checkout main git pull --ff-only agent: max_concurrent_agents: 5 max_turns: 20 retry: max_attempts: 3 backoff: exponential codex: command: "$CODEX_BIN --config 'model=\"gpt-5\"' app-server" approval_policy: on-request thread_sandbox: workspace-write --- You are an autonomous coding agent working on a Linear issue. Issue: {{ issue.identifier }} Title: {{ issue.title }} Description: {{ issue.description }} ## Your task 1. Read the issue description carefully. 2. Explore the repository to understand the codebase. 3. Implement the changes described in the issue. 4. Run the test suite and fix any failures. 5. Commit with a descriptive message referencing {{ issue.identifier }}. 6. Push a branch named `symphony/{{ issue.identifier }}`. 7. Open a pull request against main. 8. Update the Linear issue status to Human Review. ## Constraints - Do not modify files outside the repository root. - Do not install global packages. - If you need clarification, set the issue status to Rework and explain why.这个配置比最小版本多了几个关键点:active_states明确哪些状态的任务会被派发,terminal_states定义终态(进入这些状态后 Symphony 会停止 Agent 并清理 workspace),before_runhook 在每次 Agent 运行前同步主分支,retry配置瞬时失败的重试策略。
3.2 Linear 侧触发设置
Linear 不需要额外安装插件,Symphony 通过 API 轮询读取。但有几个设置需要确认:
第一,项目 slug 要和 WORKFLOW.md 里的project_slug一致。第二,issue 的初始状态要是Todo(或在active_states列表里)。第三,issue 描述要足够具体,因为 Agent 完全依赖描述来理解任务。
一个适合 Agent 执行的 issue 描述模板:
## 背景 用户反馈登录页在移动端布局错乱。 ## 期望行为 登录表单在 375px 宽度下应该垂直排列,按钮占满宽度。 ## 验收标准 - [ ] 375px 宽度下表单垂直排列 - [ ] 按钮宽度 100% - [ ] 现有测试全部通过 - [ ] 新增一个移动端布局的测试用例 ## 相关文件 - src/pages/Login.tsx - src/styles/login.css描述越结构化,Agent 的产出越可控。模糊的「优化一下登录页」会让 Agent 自由发挥,结果往往不是你想要的。
3.3 Codex provider 配置
如果 Codex 需要走统一网关,在~/.codex/config.toml里配置:
model = "gpt-5" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "responses"三件套要写全:Base URL 指向https://taotoken.net/api,Key 通过环境变量TAOTOKEN_API_KEY注入,Model ID 在model字段指定。这样 Codex app-server 启动时会用这个 provider 发请求。
3.4 启动 Symphony
git clone https://github.com/openai/symphony cd symphony/elixir mise trust mise install mise exec -- mix setup mise exec -- mix build mise exec -- ./bin/symphony /path/to/your/WORKFLOW.md --port 4000--port 4000会启动 Phoenix LiveView Dashboard,可以在浏览器里实时观察所有活跃 run。--logs-root可以指定日志目录,默认是./log。
启动后服务会开始轮询 Linear,按 WORKFLOW.md 的策略派发任务。你可以在 Dashboard 的/api/v1/state端点看到当前调度状态的 JSON。
4. 验证请求:一次任务从创建到 Agent 回写的完整闭环
配置就绪后,需要跑一次完整闭环来验证调度器工作正常。这一章用一个具体任务演示从 Linear 创建 issue 到 Agent 提交 PR 的全过程。
4.1 创建测试 issue
在 Linear 的agent-sandbox项目里创建一个新 issue:
- 标题:
Add health check endpoint to API server - 状态:
Todo - 描述:
## 背景 API server 需要一个健康检查端点,供负载均衡器探活。 ## 期望行为 GET /health 返回 200,body 为 {"status":"ok"}。 ## 验收标准 - [ ] GET /health 返回 200 - [ ] body 为 {"status":"ok"} - [ ] 新增测试用例 - [ ] 现有测试全部通过 ## 相关文件 - src/server.ts - src/routes/创建后不要手动改状态,让 Symphony 自己发现。
4.2 观察 Symphony 派发
在终端里看 Symphony 的日志输出,或者打开 Dashboard:
curl http://localhost:4000/api/v1/state | jq你会看到类似这样的状态:
{ "active_runs": [ { "issue_identifier": "AGENT-42", "status": "running", "workspace": "/home/user/code/workspaces/AGENT-42", "started_at": "2025-01-15T10:23:45Z" } ], "blocked": [], "completed": [] }Symphony 的轮询节奏会先发现这个 Todo issue,然后创建 workspace 目录,执行after_createhook 克隆仓库,再启动 Codex app-server 执行 prompt 模板。
4.3 查看 Agent 执行过程
进入 workspace 目录可以看到 Agent 的工作现场:
cd ~/code/workspaces/AGENT-42 git log --oneline -5 git branch -aAgent 会在里面创建分支、修改文件、跑测试、提交。如果配置了--port,Dashboard 的 LiveView 页面会实时显示 Agent 的每一轮对话和工具调用。
4.4 验证回写结果
Agent 完成后会做三件事:提交 PR、更新 Linear issue 状态为Human Review、在 issue 里留评论。回到 Linear 看板,你应该看到:
- issue 状态从
Todo变成Human Review - issue 里有一条评论,包含 PR 链接
- GitHub 上有一个新 PR,分支名类似
symphony/AGENT-42
PR 的内容应该包含/health端点的实现和对应的测试用例。如果一切正常,这个最小调度闭环就跑通了。
4.5 人工审核与合入
在 Linear 里审核 Agent 的产出。如果满意,把状态改成Merging,然后手动合入 PR。如果不满意,把状态改成Rework,Symphony 会重新派发这个 issue,Agent 会在同一个 workspace 里继续修改。
当 issue 进入Done、Closed、Cancelled、Duplicate这些终态时,Symphony 会自动停止该 issue 的 Agent 并清理 workspace。
5. 常见报错排查:401、local proxy failed、reading choices 与 OAuth
跑通过程中大概率会遇到几个典型报错。这一章按报错信息对照排查,每个都给出具体原因和修复方式。
5.1 401 Unauthorized
{"error":"Unauthorized","message":"Invalid API key"}原因通常是LINEAR_API_KEY没设置或设置错了。检查:
echo $LINEAR_API_KEY如果为空,重新导出。如果 key 正确但仍然 401,检查 WORKFLOW.md 里的api_key字段是否写成了$LINEAR_API_KEY(带美元符号才会被环境变量替换)。另外确认 key 没有过期,Linear 的 Personal API Key 可以设置过期时间。
如果是 Codex 侧的 401,检查TAOTOKEN_API_KEY是否正确导出,以及~/.codex/config.toml里的env_key字段名是否和实际环境变量名一致。
5.2 local proxy failed
Error: local proxy failed to connect这个报错通常出现在 Codex app-server 启动阶段。原因是 Codex 尝试连接的 API 端点不可达。排查步骤:
第一,确认base_url配置正确。如果走统一网关,应该是https://taotoken.net/api,不要多加路径后缀。第二,确认网络能访问该端点:
curl -I https://taotoken.net/api第三,检查~/.codex/config.toml里的wire_api字段。有些 provider 需要responses,有些需要chat,配置错了会导致连接失败。
5.3 reading choices 相关报错
Error reading choices: unexpected end of JSON input这个报错说明 Codex 收到了 API 响应,但响应格式不符合预期。常见原因是wire_api配置和实际 API 不匹配。如果用的是 responses 风格的 API,wire_api要设为responses;如果是 chat completions 风格,设为chat。
另一个可能原因是模型 ID 写错了。检查model字段是否和 provider 支持的模型名一致。比如gpt-5和gpt-5-codex是不同的模型 ID,写错会导致响应异常。
5.4 OAuth 相关报错
Error: OAuth token expired如果 Codex 配置的是 OAuth 认证而不是 API Key,token 过期会报这个错。解决方式是重新走 OAuth 流程,或者改用 API Key 认证。在~/.codex/config.toml里把env_key指向一个有效的 API Key 环境变量,避免 OAuth 过期问题。
5.5 Agent 卡在 blocked 状态
如果 Dashboard 显示某个 issue 处于blocked,说明 Codex 报告需要操作员输入或审批确认。检查codex.approval_policy设置:
| 策略 | 行为 |
|---|---|
| untrusted | 所有操作都需要审批 |
| on-failure | 失败时审批 |
| on-request | Agent 主动请求时审批 |
| never | 从不审批 |
| reject | 拒绝所有审批请求 |
如果不想被频繁打断,可以设为on-request或never。但never意味着 Agent 可以自由执行所有操作,需要确保沙箱隔离充分。
重启 orchestrator 后,blocked map 会被清空,issue 可以重新成为派发候选。
5.6 workspace 创建失败
Error: failed to create workspace: directory existsSymphony 为每个 issue 创建独立目录,如果目录已存在会报错。手动清理:
rm -rf ~/code/workspaces/AGENT-42然后重启 Symphony。注意清理前确认里面没有未提交的改动。
6. 把调度器接入你的工作流:从最小闭环到多 Agent 并发
跑通最小闭环后,下一步是把它接入日常开发流程。这一章讲几个实用技巧和扩展方向。
6.1 从单任务到多 Agent 并发
max_concurrent_agents控制同时运行的 Agent 数量。建议从 3 开始,观察系统资源占用和 API 速率限制,再逐步调高。每个 Agent 是一个独立的 Codex app-server 进程,会消耗内存和 API 配额。
并发跑多个 Agent 时,Dashboard 的价值就体现出来了。/api/v1/state返回所有活跃 run 的状态,/api/v1/<issue_identifier>返回单个 issue 的详情,/api/v1/refresh可以手动触发刷新。
6.2 prompt 模板的迭代
WORKFLOW.md 的 Markdown body 是 Agent 的 prompt 模板,支持{{ issue.identifier }}、{{ issue.title }}、{{ issue.description }}这些变量。你可以根据团队规范不断迭代这个模板。
几个实用技巧:在模板里明确要求 Agent 跑测试、要求提交信息引用 issue 编号、要求 PR 描述包含变更摘要。这些约束会显著提升产出质量。
6.3 自定义 Hook 的用法
hooks.after_create和hooks.before_run支持任意 shell 命令。除了 git clone 和装依赖,还可以用来:
- 复制环境配置文件
- 启动本地数据库或缓存服务
- 拉取最新的主分支代码
- 运行代码生成脚本
注意 hook 里的命令失败会导致 workspace 创建失败,所以命令要幂等且容错。
6.4 安全边界
Symphony 本身不做 sandbox 控制,依赖 Codex 和操作系统层面的安全机制。部署前确认:
- Linear API Key 的权限范围最小化
- Codex 的
thread_sandbox设为workspace-write而不是danger-full-access - workspace 目录和主仓库隔离
- Git 仓库的访问控制到位
approval_policy设为never时 Agent 可以自由执行命令,只建议在受信环境中使用。
6.5 用统一网关管理多 Agent 的模型账号
当并发 Agent 数量上去后,模型账号管理会变成一件麻烦事。每个 Agent 都要配 Key,用量分散在各个账号里,成本不好统计。用 TaoToken 这类统一网关可以把 Base URL、Key、Model ID 三件套集中管理,Codex 配置里只写一个 provider,Key 轮换和用量统计都在网关侧完成。
配置方式就是前面 3.3 节给的~/.codex/config.toml片段,把base_url指向https://taotoken.net/api,env_key指向统一的环境变量。这样多个 Agent 共享一套接入配置,新增 Agent 时不用重复配账号。
如果你想让多个 Agent 共享一套 API Key,或者想统一管理 Claude、GPT、Gemini 等多模型的用量和计费,可以看看 TaoToken 的 Coding Plan,它把模型账号管理、API Key 轮换、用量统计这些基础设施一次性解决。Symphony 跑多 Agent 任务时,配套用它来管模型账号,团队成本会更可控。
需要自己管理 Key 的话,可以在控制台创建和管理 API Key;想先验证模型连通性,可以直接在模型对话里试一条请求;接入细节参考接入文档。