Symphony

repository·main·Indexed 12 days ago

https://github.com/openai/symphony

A framework for managing autonomous coding agents by treating project tasks as isolated implementation runs. Symphony Elixir acts as an orchestrator that connects task trackers—including Linear, GitHub Issues, Jira Cloud, Asana, and GitLab—to Codex in App Server mode to automate issue resolution and verify proof of work.

Tokens
31.3K
Snippets
75
Records
149
Agent score
97%

What's inside Symphony

  1. What is Symphony

    main

    Symphony is a system designed to turn project work into isolated, autonomous implementation runs. Instead of supervising individual coding agents, teams use Symphony to manage work at a higher level.

    In a typical workflow, Symphony can monitor task management tools (like Linear) to identify work, spawn agents to handle tasks, and provide 'proof of work' such as:

    • CI status
    • PR review feedback
    • Complexity analysis
    • Walkthrough videos

    Once the work is accepted, agents can safely land Pull Requests (PRs).

    Note: Symphony is currently a low-key engineering preview intended for testing in trusted environments.

  2. Access the Symphony Web Dashboard

    main

    If Symphony is started with the --port flag, an observability UI is available via a Phoenix/LiveView stack.

    Endpoints

    • /: The LiveView dashboard.
    • /api/v1/state: JSON API for operational debugging.
    • /api/v1/<issue_identifier>: JSON API for specific issue state.
    • /api/v1/refresh: JSON API to trigger a refresh.
  3. Understand the Symphony service model

    main

    Symphony is a long-running automation service designed to orchestrate coding agents to perform project work. It operates by continuously polling a configured issue tracker, creating an isolated workspace for each identified issue, and running a coding agent session within that workspace.

    Key operational characteristics:

    • Repeatable Workflow: Converts manual issue execution into a daemonized process.
    • Isolation: Each issue runs in its own dedicated workspace directory to prevent command cross-contamination.
    • In-Repo Policy: Workflow logic, including agent prompts and runtime settings, is versioned alongside code in a WORKFLOW.md file.
    • Observability: Provides structured logs to monitor and debug concurrent agent runs.
    • Handoffs: A run can conclude at a workflow-defined state (e.g., Human Review) rather than just Done.
  4. Understand Symphony recovery behavior

    main

    Symphony implements specific recovery strategies based on the type of failure encountered:

    Failure TypeRecovery Behavior
    Dispatch validation failuresSkip new dispatches; keep service alive; continue reconciliation where possible.
    Worker failuresConvert to retries with exponential backoff.
    Tracker candidate-fetch failuresSkip the current tick; try again on the next tick.
    Reconciliation state-refresh failuresKeep current workers; retry on the next tick.
    Dashboard/log failuresDo not crash the orchestrator.

    Note on Restarts: The scheduler state is intentionally in-memory. A process restart does not restore retry timers, running sessions, or live worker state. After a restart, the service recovers by cleaning up terminal workspaces, polling active issues, and re-dispatching eligible work.

  5. How Symphony components work together

    main

    Symphony is organized into several functional layers that separate policy from execution:

    1. Policy Layer: Defined in the repository via WORKFLOW.md (contains the prompt body and team-specific rules).
    2. Configuration Layer: Parses WORKFLOW.md front matter into typed runtime settings and handles defaults.
    3. Coordination Layer (Orchestrator): Manages the polling loop, issue eligibility, concurrency limits, retries, and reconciliation.
    4. Execution Layer: Manages the filesystem lifecycle via the Workspace Manager and launches the coding agent via the Agent Runner.
    5. Integration Layer (Issue Tracker Adapter): Normalizes tracker data (issues, states) into a stable model and provides provider-native tools.
    6. Observability Layer: Provides visibility through structured logs and an optional Status Surface (e.g., terminal output or dashboards).
  6. Render prompt templates with `issue` and `attempt` variables

    main

    The Markdown body of WORKFLOW.md is treated as a template. Symphony uses a strict template engine (Liquid-compatible). Unknown variables or filters will cause a rendering failure.

    Available Variables:

    • issue (object): Contains normalized issue fields, including labels and blockers.
    • attempt (integer or null): The current attempt number. It is null or absent on the first attempt, and an integer on retries/continuations.
    # Prompt Template
    
    Issue: {{ issue.title }}
    Labels: {{ issue.labels | join: ', ' }}
    
    {% if attempt %}
    This is attempt #{{ attempt }}.
    {% else %}
    This is the first attempt.
    {% endif %}
  7. Understand the Run Attempt Lifecycle

    main

    A single run attempt progresses through several distinct phases. Identifying the terminal phase is critical for debugging and retry logic.

    Lifecycle Phases:

    1. PreparingWorkspace
    2. BuildingPrompt
    3. LaunchingAgentProcess
    4. InitializingSession
    5. StreamingTurn
    6. Finishing

    Terminal States:

    • Succeeded
    • Failed
    • TimedOut
    • Stalled
    • CanceledByReconciliation
  8. Configure Workflow file path and reloading

    main

    Symphony uses a WORKFLOW.md file for configuration and prompt templates.

    Path Precedence:

    1. An explicit runtime path provided via the CLI.
    2. The current working directory (CWD) default: ./WORKFLOW.md.

    Behavior:

    • Changes to the workflow file are detected and trigger a re-read/re-apply without requiring a service restart.
    • If an invalid reload occurs, the system keeps the last known good configuration and emits an operator-visible error.
    • Missing WORKFLOW.md (when no explicit path is provided) results in a typed error.
  9. Process Streaming Turns and Continuations

    main

    The client must process app-server updates according to the targeted protocol until the turn terminates.

    Completion/Failure Conditions:

    • Success: Targeted-protocol turn completion signal.
    • Failure: Targeted-protocol turn failure, cancellation, subprocess exit, or a turn_timeout_ms (silence timeout).

    Continuation Logic: If the worker decides to continue after a successful turn, it should start a new turn on the same live thread. The app-server subprocess should remain alive across these turns and only be stopped when the worker run ends.

  10. Handle Worker Exits and Retries

    main

    Symphony manages worker failures and completions through a retry mechanism. When a worker exits, the on_worker_exit handler determines the next step based on the exit reason:

    • Normal Exit: If the worker exits normally, the issue is marked as completed in the state, and a continuation retry is scheduled.
    • Error Exit: If the worker exits with an error, a retry is scheduled using the next_attempt_from logic, passing the error reason.

    Retry Execution: When a retry timer triggers (on_retry_timer), the service:

    1. Pops the retry entry from the state.
    2. Refreshes the issue state from the tracker.
    3. Verifies if the issue still exists and if dispatching is allowed.
    4. Checks for available orchestrator slots.
    5. If all conditions are met, it calls dispatch_issue to start a new worker attempt.
  11. How Worker Attempts and Agent Turns Work

    main

    A worker attempt (run_agent_attempt) is the execution unit for a single issue. It manages the lifecycle of a workspace, a session, and multiple agent turns:

    1. Workspace Creation: A unique workspace is created for the issue's identifier.
    2. Hooks: The service executes a before_run hook on the workspace path.
    3. Session Management: An app_server session is started using the workspace path.
    4. The Turn Loop: The worker enters a loop that continues until the issue reaches a terminal state, the issue is no longer routable, or max_turns (defined in config) is reached.
      • Prompt Building: A prompt is constructed using the workflow template, issue data, and current turn number.
      • Turn Execution: app_server.run_turn executes the prompt. It accepts an on_message callback to send updates back to the orchestrator.
      • State Refresh: After each turn, the worker refreshes the issue state from the tracker to check if work is complete.
    5. Cleanup: Once the loop terminates, the session is stopped, and an after_run hook is executed (best-effort) to clean up the workspace.
    // Simplified Worker Attempt Lifecycle
    function run_agent_attempt(issue, attempt, orchestrator_channel):
      workspace = workspace_manager.create_for_issue(issue.identifier)
      run_hook("before_run", workspace.path)
      
      session = app_server.start_session(workspace=workspace.path)
      
      while turn_number < max_turns:
        prompt = build_turn_prompt(workflow_template, issue, attempt, turn_number, max_turns)
        turn_result = app_server.run_turn(
          session=session,
          prompt=prompt,
          issue=issue,
          on_message=(msg) -> send(orchestrator_channel, {codex_update, issue.id, msg})
        )
        // ... refresh issue state and check loop conditions
    
      app_server.stop_session(session)
      run_hook_best_effort("after_run", workspace.path)
  12. How prompt construction and context assembly works

    main

    Symphony assembles prompts for agents using specific inputs and rendering rules to ensure consistency across retries.

    Inputs to Prompt Rendering:

    • workflow.prompt_template: The template defining the prompt.
    • issue: The normalized issue object.
    • attempt (Optional): An integer representing the 1-based retry/continuation count.

    Rendering Rules:

    • Use strict variable and filter checking.
    • Convert issue object keys to strings for template compatibility.
    • Preserve nested arrays and maps (e.g., labels, blockers) to allow templates to iterate over them.

    Retry/Continuation Semantics:

    • First run: attempt is null or absent.
    • Subsequent runs: attempt is an integer (1, 2, 3...).
    • Note: The attempt value does not distinguish between a normal continuation and an error/timeout retry; it is a simple count.

    Failure Semantics:

    • If prompt rendering fails, the run attempt fails immediately, and the orchestrator decides the retry behavior.