openai-symphony-autonomous-agents
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOpenAI Symphony
OpenAI Symphony
Skill by ara.so — Daily 2026 Skills collection.
Symphony turns project work into isolated, autonomous implementation runs, allowing teams to manage work instead of supervising coding agents. Instead of watching an agent code, you define tasks (e.g. in Linear), and Symphony spawns agents that complete them, provide proof of work (CI status, PR reviews, walkthrough videos), and land PRs autonomously.
由 ara.so 开发的Skill — 2026每日技能合集。
Symphony 将项目工作转化为独立、自主的实现运行流程,让团队能够专注于管理工作,而非监督编码Agent。你无需盯着Agent编码,只需在任务工具(如Linear)中定义任务,Symphony就会启动Agent完成任务,提供工作成果证明(CI状态、PR审核、演示视频),并自主提交PR。
What Symphony Does
Symphony 的功能
- Monitors a work tracker (e.g. Linear) for tasks
- Spawns isolated agent runs per task (using Codex or similar)
- Each agent implements the task, opens a PR, and provides proof of work
- Engineers review outcomes, not agent sessions
- Works best in codebases using harness engineering
- 监控任务追踪工具(如Linear)中的任务
- 为每个任务启动独立的Agent运行实例(使用Codex或类似模型)
- 每个Agent会完成任务、创建PR并提供工作成果证明
- 工程师只需审核结果,无需监控Agent的运行过程
- 在采用harness engineering的代码库中效果最佳
Installation Options
安装选项
Option 1: Ask an agent to build it
选项1:让Agent帮你构建
Paste this prompt into Claude Code, Cursor, or Codex:
Implement Symphony according to the following spec:
https://github.com/openai/symphony/blob/main/SPEC.md将以下提示语粘贴到Claude Code、Cursor或Codex中:
Implement Symphony according to the following spec:
https://github.com/openai/symphony/blob/main/SPEC.mdOption 2: Use the Elixir reference implementation
选项2:使用Elixir参考实现
bash
git clone https://github.com/openai/symphony.git
cd symphony/elixirFollow , or ask an agent:
elixir/README.mdSet up Symphony for my repository based on
https://github.com/openai/symphony/blob/main/elixir/README.mdbash
git clone https://github.com/openai/symphony.git
cd symphony/elixir遵循的指引,或者让Agent帮你:
elixir/README.mdSet up Symphony for my repository based on
https://github.com/openai/symphony/blob/main/elixir/README.mdElixir Reference Implementation Setup
Elixir参考实现设置
Requirements
要求
- Elixir + Mix installed
- An OpenAI API key (for Codex agent)
- A Linear API key (if using Linear integration)
- A GitHub token (for PR operations)
- 已安装Elixir + Mix
- OpenAI API密钥(用于Codex Agent)
- Linear API密钥(如果集成Linear)
- GitHub令牌(用于PR操作)
Environment Variables
环境变量
bash
export OPENAI_API_KEY="sk-..." # OpenAI API key for Codex
export LINEAR_API_KEY="lin_api_..." # Linear integration
export GITHUB_TOKEN="ghp_..." # GitHub PR operations
export SYMPHONY_REPO_PATH="/path/to/repo" # Target repositorybash
export OPENAI_API_KEY="sk-..." # 用于Codex的OpenAI API密钥
export LINEAR_API_KEY="lin_api_..." # Linear集成
export GITHUB_TOKEN="ghp_..." # GitHub PR操作
export SYMPHONY_REPO_PATH="/path/to/repo" # 目标代码库路径Install Dependencies
安装依赖
bash
cd elixir
mix deps.getbash
cd elixir
mix deps.getConfiguration (elixir/config/config.exs
)
elixir/config/config.exs配置(elixir/config/config.exs
)
elixir/config/config.exselixir
import Config
config :symphony,
openai_api_key: System.get_env("OPENAI_API_KEY"),
linear_api_key: System.get_env("LINEAR_API_KEY"),
github_token: System.get_env("GITHUB_TOKEN"),
repo_path: System.get_env("SYMPHONY_REPO_PATH", "./"),
poll_interval_ms: 30_000,
max_concurrent_agents: 3elixir
import Config
config :symphony,
openai_api_key: System.get_env("OPENAI_API_KEY"),
linear_api_key: System.get_env("LINEAR_API_KEY"),
github_token: System.get_env("GITHUB_TOKEN"),
repo_path: System.get_env("SYMPHONY_REPO_PATH", "./"),
poll_interval_ms: 30_000,
max_concurrent_agents: 3Run Symphony
启动Symphony
bash
mix symphony.startbash
mix symphony.startor in IEx for development
或在开发环境中使用IEx启动
iex -S mix
---iex -S mix
---Core Concepts
核心概念
Isolated Implementation Runs
独立实现运行流程
Each task gets its own isolated run:
- Fresh git branch per task
- Agent operates only within that branch
- No shared state between runs
- Proof of work collected before PR merge
每个任务都有独立的运行实例:
- 每个任务对应全新的Git分支
- Agent仅在该分支内操作
- 不同运行实例之间无共享状态
- PR合并前会收集工作成果证明
Proof of Work
工作成果证明
Before a PR is accepted, Symphony collects:
- CI/CD pipeline status
- PR review feedback
- Complexity analysis
- (optionally) walkthrough videos
在PR被接受前,Symphony会收集以下内容:
- CI/CD流水线状态
- PR审核反馈
- 复杂度分析
- (可选)演示视频
Key Elixir Modules & Patterns
关键Elixir模块与模式
Starting the Symphony supervisor
启动Symphony监督进程
elixir
undefinedelixir
undefinedIn your application.ex or directly
在application.ex中直接定义
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
Symphony.Supervisor
]
Supervisor.start_link(children, strategy: :one_for_one)
end
end
undefineddefmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
Symphony.Supervisor
]
Supervisor.start_link(children, strategy: :one_for_one)
end
end
undefinedDefining a Task (Symphony Task struct)
定义任务(Symphony Task结构体)
elixir
defmodule Symphony.Task do
@type t :: %__MODULE__{
id: String.t(),
title: String.t(),
description: String.t(),
source: :linear | :manual,
status: :pending | :running | :completed | :failed,
branch: String.t() | nil,
pr_url: String.t() | nil,
proof_of_work: map() | nil
}
defstruct [:id, :title, :description, :source,
status: :pending, branch: nil,
pr_url: nil, proof_of_work: nil]
endelixir
defmodule Symphony.Task do
@type t :: %__MODULE__{
id: String.t(),
title: String.t(),
description: String.t(),
source: :linear | :manual,
status: :pending | :running | :completed | :failed,
branch: String.t() | nil,
pr_url: String.t() | nil,
proof_of_work: map() | nil
}
defstruct [:id, :title, :description, :source,
status: :pending, branch: nil,
pr_url: nil, proof_of_work: nil]
endSpawning an Agent Run
启动Agent运行实例
elixir
defmodule Symphony.AgentRunner do
@doc """
Spawns an isolated agent run for a given task.
Each run gets its own branch and Codex session.
"""
def run(task) do
branch = "symphony/#{task.id}-#{slugify(task.title)}"
with :ok <- Git.create_branch(branch),
{:ok, result} <- Codex.implement(task, branch),
{:ok, pr_url} <- GitHub.open_pr(branch, task),
{:ok, proof} <- ProofOfWork.collect(pr_url) do
{:ok, %{task | status: :completed, pr_url: pr_url, proof_of_work: proof}}
else
{:error, reason} -> {:error, reason}
end
end
defp slugify(title) do
title
|> String.downcase()
|> String.replace(~r/[^a-z0-9]+/, "-")
|> String.trim("-")
end
endelixir
defmodule Symphony.AgentRunner do
@doc """
为指定任务启动独立的Agent运行实例。
每个运行实例会创建独立分支和Codex会话。
"""
def run(task) do
branch = "symphony/#{task.id}-#{slugify(task.title)}"
with :ok <- Git.create_branch(branch),
{:ok, result} <- Codex.implement(task, branch),
{:ok, pr_url} <- GitHub.open_pr(branch, task),
{:ok, proof} <- ProofOfWork.collect(pr_url) do
{:ok, %{task | status: :completed, pr_url: pr_url, proof_of_work: proof}}
else
{:error, reason} -> {:error, reason}
end
end
defp slugify(title) do
title
|> String.downcase()
|> String.replace(~r/[^a-z0-9]+/, "-")
|> String.trim("-")
end
endLinear Integration — Polling for Tasks
Linear集成 — 任务轮询
elixir
defmodule Symphony.Linear.Poller do
use GenServer
@poll_interval Application.compile_env(:symphony, :poll_interval_ms, 30_000)
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def init(_opts) do
schedule_poll()
{:ok, %{processed_ids: MapSet.new()}}
end
def handle_info(:poll, state) do
case Symphony.Linear.Client.fetch_todo_tasks() do
{:ok, tasks} ->
new_tasks = Enum.reject(tasks, &MapSet.member?(state.processed_ids, &1.id))
Enum.each(new_tasks, &Symphony.AgentRunner.run/1)
new_ids = Enum.reduce(new_tasks, state.processed_ids, &MapSet.put(&2, &1.id))
schedule_poll()
{:noreply, %{state | processed_ids: new_ids}}
{:error, reason} ->
Logger.error("Linear poll failed: #{inspect(reason)}")
schedule_poll()
{:noreply, state}
end
end
defp schedule_poll do
Process.send_after(self(), :poll, @poll_interval)
end
endelixir
defmodule Symphony.Linear.Poller do
use GenServer
@poll_interval Application.compile_env(:symphony, :poll_interval_ms, 30_000)
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def init(_opts) do
schedule_poll()
{:ok, %{processed_ids: MapSet.new()}}
end
def handle_info(:poll, state) do
case Symphony.Linear.Client.fetch_todo_tasks() do
{:ok, tasks} ->
new_tasks = Enum.reject(tasks, &MapSet.member?(state.processed_ids, &1.id))
Enum.each(new_tasks, &Symphony.AgentRunner.run/1)
new_ids = Enum.reduce(new_tasks, state.processed_ids, &MapSet.put(&2, &1.id))
schedule_poll()
{:noreply, %{state | processed_ids: new_ids}}
{:error, reason} ->
Logger.error("Linear poll failed: #{inspect(reason)}")
schedule_poll()
{:noreply, state}
end
end
defp schedule_poll do
Process.send_after(self(), :poll, @poll_interval)
end
endLinear API Client
Linear API客户端
elixir
defmodule Symphony.Linear.Client do
@linear_api "https://api.linear.app/graphql"
def fetch_todo_tasks do
query = """
query {
issues(filter: { state: { name: { eq: "Todo" } } }) {
nodes {
id
title
description
}
}
}
"""
case HTTPoison.post(@linear_api, Jason.encode!(%{query: query}), headers()) do
{:ok, %{status_code: 200, body: body}} ->
tasks =
body
|> Jason.decode!()
|> get_in(["data", "issues", "nodes"])
|> Enum.map(&to_task/1)
{:ok, tasks}
{:error, reason} ->
{:error, reason}
end
end
defp headers do
[
{"Authorization", System.get_env("LINEAR_API_KEY")},
{"Content-Type", "application/json"}
]
end
defp to_task(%{"id" => id, "title" => title, "description" => desc}) do
%Symphony.Task{id: id, title: title, description: desc, source: :linear}
end
endelixir
defmodule Symphony.Linear.Client do
@linear_api "https://api.linear.app/graphql"
def fetch_todo_tasks do
query = """
query {
issues(filter: { state: { name: { eq: "Todo" } } }) {
nodes {
id
title
description
}
}
}
"""
case HTTPoison.post(@linear_api, Jason.encode!(%{query: query}), headers()) do
{:ok, %{status_code: 200, body: body}} ->
tasks =
body
|> Jason.decode!()
|> get_in(["data", "issues", "nodes"])
|> Enum.map(&to_task/1)
{:ok, tasks}
{:error, reason} ->
{:error, reason}
end
end
defp headers do
[
{"Authorization", System.get_env("LINEAR_API_KEY")},
{"Content-Type", "application/json"}
]
end
defp to_task(%{"id" => id, "title" => title, "description" => desc}) do
%Symphony.Task{id: id, title: title, description: desc, source: :linear}
end
endProof of Work Collection
工作成果证明收集
elixir
defmodule Symphony.ProofOfWork do
@doc """
Collects proof of work for a PR before it can be merged.
Returns a map with CI status, review feedback, and complexity.
"""
def collect(pr_url) do
with {:ok, ci_status} <- wait_for_ci(pr_url),
{:ok, reviews} <- fetch_reviews(pr_url),
{:ok, complexity} <- analyze_complexity(pr_url) do
{:ok, %{
ci_status: ci_status,
reviews: reviews,
complexity: complexity,
collected_at: DateTime.utc_now()
}}
end
end
defp wait_for_ci(pr_url, retries \\ 30) do
case GitHub.get_pr_ci_status(pr_url) do
{:ok, :success} -> {:ok, :success}
{:ok, :pending} when retries > 0 ->
Process.sleep(60_000)
wait_for_ci(pr_url, retries - 1)
{:ok, status} -> {:ok, status}
{:error, reason} -> {:error, reason}
end
end
defp fetch_reviews(pr_url), do: GitHub.get_pr_reviews(pr_url)
defp analyze_complexity(pr_url), do: GitHub.get_pr_diff_complexity(pr_url)
endelixir
defmodule Symphony.ProofOfWork do
@doc """
在PR合并前收集工作成果证明。
返回包含CI状态、审核反馈和复杂度的映射表。
"""
def collect(pr_url) do
with {:ok, ci_status} <- wait_for_ci(pr_url),
{:ok, reviews} <- fetch_reviews(pr_url),
{:ok, complexity} <- analyze_complexity(pr_url) do
{:ok, %{
ci_status: ci_status,
reviews: reviews,
complexity: complexity,
collected_at: DateTime.utc_now()
}}
end
end
defp wait_for_ci(pr_url, retries \\ 30) do
case GitHub.get_pr_ci_status(pr_url) do
{:ok, :success} -> {:ok, :success}
{:ok, :pending} when retries > 0 ->
Process.sleep(60_000)
wait_for_ci(pr_url, retries - 1)
{:ok, status} -> {:ok, status}
{:error, reason} -> {:error, reason}
end
end
defp fetch_reviews(pr_url), do: GitHub.get_pr_reviews(pr_url)
defp analyze_complexity(pr_url), do: GitHub.get_pr_diff_complexity(pr_url)
endImplementing the SPEC.md (Custom Implementation)
实现SPEC.md(自定义实现)
When building Symphony in another language, the spec defines:
- Task Source — poll Linear/GitHub/Jira for tasks in a specific state
- Agent Invocation — call Codex (or another agent) with task context
- Isolation — each run on a fresh branch, containerized if possible
- Proof of Work — CI, review, and analysis before merge
- Landing — auto-merge or present to engineer for approval
Minimal implementation loop in pseudocode:
elixir
undefined当用其他语言构建Symphony时,该规范定义了以下内容:
- 任务源 — 轮询Linear/GitHub/Jira中特定状态的任务
- Agent调用 — 结合任务上下文调用Codex(或其他Agent)
- 隔离性 — 每个运行实例使用全新分支,尽可能采用容器化
- 工作成果证明 — 合并前需验证CI、审核和分析结果
- 交付 — 自动合并或提交给工程师审批
伪代码实现的最小循环:
elixir
undefinedCore symphony loop
Symphony核心循环
def symphony_loop(state) do
tasks = fetch_new_tasks(state.source)
tasks
|> Enum.filter(&(&1.status == :todo))
|> Enum.each(fn task ->
Task.async(fn ->
branch = create_isolated_branch(task)
invoke_agent(task, branch) # Codex / Claude / etc.
proof = collect_proof_of_work(branch)
present_for_review(task, proof)
end)
end)
Process.sleep(state.poll_interval)
symphony_loop(state)
end
---def symphony_loop(state) do
tasks = fetch_new_tasks(state.source)
tasks
|> Enum.filter(&(&1.status == :todo))
|> Enum.each(fn task ->
Task.async(fn ->
branch = create_isolated_branch(task)
invoke_agent(task, branch) # Codex / Claude / 其他模型
proof = collect_proof_of_work(branch)
present_for_review(task, proof)
end)
end)
Process.sleep(state.poll_interval)
symphony_loop(state)
end
---Common Patterns
常见模式
Limiting Concurrent Agent Runs
限制并发Agent运行实例数量
elixir
defmodule Symphony.AgentPool do
use GenServer
@max_concurrent 3
def start_link(_), do: GenServer.start_link(__MODULE__, %{running: 0, queue: []}, name: __MODULE__)
def submit(task) do
GenServer.cast(__MODULE__, {:submit, task})
end
def handle_cast({:submit, task}, %{running: n} = state) when n < @max_concurrent do
spawn_agent(task)
{:noreply, %{state | running: n + 1}}
end
def handle_cast({:submit, task}, %{queue: q} = state) do
{:noreply, %{state | queue: q ++ [task]}}
end
def handle_info({:agent_done, _result}, %{running: n, queue: [next | rest]} = state) do
spawn_agent(next)
{:noreply, %{state | running: n, queue: rest}}
end
def handle_info({:agent_done, _result}, %{running: n} = state) do
{:noreply, %{state | running: n - 1}}
end
defp spawn_agent(task) do
parent = self()
spawn(fn ->
result = Symphony.AgentRunner.run(task)
send(parent, {:agent_done, result})
end)
end
endelixir
defmodule Symphony.AgentPool do
use GenServer
@max_concurrent 3
def start_link(_), do: GenServer.start_link(__MODULE__, %{running: 0, queue: []}, name: __MODULE__)
def submit(task) do
GenServer.cast(__MODULE__, {:submit, task})
end
def handle_cast({:submit, task}, %{running: n} = state) when n < @max_concurrent do
spawn_agent(task)
{:noreply, %{state | running: n + 1}}
end
def handle_cast({:submit, task}, %{queue: q} = state) do
{:noreply, %{state | queue: q ++ [task]}}
end
def handle_info({:agent_done, _result}, %{running: n, queue: [next | rest]} = state) do
spawn_agent(next)
{:noreply, %{state | running: n, queue: rest}}
end
def handle_info({:agent_done, _result}, %{running: n} = state) do
{:noreply, %{state | running: n - 1}}
end
defp spawn_agent(task) do
parent = self()
spawn(fn ->
result = Symphony.AgentRunner.run(task)
send(parent, {:agent_done, result})
end)
end
endManual Task Injection (No Linear)
手动注入任务(无需Linear)
elixir
undefinedelixir
undefinedIn IEx or a Mix task
在IEx或Mix任务中执行
Symphony.AgentPool.submit(%Symphony.Task{
id: "manual-001",
title: "Add rate limiting to API",
description: "Implement token bucket rate limiting on /api/v1 endpoints",
source: :manual
})
---Symphony.AgentPool.submit(%Symphony.Task{
id: "manual-001",
title: "Add rate limiting to API",
description: "Implement token bucket rate limiting on /api/v1 endpoints",
source: :manual
})
---Troubleshooting
故障排查
| Problem | Likely Cause | Fix |
|---|---|---|
| Agents not spawning | Missing | Check env var is exported |
| Linear tasks not detected | Wrong Linear state filter | Update query filter to match your board's state name |
| PRs not opening | Missing | Verify token has |
| CI never completes | Timeout too short | Increase |
| Too many concurrent runs | Default pool size | Set |
| Branch conflicts | Agent reusing branch names | Ensure task IDs are unique per run |
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| Agent未启动 | 缺少 | 检查是否已导出该环境变量 |
| Linear任务未被检测到 | Linear状态过滤器错误 | 更新查询过滤器以匹配你的看板状态名称 |
| PR无法创建 | 缺少 | 验证令牌是否拥有 |
| CI始终未完成 | 超时时间过短 | 增加 |
| 并发运行实例过多 | 默认池大小设置问题 | 在配置中设置 |
| 分支冲突 | Agent重复使用分支名称 | 确保每个运行实例的任务ID唯一 |
Debug Mode
调试模式
elixir
undefinedelixir
undefinedIn config/dev.exs
在config/dev.exs中配置
config :symphony, log_level: :debug
config :symphony, log_level: :debug
Or at runtime
或在运行时设置
Logger.put_module_level(Symphony.AgentRunner, :debug)
Logger.put_module_level(Symphony.Linear.Poller, :debug)
---Logger.put_module_level(Symphony.AgentRunner, :debug)
Logger.put_module_level(Symphony.Linear.Poller, :debug)
---Resources
资源
- SPEC.md — Full Symphony specification
- elixir/README.md — Elixir setup guide
- Harness Engineering — Prerequisite methodology
- Apache 2.0 License
- SPEC.md — Symphony完整规范
- elixir/README.md — Elixir设置指南
- Harness Engineering — 必备方法论
- Apache 2.0 License