From c23d7944b70e0cde59c6195b5c0f7eb0382fd3d4 Mon Sep 17 00:00:00 2001 From: tianhei Date: Thu, 30 Apr 2026 07:02:31 +0800 Subject: [PATCH 1/2] Default Symphony concurrency to five workers The previous global default of ten concurrent agents was too aggressive for the expected out-of-the-box operating profile. Lower the runtime schema, installer-generated custom and symphony-dev profiles, local workflow contract, and dashboard snapshots to five while leaving explicitly conservative starter profiles at one worker. Constraint: Starter and review-gated onboarding profiles intentionally remain at one concurrent agent for first production-like runs Rejected: Only change elixir/WORKFLOW.md | generated and schema-default workflows would still produce ten workers Confidence: high Scope-risk: narrow Tested: make -C elixir all Tested: mix test test/symphony_elixir/workspace_and_config_test.exs:722 test/symphony_elixir/installer_apply_test.exs:45 Tested: UPDATE_SNAPSHOTS=1 mix test test/symphony_elixir/status_dashboard_snapshot_test.exs Co-authored-by: Codex --- SPEC.md | 2 +- elixir/WORKFLOW.md | 2 +- elixir/lib/symphony_elixir/config/schema.ex | 2 +- elixir/lib/symphony_elixir/installer/workflow_profile.ex | 4 ++-- .../status_dashboard_snapshots/backoff_queue.evidence.md | 2 +- .../status_dashboard_snapshots/backoff_queue.snapshot.txt | 2 +- .../status_dashboard_snapshots/credits_unlimited.evidence.md | 2 +- .../status_dashboard_snapshots/credits_unlimited.snapshot.txt | 2 +- .../test/fixtures/status_dashboard_snapshots/idle.evidence.md | 2 +- .../fixtures/status_dashboard_snapshots/idle.snapshot.txt | 2 +- .../idle_with_dashboard_url.evidence.md | 2 +- .../idle_with_dashboard_url.snapshot.txt | 2 +- .../status_dashboard_snapshots/super_busy.evidence.md | 2 +- .../status_dashboard_snapshots/super_busy.snapshot.txt | 2 +- elixir/test/support/test_support.exs | 2 +- elixir/test/symphony_elixir/installer_apply_test.exs | 1 + elixir/test/symphony_elixir/workspace_and_config_test.exs | 2 +- 17 files changed, 18 insertions(+), 17 deletions(-) diff --git a/SPEC.md b/SPEC.md index 57dc3d3b7a..9092a68099 100644 --- a/SPEC.md +++ b/SPEC.md @@ -583,7 +583,7 @@ not require recognizing or validating extension fields unless that extension is - `hooks.after_run`: shell script or null - `hooks.before_remove`: shell script or null - `hooks.timeout_ms`: integer, default `60000` -- `agent.max_concurrent_agents`: integer, default `10` +- `agent.max_concurrent_agents`: integer, default `5` - `agent.max_turns`: integer, default `20` - `agent.max_retry_backoff_ms`: integer, default `300000` (5m) - `agent.max_concurrent_agents_by_state`: map of positive integers, default `{}` diff --git a/elixir/WORKFLOW.md b/elixir/WORKFLOW.md index 09b493fd9c..3df8d079ae 100644 --- a/elixir/WORKFLOW.md +++ b/elixir/WORKFLOW.md @@ -27,7 +27,7 @@ hooks: before_remove: | cd elixir && mise exec -- mix workspace.before_remove agent: - max_concurrent_agents: 10 + max_concurrent_agents: 5 max_turns: 20 codex: command: codex --config shell_environment_policy.inherit=all --config model_reasoning_effort=xhigh --model gpt-5.3-codex app-server diff --git a/elixir/lib/symphony_elixir/config/schema.ex b/elixir/lib/symphony_elixir/config/schema.ex index b1efb97773..1194d81070 100644 --- a/elixir/lib/symphony_elixir/config/schema.ex +++ b/elixir/lib/symphony_elixir/config/schema.ex @@ -129,7 +129,7 @@ defmodule SymphonyElixir.Config.Schema do @primary_key false embedded_schema do - field(:max_concurrent_agents, :integer, default: 10) + field(:max_concurrent_agents, :integer, default: 5) field(:max_turns, :integer, default: 20) field(:max_retry_backoff_ms, :integer, default: 300_000) field(:max_concurrent_agents_by_state, :map, default: %{}) diff --git a/elixir/lib/symphony_elixir/installer/workflow_profile.ex b/elixir/lib/symphony_elixir/installer/workflow_profile.ex index 2368274f65..eecf1cbef6 100644 --- a/elixir/lib/symphony_elixir/installer/workflow_profile.ex +++ b/elixir/lib/symphony_elixir/installer/workflow_profile.ex @@ -68,7 +68,7 @@ defmodule SymphonyElixir.Installer.WorkflowProfile do defp apply_profile_defaults(plan, "symphony-dev") do plan |> put_default(:terminal_states, @default_terminal_states) - |> put_default(:agent_max_concurrent_agents, 10) + |> put_default(:agent_max_concurrent_agents, 5) |> put_default(:agent_max_turns, 20) |> put_default(:human_review_state, @default_human_review_state) |> put_default(:rework_state, @default_rework_state) @@ -77,7 +77,7 @@ defmodule SymphonyElixir.Installer.WorkflowProfile do defp apply_profile_defaults(plan, _profile) do plan - |> put_default(:agent_max_concurrent_agents, 10) + |> put_default(:agent_max_concurrent_agents, 5) |> put_default(:agent_max_turns, 20) end diff --git a/elixir/test/fixtures/status_dashboard_snapshots/backoff_queue.evidence.md b/elixir/test/fixtures/status_dashboard_snapshots/backoff_queue.evidence.md index 3e19e92bc6..98c4f7311d 100644 --- a/elixir/test/fixtures/status_dashboard_snapshots/backoff_queue.evidence.md +++ b/elixir/test/fixtures/status_dashboard_snapshots/backoff_queue.evidence.md @@ -1,6 +1,6 @@ ```text ╭─ SYMPHONY STATUS -│ Agents: 1/10 +│ Agents: 1/5 │ Throughput: 15 tps │ Runtime: 45m 0s │ Tokens: in 18,000 | out 2,200 | total 20,200 diff --git a/elixir/test/fixtures/status_dashboard_snapshots/backoff_queue.snapshot.txt b/elixir/test/fixtures/status_dashboard_snapshots/backoff_queue.snapshot.txt index 5e7ef79ebb..6774ea41f3 100644 --- a/elixir/test/fixtures/status_dashboard_snapshots/backoff_queue.snapshot.txt +++ b/elixir/test/fixtures/status_dashboard_snapshots/backoff_queue.snapshot.txt @@ -1,5 +1,5 @@ \e[1m╭─ SYMPHONY STATUS\e[0m -\e[1m│ Agents: \e[0m\e[32m1\e[0m\e[90m/\e[0m\e[90m10\e[0m +\e[1m│ Agents: \e[0m\e[32m1\e[0m\e[90m/\e[0m\e[90m5\e[0m \e[1m│ Throughput: \e[0m\e[36m15 tps\e[0m \e[1m│ Runtime: \e[0m\e[35m45m 0s\e[0m \e[1m│ Tokens: \e[0m\e[33min 18,000\e[0m\e[90m | \e[0m\e[33mout 2,200\e[0m\e[90m | \e[0m\e[33mtotal 20,200\e[0m diff --git a/elixir/test/fixtures/status_dashboard_snapshots/credits_unlimited.evidence.md b/elixir/test/fixtures/status_dashboard_snapshots/credits_unlimited.evidence.md index d88b27d91e..254155fd91 100644 --- a/elixir/test/fixtures/status_dashboard_snapshots/credits_unlimited.evidence.md +++ b/elixir/test/fixtures/status_dashboard_snapshots/credits_unlimited.evidence.md @@ -1,6 +1,6 @@ ```text ╭─ SYMPHONY STATUS -│ Agents: 1/10 +│ Agents: 1/5 │ Throughput: 42 tps │ Runtime: 1m 15s │ Tokens: in 90 | out 12 | total 102 diff --git a/elixir/test/fixtures/status_dashboard_snapshots/credits_unlimited.snapshot.txt b/elixir/test/fixtures/status_dashboard_snapshots/credits_unlimited.snapshot.txt index 41e358b2cc..e42afe73e1 100644 --- a/elixir/test/fixtures/status_dashboard_snapshots/credits_unlimited.snapshot.txt +++ b/elixir/test/fixtures/status_dashboard_snapshots/credits_unlimited.snapshot.txt @@ -1,5 +1,5 @@ \e[1m╭─ SYMPHONY STATUS\e[0m -\e[1m│ Agents: \e[0m\e[32m1\e[0m\e[90m/\e[0m\e[90m10\e[0m +\e[1m│ Agents: \e[0m\e[32m1\e[0m\e[90m/\e[0m\e[90m5\e[0m \e[1m│ Throughput: \e[0m\e[36m42 tps\e[0m \e[1m│ Runtime: \e[0m\e[35m1m 15s\e[0m \e[1m│ Tokens: \e[0m\e[33min 90\e[0m\e[90m | \e[0m\e[33mout 12\e[0m\e[90m | \e[0m\e[33mtotal 102\e[0m diff --git a/elixir/test/fixtures/status_dashboard_snapshots/idle.evidence.md b/elixir/test/fixtures/status_dashboard_snapshots/idle.evidence.md index da08c940a6..1da50a148f 100644 --- a/elixir/test/fixtures/status_dashboard_snapshots/idle.evidence.md +++ b/elixir/test/fixtures/status_dashboard_snapshots/idle.evidence.md @@ -1,6 +1,6 @@ ```text ╭─ SYMPHONY STATUS -│ Agents: 0/10 +│ Agents: 0/5 │ Throughput: 0 tps │ Runtime: 0m 0s │ Tokens: in 0 | out 0 | total 0 diff --git a/elixir/test/fixtures/status_dashboard_snapshots/idle.snapshot.txt b/elixir/test/fixtures/status_dashboard_snapshots/idle.snapshot.txt index c93b3a0569..20dbd9cb8e 100644 --- a/elixir/test/fixtures/status_dashboard_snapshots/idle.snapshot.txt +++ b/elixir/test/fixtures/status_dashboard_snapshots/idle.snapshot.txt @@ -1,5 +1,5 @@ \e[1m╭─ SYMPHONY STATUS\e[0m -\e[1m│ Agents: \e[0m\e[32m0\e[0m\e[90m/\e[0m\e[90m10\e[0m +\e[1m│ Agents: \e[0m\e[32m0\e[0m\e[90m/\e[0m\e[90m5\e[0m \e[1m│ Throughput: \e[0m\e[36m0 tps\e[0m \e[1m│ Runtime: \e[0m\e[35m0m 0s\e[0m \e[1m│ Tokens: \e[0m\e[33min 0\e[0m\e[90m | \e[0m\e[33mout 0\e[0m\e[90m | \e[0m\e[33mtotal 0\e[0m diff --git a/elixir/test/fixtures/status_dashboard_snapshots/idle_with_dashboard_url.evidence.md b/elixir/test/fixtures/status_dashboard_snapshots/idle_with_dashboard_url.evidence.md index ecbbe22622..829409c7b8 100644 --- a/elixir/test/fixtures/status_dashboard_snapshots/idle_with_dashboard_url.evidence.md +++ b/elixir/test/fixtures/status_dashboard_snapshots/idle_with_dashboard_url.evidence.md @@ -1,6 +1,6 @@ ```text ╭─ SYMPHONY STATUS -│ Agents: 0/10 +│ Agents: 0/5 │ Throughput: 0 tps │ Runtime: 0m 0s │ Tokens: in 0 | out 0 | total 0 diff --git a/elixir/test/fixtures/status_dashboard_snapshots/idle_with_dashboard_url.snapshot.txt b/elixir/test/fixtures/status_dashboard_snapshots/idle_with_dashboard_url.snapshot.txt index 0f5c2d1277..7fa58cdfd2 100644 --- a/elixir/test/fixtures/status_dashboard_snapshots/idle_with_dashboard_url.snapshot.txt +++ b/elixir/test/fixtures/status_dashboard_snapshots/idle_with_dashboard_url.snapshot.txt @@ -1,5 +1,5 @@ \e[1m╭─ SYMPHONY STATUS\e[0m -\e[1m│ Agents: \e[0m\e[32m0\e[0m\e[90m/\e[0m\e[90m10\e[0m +\e[1m│ Agents: \e[0m\e[32m0\e[0m\e[90m/\e[0m\e[90m5\e[0m \e[1m│ Throughput: \e[0m\e[36m0 tps\e[0m \e[1m│ Runtime: \e[0m\e[35m0m 0s\e[0m \e[1m│ Tokens: \e[0m\e[33min 0\e[0m\e[90m | \e[0m\e[33mout 0\e[0m\e[90m | \e[0m\e[33mtotal 0\e[0m diff --git a/elixir/test/fixtures/status_dashboard_snapshots/super_busy.evidence.md b/elixir/test/fixtures/status_dashboard_snapshots/super_busy.evidence.md index ebc7bf8247..6fc468fe78 100644 --- a/elixir/test/fixtures/status_dashboard_snapshots/super_busy.evidence.md +++ b/elixir/test/fixtures/status_dashboard_snapshots/super_busy.evidence.md @@ -1,6 +1,6 @@ ```text ╭─ SYMPHONY STATUS -│ Agents: 2/10 +│ Agents: 2/5 │ Throughput: 1,842 tps │ Runtime: 72m 1s │ Tokens: in 250,000 | out 18,500 | total 268,500 diff --git a/elixir/test/fixtures/status_dashboard_snapshots/super_busy.snapshot.txt b/elixir/test/fixtures/status_dashboard_snapshots/super_busy.snapshot.txt index c7d46a87af..12e6d7c7db 100644 --- a/elixir/test/fixtures/status_dashboard_snapshots/super_busy.snapshot.txt +++ b/elixir/test/fixtures/status_dashboard_snapshots/super_busy.snapshot.txt @@ -1,5 +1,5 @@ \e[1m╭─ SYMPHONY STATUS\e[0m -\e[1m│ Agents: \e[0m\e[32m2\e[0m\e[90m/\e[0m\e[90m10\e[0m +\e[1m│ Agents: \e[0m\e[32m2\e[0m\e[90m/\e[0m\e[90m5\e[0m \e[1m│ Throughput: \e[0m\e[36m1,842 tps\e[0m \e[1m│ Runtime: \e[0m\e[35m72m 1s\e[0m \e[1m│ Tokens: \e[0m\e[33min 250,000\e[0m\e[90m | \e[0m\e[33mout 18,500\e[0m\e[90m | \e[0m\e[33mtotal 268,500\e[0m diff --git a/elixir/test/support/test_support.exs b/elixir/test/support/test_support.exs index 6a62f30712..fc30bea903 100644 --- a/elixir/test/support/test_support.exs +++ b/elixir/test/support/test_support.exs @@ -199,7 +199,7 @@ defmodule SymphonyElixir.TestSupport do workspace_root: Path.join(System.tmp_dir!(), "symphony_workspaces"), worker_ssh_hosts: [], worker_max_concurrent_agents_per_host: nil, - max_concurrent_agents: 10, + max_concurrent_agents: 5, max_turns: 20, max_retry_backoff_ms: 300_000, max_concurrent_agents_by_state: %{}, diff --git a/elixir/test/symphony_elixir/installer_apply_test.exs b/elixir/test/symphony_elixir/installer_apply_test.exs index d0aae4ad5d..5221311a45 100644 --- a/elixir/test/symphony_elixir/installer_apply_test.exs +++ b/elixir/test/symphony_elixir/installer_apply_test.exs @@ -60,6 +60,7 @@ defmodule SymphonyElixir.InstallerApplyTest do assert workflow_artifact.content =~ "Forge provider: GitHub" assert workflow_artifact.content =~ "Validation command before handoff: mix test" + assert workflow_artifact.content =~ "max_concurrent_agents: 5" assert workflow_artifact.content =~ "networkAccess: true" assert workflow_artifact.content =~ "Progress SLA" assert workflow_artifact.content =~ "Create or refresh `## Codex Workpad` within 5 minutes" diff --git a/elixir/test/symphony_elixir/workspace_and_config_test.exs b/elixir/test/symphony_elixir/workspace_and_config_test.exs index c8cc85e7d4..2866aecb63 100644 --- a/elixir/test/symphony_elixir/workspace_and_config_test.exs +++ b/elixir/test/symphony_elixir/workspace_and_config_test.exs @@ -743,7 +743,7 @@ defmodule SymphonyElixir.WorkspaceAndConfigTest do assert config.tracker.project_slug == nil assert config.workspace.root == Path.join(System.tmp_dir!(), "symphony_workspaces") assert config.worker.max_concurrent_agents_per_host == nil - assert config.agent.max_concurrent_agents == 10 + assert config.agent.max_concurrent_agents == 5 assert config.codex.command == "codex app-server" assert config.codex.approval_policy == "never" From 7d5271ac896a0b2ed1e9caf2426522e17ee033ef Mon Sep 17 00:00:00 2001 From: tianhei Date: Thu, 30 Apr 2026 07:08:25 +0800 Subject: [PATCH 2/2] Keep the concurrency default spec internally consistent The first concurrency-default commit updated the cheat sheet entry but missed the detailed `agent` section. Align both SPEC mentions on the intended five-agent default so the configuration contract has one answer. Constraint: CodeRabbit review found the stale second SPEC default before merge Confidence: high Scope-risk: narrow Tested: rg -n 'max_concurrent_agents.*default.*10|Default: `10`|default `10`' SPEC.md Co-authored-by: Codex --- SPEC.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPEC.md b/SPEC.md index 9092a68099..2adb04f43d 100644 --- a/SPEC.md +++ b/SPEC.md @@ -410,7 +410,7 @@ Fields: Fields: - `max_concurrent_agents` (integer) - - Default: `10` + - Default: `5` - Changes SHOULD be re-applied at runtime and affect subsequent dispatch decisions. - `max_turns` (positive integer) - Default: `20`