openai-symphony-autonomous-agents

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OpenAI 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.md

Option 2: Use the Elixir reference implementation

选项2:使用Elixir参考实现

bash
git clone https://github.com/openai/symphony.git
cd symphony/elixir
Follow
elixir/README.md
, or ask an agent:
Set up Symphony for my repository based on
https://github.com/openai/symphony/blob/main/elixir/README.md

bash
git clone https://github.com/openai/symphony.git
cd symphony/elixir
遵循
elixir/README.md
的指引,或者让Agent帮你:
Set up Symphony for my repository based on
https://github.com/openai/symphony/blob/main/elixir/README.md

Elixir 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 repository
bash
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.get
bash
cd elixir
mix deps.get

Configuration (
elixir/config/config.exs
)

配置(
elixir/config/config.exs

elixir
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: 3
elixir
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: 3

Run Symphony

启动Symphony

bash
mix symphony.start
bash
mix symphony.start

or 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
undefined
elixir
undefined

In 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
undefined
defmodule MyApp.Application do use Application
def start(_type, _args) do children = [ Symphony.Supervisor ] Supervisor.start_link(children, strategy: :one_for_one) end end
undefined

Defining 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]
end
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]
end

Spawning 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
end
elixir
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
end

Linear 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
end
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
end

Linear 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
end
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
end

Proof 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)
end

elixir
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)
end

Implementing the SPEC.md (Custom Implementation)

实现SPEC.md(自定义实现)

When building Symphony in another language, the spec defines:
  1. Task Source — poll Linear/GitHub/Jira for tasks in a specific state
  2. Agent Invocation — call Codex (or another agent) with task context
  3. Isolation — each run on a fresh branch, containerized if possible
  4. Proof of Work — CI, review, and analysis before merge
  5. Landing — auto-merge or present to engineer for approval
Minimal implementation loop in pseudocode:
elixir
undefined
当用其他语言构建Symphony时,该规范定义了以下内容:
  1. 任务源 — 轮询Linear/GitHub/Jira中特定状态的任务
  2. Agent调用 — 结合任务上下文调用Codex(或其他Agent)
  3. 隔离性 — 每个运行实例使用全新分支,尽可能采用容器化
  4. 工作成果证明 — 合并前需验证CI、审核和分析结果
  5. 交付 — 自动合并或提交给工程师审批
伪代码实现的最小循环:
elixir
undefined

Core 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
end
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
end

Manual Task Injection (No Linear)

手动注入任务(无需Linear)

elixir
undefined
elixir
undefined

In 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

故障排查

ProblemLikely CauseFix
Agents not spawningMissing
OPENAI_API_KEY
Check env var is exported
Linear tasks not detectedWrong Linear state filterUpdate query filter to match your board's state name
PRs not openingMissing
GITHUB_TOKEN
or wrong repo
Verify token has
repo
scope
CI never completesTimeout too shortIncrease
retries
in
wait_for_ci/2
Too many concurrent runsDefault pool sizeSet
max_concurrent_agents
in config
Branch conflictsAgent reusing branch namesEnsure task IDs are unique per run
问题可能原因解决方法
Agent未启动缺少
OPENAI_API_KEY
检查是否已导出该环境变量
Linear任务未被检测到Linear状态过滤器错误更新查询过滤器以匹配你的看板状态名称
PR无法创建缺少
GITHUB_TOKEN
或代码库路径错误
验证令牌是否拥有
repo
权限
CI始终未完成超时时间过短增加
wait_for_ci/2
中的
retries
参数
并发运行实例过多默认池大小设置问题在配置中设置
max_concurrent_agents
参数
分支冲突Agent重复使用分支名称确保每个运行实例的任务ID唯一

Debug Mode

调试模式

elixir
undefined
elixir
undefined

In 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

资源