Appearance
Symphony(编排器 / Orchestrator)
这一页是“第一层(使用者/运维/联调)”里对 Symphony 的系统性说明:它解决什么问题、运行时的核心循环是什么、你需要准备哪些配置、以及如何用它给 Web UI 提供状态数据。
如果你想了解官方设计与约束(为什么这么做、哪些行为是规范的一部分),请看:
- Symphony SPEC 解析(Reference)
- 上游 SPEC 原文:
openai/symphony的SPEC.md(本页会按它的结构来解释“怎么用”)
0. 一句话理解 Symphony
Symphony 是一个控制循环(control loop):
- 周期性从 Tracker(目前是 Linear)拉取候选 Issue
- 根据调度规则挑选一些 Issue 分配给 Agent
- 为每个 Issue 准备 workspace,并驱动 Codex “turn” 一次次执行
- 当 Issue 不再处于 active 状态或达到上限时停止,并按策略重试/清理
对你来说,Symphony 最核心的输出就是一个可以给 UI 消费的 Snapshot(运行中/重试中/累计用量等)。
1. Symphony 解决的“具体问题”
当你希望让一个 LLM Agent 自动推进工作时,通常会遇到这些现实问题:
- 从哪里取任务?(Tracker:Linear)
- 怎么做并发控制?(全局并发、按状态并发、避免重复派发)
- 怎么保证每个任务有稳定的工作目录?(workspace 与 hook)
- 怎么避免卡死?(stall timeout)
- 怎么把运行状态暴露给 UI?(health + snapshot)
- 怎么处理失败与重试?(continuation/backoff)
Symphony 不是“执行器”本身:它主要负责调度与生命周期;实际干活的是 Codex app-server 驱动下的 Agent。
2. 核心术语(建议先读完这节)
Issue(任务单)
Symphony 的输入对象是 Tracker 的 Issue。你至少会关心:
identifier:像ABC-123(用于 workspace key、日志、UI 展示)state:任务状态(用于调度与停止条件)blocked_by:阻塞关系(影响 Todo 的派发规则)
Active / Terminal 状态
你在 WORKFLOW.md 里配置两组状态:
tracker.active_states:允许派发/继续运行的状态(例如Todo,In Progress)tracker.terminal_states:认为已终结的状态(例如Done,Canceled)
实际运行里,Symphony 会在每轮/每次 attempt 后从 Tracker 刷新 issue state,决定是否继续。
Attempt vs Turn
- Turn:一次 Codex 调用(一次“对话回合”)
- Attempt:一次“从 workspace → 多个 turn → 结束”的执行周期
在 Synclax 的实现里,一个 attempt 会最多跑 agent.max_turns 个 turn;每个 turn 完成后会刷新 issue 状态,如果不再 active 则提前结束。
Workspace
每个 issue identifier 都映射到一个稳定的 workspace 路径(可复用)。workspace 根目录由 workspace.root 决定。
你可以把 workspace 当作:
- Agent 的当前工作目录(cwd)
- hook 脚本执行的目录(例如 clone repo、make gen)
- “任务连续执行”的持久上下文(继续运行时不会丢失文件)
Snapshot(给 UI 的状态投影)
Snapshot 是 Symphony 运行时状态的“可序列化结构”,核心用于 UI。
在 Synclax 里你应该优先使用主 API Server:
GET /api/v1/symphony/snapshot
(可选)如果你开启了 Symphony debug server(仅本机 127.0.0.1),也能拿到:
GET http://127.0.0.1:<port>/snapshot
Snapshot 字段补充:completed[] 与持久化
为了让 UI 能展示“刚刚发生了什么”(以及在服务重启后仍能看到最近历史),Synclax 额外提供了:
completed[]:最近的 attempt 结束记录(成功/失败/取消/超时等),用于 UI 的“历史/日志”视图
同时,Synclax 会把一部分运行数据落盘(best-effort,不保证 100% 写入):
- 持久化目录:
<workspace.root>/.symphony_state/totals.json:codex_totals与rate_limits的最近一次快照(用于重启后恢复累计值)attempts.jsonl:最近 attempt 历史(JSON Lines,每行一个completedentry)
这套持久化的目标不是“强一致的审计账本”,而是:
- 重启后 UI 不至于完全丢失“刚刚跑过什么”
- 让累计 token/时间等指标不会因为重启而清零(或至少能尽量恢复)
3. 你需要准备什么(最小可用)
3.1 Tracker:Linear
要让 Symphony 真正开始派发任务,你至少需要:
tracker.project_slugtracker.api_key(推荐写$LINEAR_API_KEY)
并在环境变量里设置:
bash
export LINEAR_API_KEY='你的 Linear API Key'3.2 Codex app-server
在 WORKFLOW.md 中:
codex.command默认为codex app-server
你要确保运行环境里存在该命令,并且它能在 workspace cwd 下工作。
4. 运行与控制:你应该怎么“用” Symphony
在 Synclax 里,我们把 Symphony 做成了“可控模块”,可以通过 API 启动/停止,并给 UI 暴露 snapshot。
4.1 健康检查(UI 的第一个依赖点)
bash
curl -sS http://localhost:2910/api/v1/health | jq .这个 endpoint 目标是稳定(服务活着就返回 200),并附带 Symphony 是否在运行。
4.2 启动 Symphony
bash
curl -sS -X POST http://localhost:2910/api/v1/symphony/start | jq .也可以覆盖 workflow 与 debug server 端口:
bash
curl -sS -X POST http://localhost:2910/api/v1/symphony/start \
-H "Content-Type: application/json" \
-d '{"workflow_path":"./WORKFLOW.md","http_port":8089}' | jq .TIP
http_port: -1 会强制关闭 Symphony debug server(即使 WORKFLOW.md 设置了 server.port)。
4.3 UI 轮询 Snapshot
bash
curl -sS http://localhost:2910/api/v1/symphony/snapshot | jq .UI 实战建议(第一层给一个默认答案):
- 轮询间隔:
1s ~ 3s - 当
running为空且retrying为空:UI 显示“空闲/等待候选任务” - 当
retrying非空:UI 展示“等待下一次 due_at” - 当
running非空:UI 重点展示phase与workspace_path
4.4 停止 Symphony
bash
curl -sS -X POST http://localhost:2910/api/v1/symphony/stop | jq .5. 调度与执行:Symphony 内部到底怎么跑(面向“怎么配置/怎么解释现象”)
这一节是为了让你能读懂 snapshot、读懂日志、并知道应该调哪个参数。
5.1 调度入口:候选 Issue
Symphony 会周期性调用 Tracker 拉取候选 issue(候选集合通常限制在 active states)。
5.2 排序与派发
派发之前会排序(Synclax 当前实现的排序规则):
priority(越小越优先;缺失的排后)created_at(越早越优先;缺失的排后)identifier(稳定排序用)
然后按并发上限派发:
- 全局并发:
agent.max_concurrent_agents - 按 state 并发:
agent.max_concurrent_agents_by_state(例如In Progress: 2)
5.3 Todo blocker 规则(非常常见的“为什么不派发”原因)
当 issue state 为 Todo 时,如果它有 blocker 且 blocker 不是 terminal,Symphony 不会派发它。
这条规则用来避免“明知被阻塞仍强行执行”的浪费。
5.4 attempt 执行的阶段(phase)
为了让 UI 以及 debug 更清晰,Synclax 会在 attempt 生命周期里更新 phase:
PreparingWorkspace:创建/复用 workspaceLaunchingAgentProcess:启动 codex app-server 进程InitializingSession:创建 thread/session(会填 thread_id/pid 等)BuildingPrompt:构造 prompt(首回合用模板;后续回合用 continuation)StreamingTurn:正在执行 turnFinishing:收尾
你可以把这些 phase 当作“给 UI 展示的运行进度条”。
5.5 Prompt 模板与 continuation
WORKFLOW.md 的正文是 Liquid 模板:
- 首回合:使用模板渲染(包含 issue 信息与 attempt)
- 后续回合:不再重复完整 prompt,只发送 continuation 指令(避免 token 浪费和重复上下文)
这也是为什么你会看到“attempt 成功后又会继续跑”的现象:continuation retry 会在 issue 仍 active 时再次触发执行。
5.6 重试策略:continuation vs backoff
- attempt 成功且 issue 仍 active → 进入 continuation retry(短延迟再次执行)
- attempt 失败 → 进入 backoff retry(指数退避,且有上限)
5.7 Stall(卡死检测)
如果长时间没有收到 Codex event(或没有新的活动迹象),Symphony 会判定该运行可能卡死,并取消它。
这个时间由 codex.stall_timeout_ms 控制:
<= 0表示关闭 stall 检测
6. Workspace Hooks(非常重要,但也最容易踩坑)
你可以在 WORKFLOW.md 中为 workspace 生命周期配置 hook 脚本(bash -lc 执行):
after_create:仅首次创建 workspace 时执行(失败 fatal)before_run:每次 attempt 前执行(失败 fatal)after_run:每次 attempt 后执行(失败忽略,仅记录日志)before_remove:删除 workspace 前执行(失败忽略)
建议(第一层给可操作的“规范”):
- hook 要可重复执行(幂等)
- hook 要尽量快,必要时调大
hooks.timeout_ms - 不要在 hook 里输出敏感信息(会进日志)
WARNING
hook 是本地执行脚本,相当于“在 orchestrator 进程里执行任意命令”。把它当作受信任运维脚本,不要让不受信任用户可写 WORKFLOW.md。
7. Debug HTTP server(仅本机)
上游 SPEC 定义了内部 debug server 的两个端点:
/healthz/snapshot
Synclax 保留了这一能力,但把“对 UI 稳定开放的接口”放在主 API Server 的 /api/v1/... 下。
推荐:
- Web UI / 外部系统:用
/api/v1/symphony/snapshot - 本机 debug:需要时再开
server.port或http_port
Concepts
Active vs terminal states
Symphony uses two lists from WORKFLOW.md:
tracker.active_states: issues in these states are eligible to run (e.g.Todo,In Progress)tracker.terminal_states: issues in these states are considered done/canceled and will be cleaned up
Workspaces
Each issue identifier maps to a stable workspace key (sanitized). The workspace root comes from:
workspace.rootinWORKFLOW.md(with~expansion)
Optional hooks (bash scripts) can run on workspace lifecycle events:
hooks.after_create(fatal)hooks.before_run(fatal)hooks.after_run(best-effort)hooks.before_remove(best-effort)
Turns + retries
- A “successful attempt” (no error) schedules a continuation retry after a short delay if the issue is still active.
- A “failed attempt” schedules a backoff retry (exponential, capped).
Control (HTTP)
The Anclax API exposes a stable set of endpoints for the UI:
GET /api/v1/healthGET /api/v1/symphony/snapshotPOST /api/v1/symphony/startPOST /api/v1/symphony/stop
See HTTP API.
Debug HTTP server (optional)
Symphony also has an internal debug HTTP server (intended for local-only inspection), bound to 127.0.0.1:
GET /healthz→okGET /snapshot→ JSON snapshot
How it’s configured:
WORKFLOW.mdserver.portenables it- Synclax can override it via
POST /api/v1/symphony/startbodyhttp_port- set
http_port: -1to force-disable even ifWORKFLOW.mdenables it
- set
WARNING
For web UIs, prefer the Anclax API endpoints under /api/v1/.... The debug server is local-only (127.0.0.1) by design.