Skip to main content
The agent configuration section controls how Symphony schedules and retries agent runs, including concurrency limits and turn budgets.

Configuration

WORKFLOW.md
agent:
  max_concurrent_agents: 10
  max_turns: 20
  max_retry_backoff_ms: 300000

Fields

max_concurrent_agents
integer
default:10
Global limit for concurrent agent sessions.The orchestrator enforces this limit during dispatch:
available_slots = max(max_concurrent_agents - running_count, 0)
When all slots are occupied, eligible issues wait for the next poll tick.Dynamic: Changes apply immediately to future dispatch decisions without restart.
max_turns
integer
default:20
Maximum Codex turns per agent run (per worker session).After each successful turn, the agent runner:
  1. Checks if the issue is still in an active state
  2. If active and under max_turns, starts another turn on the same thread
  3. If max_turns is reached, returns control to the orchestrator
The first turn receives the full rendered task prompt. Continuation turns receive minimal guidance:
Continuation guidance:
- The previous Codex turn completed normally, but the Linear issue is still in an active state.
- This is continuation turn #X of Y for the current agent run.
- Resume from the current workspace and workpad state instead of restarting from scratch.
- The original task instructions and prior turn context are already present in this thread.
- Focus on the remaining ticket work and do not end the turn while the issue stays active unless you are truly blocked.
Important: Reaching max_turns does not fail the run. The orchestrator schedules a continuation retry to re-check the issue state.
max_retry_backoff_ms
integer
default:300000
Maximum retry delay in milliseconds (5 minutes by default).Caps exponential backoff for failure-driven retries:
delay = min(10000 * 2^(attempt - 1), max_retry_backoff_ms)
Retry schedule examples:
  • Attempt 1: 10 seconds
  • Attempt 2: 20 seconds
  • Attempt 3: 40 seconds
  • Attempt 4: 80 seconds
  • Attempt 5: 160 seconds
  • Attempt 6+: 300 seconds (capped)
Note: Normal continuation retries after clean exits use a fixed 1-second delay, not exponential backoff.
max_concurrent_agents_by_state
object
Per-state concurrency limits (optional).Allows finer control over how many agents can run for issues in specific states:
agent:
  max_concurrent_agents: 10
  max_concurrent_agents_by_state:
    "in progress": 5
    "merging": 2
State keys are normalized (trim + lowercase) for lookup. If a state isn’t listed, the global max_concurrent_agents applies.Invalid entries (non-positive or non-numeric values) are ignored.

Concurrency Control

The orchestrator evaluates concurrency in this order:
  1. Global limit check:
    running_count < max_concurrent_agents
    
  2. Per-state limit check (if configured):
    state_running_count < max_concurrent_agents_by_state[normalized_state]
    
  3. Blocker rule (for Todo state only):
    • Issues in Todo state with non-terminal blockers are not dispatched
Example from source:
@spec max_concurrent_agents_for_state(term()) :: pos_integer()
def max_concurrent_agents_for_state(state_name) when is_binary(state_name) do
  state_limits = get_in(validated_workflow_options(), [:agent, :max_concurrent_agents_by_state])
  global_limit = max_concurrent_agents()
  Map.get(state_limits, normalize_issue_state(state_name), global_limit)
end

Retry Behavior

Continuation Retries

After a normal worker exit (no error), the orchestrator:
  1. Removes the running entry
  2. Marks the issue as completed (bookkeeping only)
  3. Schedules a continuation retry with 1-second delay
  4. On retry, re-fetches the issue state:
    • If still active → re-dispatch
    • If terminal or missing → release claim and clean workspace

Failure Retries

After abnormal worker exit (error, timeout, stall), the orchestrator:
  1. Removes the running entry
  2. Schedules a failure retry with exponential backoff
  3. Increments the attempt counter
  4. On retry, re-fetches the issue state and re-dispatches if still eligible

Retry Queue Management

Retry entries track:
  • issue_id: Tracker issue ID
  • identifier: Human-readable ID (for logs)
  • attempt: 1-based retry attempt number
  • due_at_ms: Monotonic timestamp for retry execution
  • timer_handle: Runtime timer reference
  • error: Optional error message from previous attempt
From source:
defp schedule_issue_retry(state, issue_id, attempt, opts) do
  identifier = Keyword.get(opts, :identifier, issue_id)
  error = Keyword.get(opts, :error)
  delay_type = Keyword.get(opts, :delay_type, :exponential)

  delay_ms =
    case delay_type do
      :continuation -> @continuation_retry_delay_ms
      :exponential -> exponential_backoff_ms(attempt, state.max_retry_backoff_ms)
    end

  due_at_ms = System.monotonic_time(:millisecond) + delay_ms
  timer_ref = Process.send_after(self(), {:retry_issue, issue_id}, delay_ms)

  retry_entry = %{
    issue_id: issue_id,
    identifier: identifier,
    attempt: attempt,
    due_at_ms: due_at_ms,
    timer_handle: timer_ref,
    error: error
  }

  put_in(state.retry_attempts[issue_id], retry_entry)
end

Turn Budget Example

Given max_turns: 20:
  1. Turn 1: Agent receives full task prompt, completes
  2. State check: Issue still active → continue
  3. Turn 2-19: Agent receives continuation guidance, continues work
  4. Turn 20: Agent completes final turn
  5. State check: Issue still active but max_turns reached
  6. Orchestrator action: Schedule continuation retry (1s delay)
  7. Retry fires: Re-check issue state and dispatch new worker session if still active
This allows long-running work to span multiple worker sessions without failing.

Configuration Reloading

Agent configuration changes are applied dynamically:
  • max_concurrent_agents: Affects next dispatch cycle
  • max_turns: Affects newly started agent runs
  • max_retry_backoff_ms: Affects future retry scheduling
  • max_concurrent_agents_by_state: Affects next dispatch cycle
Running agents continue with their original turn budget.

Examples

High Throughput

agent:
  max_concurrent_agents: 50
  max_turns: 10
  max_retry_backoff_ms: 60000

Conservative with Per-State Limits

agent:
  max_concurrent_agents: 10
  max_turns: 30
  max_retry_backoff_ms: 600000
  max_concurrent_agents_by_state:
    "in progress": 5
    "merging": 2
    "rework": 3

Single Agent (Sequential)

agent:
  max_concurrent_agents: 1
  max_turns: 50
  • polling - Control polling frequency
  • codex - Configure turn timeouts and stall detection
  • tracker - Define active and terminal states

Build docs developers (and LLMs) love