feat(desktop): cap OpenClaw agent parallelism at 5 - #4019
Conversation
187ffbf to
3333dce
Compare
OpenClaw connects to a single shared Gateway daemon. Spawning the default 10 ACP workers per agent wastes resources and is architecturally wrong. Tyler's ruling: cap at 5. Contract: stored = requested; effective = min(requested, harness cap). The cap is applied only where the value becomes a running worker-pool size. - parallelism.rs: policy core — OPENCLAW_MAX_PARALLELISM=5, harness_max_parallelism(), effective_parallelism(), acp_agents_value(). Keyed on normalize_command_identity so path prefixes and .exe suffix are handled. - BUZZ_ACP_AGENTS added to RESERVED_ENV_KEYS: the Desktop resolves the effective value into launch.policy_env; a user-supplied override would bypass the cap against the single Gateway daemon. - runtime.rs: spawn clamp — BUZZ_ACP_AGENTS = acp_agents_value(cmd, parallelism). - agents_deploy.rs: deploy egress clamp — policy_env and legacy top-level parallelism field both project effective_parallelism. - SpawnConfigSnapshot: stores effective parallelism so over-cap edits that don't change the running pool size (e.g. 10→8, both clamp to 5) don't raise a spurious restart badge. The diff surface displays effective value. - AcpRuntimeCatalogEntry.max_parallelism: Option<u32> — derived from the static definition command (not the probed entry.command) so unavailable OpenClaw entries still carry the cap. Propagated through all four catalog constructors, IPC types, and the frontend catalog type. - UI: amber hint on both EditAgentAdvancedFields and PersonaAdvancedFields when the selected harness has a cap and the requested value exceeds it. Allow-and-explain: no max attr, no save-path clamp, cap and label come from the catalog entry (no hardcoded 5 in TS). - docs/remote-agents.md: BUZZ_ACP_AGENTS moved from deliberately-non-reserved to reserved; new contract documented. - desktop/src/features/agents/AGENTS.md: command-keyed execution policy documented as the sanctioned second metadata source feeding catalog. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
3333dce to
3c34e8a
Compare
…lelism.rs hash_above_cap_equivalence and hash_cap_crossing_inequality in parallelism.rs drove prospective_spawn_config_snapshot for the same two behaviors already covered by openclaw_above_cap_parallelism_snapshots_equal and openclaw_cap_crossing_parallelism_snapshots_differ in spawn_snapshot/tests.rs. The snapshot-module seam tests are the stronger placement. Remove the duplicate pair and its snapshot_for helper (~42 lines). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wesbillman
left a comment
There was a problem hiding this comment.
Reviewing on Wes Pfleger's behalf.
The executable launch block and the legacy top-level parallelism field can disagree for inherited or overridden harnesses. build_deploy_payload has already resolved the effective harness into descriptor and uses descriptor.command to cap launch.policy_env["BUZZ_ACP_AGENTS"] (lines 131–142), but deploy_payload_json recomputes the legacy value from record.agent_command (lines 185–189). That field is explicitly a stale create-time snapshot in several supported states: record_agent_command instead prefers agent_command_override, then record.runtime, then the live persona (managed_agents/discovery.rs:308–330). For example, an agent whose stored agent_command is goose but whose live persona now resolves to OpenClaw will execute with BUZZ_ACP_AGENTS=5 while the top-level field still says 10; the opposite transition reports 5 while running 10.
Please derive both projections from the same already-resolved descriptor (for example, pass the effective command/value into deploy_payload_json) and add regression coverage for stale persona inheritance and/or an explicit override. Otherwise the new "display/bookkeeping consistency" guarantee is precisely where the dungeon floor opens.
Both the legacy top-level `parallelism` field and `launch.policy_env["BUZZ_ACP_AGENTS"]` now derive from the same resolved descriptor command rather than from `record.agent_command`. Previously the legacy field called `effective_parallelism` on the stale `record.agent_command` snapshot while the executable `policy_env` used the live descriptor. For a persona-inherited harness switch (e.g. record created as Goose, persona later changed to OpenClaw) the two fields would disagree: policy_env would cap at 5 while the legacy field would report the uncapped requested value. `build_deploy_payload` now computes the effective value once from `descriptor.command` and passes it into `deploy_payload_json` via a `DeployProjections` struct alongside the other effective-config fields. The three regression tests cover the stale-goose/live-openclaw, stale-openclaw/live-goose, and explicit-override directions. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Picks up #4671 (multi-repository projects): adds BUZZ_ACP_DISPLAY_NAME to launch.policy_env and RESERVED_ENV_KEYS. Auto-merged cleanly with no conflicts in agents_deploy.rs, env_vars.rs, or runtime.rs. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wesbillman
left a comment
There was a problem hiding this comment.
Re-reviewing on Wes Pfleger’s behalf.
The requested deploy correction is in place: build_deploy_payload computes effective parallelism once from the resolved descriptor.command, and both the executable launch.policy_env["BUZZ_ACP_AGENTS"] and legacy top-level parallelism now receive that same projection. The added stale-persona and explicit-override regression cases cover the mismatch that blocked the prior head.
I also reviewed the incremental fix and merge result; no new blocking issues found. CI is green at the reviewed head.
…arer-auth * origin/main: (65 commits) fix(desktop): route macos notification clicks (#4799) feat(mobile): sync themes per community (#3767) feat(desktop): sync themes per community (#3653) feat(desktop): cap OpenClaw agent parallelism at 5 (#4019) fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805) Fix mobile message timeline bounce (#4862) Polish mobile bottom sheets and profile cards (#4911) Fix media attachment actions (#4849) fix(desktop): remove join API token control (#4897) fix(desktop): allow shared agent mentions (#4913) Polish mobile top navigation (#4778) fix(release): tag immutable desktop candidates (#4811) fix(channels): restrict private-channel invitations (#4612) fix(acp): reject unattended permission requests (#4609) fix(workflow): bind trigger author to the signed event (#4607) fix(git): revoke access for banned relay members (#4608) fix(agent): recover from unsupported image input instead of poisoning the turn (#4896) Define private managed agent wire protocol (#4593) fix(mobile): serialize channel sections sync (#3165) fix(desktop): make missing-command error actionable for released builds (#4802) ... Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> # Conflicts: # CHANGELOG.md
Resolve two conflicts: 1. crates/buzz-acp/src/config.rs: PermissionMode::BypassPermissions removed by main (#4609, security fix). Branch added PermissionMode::Auto and effort_level. Resolution: keep Auto and effort_level additions; drop BypassPermissions (aligns with #4609's intent that the variant cannot be restored by configuration). One test assertion for BypassPermissions.is_default() removed accordingly. 2. desktop/src-tauri/src/commands/agents_deploy.rs: branch added claude B2/I-4 tests (ANTHROPIC_MODEL routing, BUZZ_ACP_EFFORT_LEVEL); main added OpenClaw parallelism-cap tests via #4019. Resolution: keep both test sets; fix trailing blank line introduced by merge tool. Also: desktop/src-tauri/src/managed_agents/parallelism.rs (new in main via #4019) constructs ManagedAgentRecord in tests without the effort_level field added by this branch. Added effort_level: None to the test helper to satisfy the struct initializer. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…ed-agent-store-merge * origin/main: (24 commits) fix(reactions): support max-length custom emoji (#3833) feat(desktop): allow leaving your final community (#3621) fix(buzz-agent): recover from context-window 400s instead of sticking (#4946) docs(persona-pack): fix stale desktop import instructions (#4500) fix(desktop): route macos notification clicks (#4799) feat(mobile): sync themes per community (#3767) feat(desktop): sync themes per community (#3653) feat(desktop): cap OpenClaw agent parallelism at 5 (#4019) fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805) Fix mobile message timeline bounce (#4862) Polish mobile bottom sheets and profile cards (#4911) Fix media attachment actions (#4849) fix(desktop): remove join API token control (#4897) fix(desktop): allow shared agent mentions (#4913) Polish mobile top navigation (#4778) fix(release): tag immutable desktop candidates (#4811) fix(channels): restrict private-channel invitations (#4612) fix(acp): reject unattended permission requests (#4609) fix(workflow): bind trigger author to the signed event (#4607) fix(git): revoke access for banned relay members (#4608) ... Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> # Conflicts: # desktop/src-tauri/src/managed_agents/runtime.rs
…p-csp * origin/main: (66 commits) fix(reactions): support max-length custom emoji (#3833) feat(desktop): allow leaving your final community (#3621) fix(buzz-agent): recover from context-window 400s instead of sticking (#4946) docs(persona-pack): fix stale desktop import instructions (#4500) fix(desktop): route macos notification clicks (#4799) feat(mobile): sync themes per community (#3767) feat(desktop): sync themes per community (#3653) feat(desktop): cap OpenClaw agent parallelism at 5 (#4019) fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805) Fix mobile message timeline bounce (#4862) Polish mobile bottom sheets and profile cards (#4911) Fix media attachment actions (#4849) fix(desktop): remove join API token control (#4897) fix(desktop): allow shared agent mentions (#4913) Polish mobile top navigation (#4778) fix(release): tag immutable desktop candidates (#4811) fix(channels): restrict private-channel invitations (#4612) fix(acp): reject unattended permission requests (#4609) fix(workflow): bind trigger author to the signed event (#4607) fix(git): revoke access for banned relay members (#4608) ...
Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> * origin/main: fix(reactions): support max-length custom emoji (#3833) feat(desktop): allow leaving your final community (#3621) fix(buzz-agent): recover from context-window 400s instead of sticking (#4946) docs(persona-pack): fix stale desktop import instructions (#4500) fix(desktop): route macos notification clicks (#4799) feat(mobile): sync themes per community (#3767) feat(desktop): sync themes per community (#3653) feat(desktop): cap OpenClaw agent parallelism at 5 (#4019) fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805) Fix mobile message timeline bounce (#4862) Polish mobile bottom sheets and profile cards (#4911) Fix media attachment actions (#4849) fix(desktop): remove join API token control (#4897) fix(desktop): allow shared agent mentions (#4913) Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> * origin/main: fix(reactions): support max-length custom emoji (#3833) feat(desktop): allow leaving your final community (#3621) fix(buzz-agent): recover from context-window 400s instead of sticking (#4946) docs(persona-pack): fix stale desktop import instructions (#4500) fix(desktop): route macos notification clicks (#4799) feat(mobile): sync themes per community (#3767) feat(desktop): sync themes per community (#3653) feat(desktop): cap OpenClaw agent parallelism at 5 (#4019) fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805) Fix mobile message timeline bounce (#4862) Polish mobile bottom sheets and profile cards (#4911) Fix media attachment actions (#4849) fix(desktop): remove join API token control (#4897) Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…-setting * origin/main: fix(reactions): support max-length custom emoji (#3833) feat(desktop): allow leaving your final community (#3621) fix(buzz-agent): recover from context-window 400s instead of sticking (#4946) docs(persona-pack): fix stale desktop import instructions (#4500) fix(desktop): route macos notification clicks (#4799) feat(mobile): sync themes per community (#3767) feat(desktop): sync themes per community (#3653) feat(desktop): cap OpenClaw agent parallelism at 5 (#4019) fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805) Fix mobile message timeline bounce (#4862) Polish mobile bottom sheets and profile cards (#4911) Fix media attachment actions (#4849) fix(desktop): remove join API token control (#4897) Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
OpenClaw connects to a single shared Gateway daemon. Spawning the default 10 ACP workers per agent is both resource-expensive and architecturally wrong — each worker opens a separate gateway connection. Tyler's ruling: cap at 5, lower if needed.
Contract
Store the requested value (1–32) verbatim at every persistence and wire boundary. Apply
effective = min(requested, harness_cap)only at the four enforcement points:BUZZ_ACP_AGENTSenv var in childCommandlaunch.policy_env["BUZZ_ACP_AGENTS"]+ legacyparallelismfieldSpawnConfigSnapshot.parallelismstores effective value; the diff surface displays what actually runsmaxattribute, no save-path clampBUZZ_ACP_AGENTSis added toRESERVED_ENV_KEYS— the Desktop resolves the effective value intopolicy_env; a user-supplied override inenvwould bypass the cap and is silently stripped.Changes
managed_agents/parallelism.rs(new) — policy core:OPENCLAW_MAX_PARALLELISM = 5harness_max_parallelism(command)— keyed onnormalize_command_identityso path prefixes,.exesuffixes, and other cosmetic differences are ignoredeffective_parallelism(command, value)— identity for uncapped harnessesacp_agents_value(command, parallelism)—env("BUZZ_ACP_AGENTS", …)helperruntime.rs— spawn clamp:BUZZ_ACP_AGENTS = acp_agents_value(effective_command, record.parallelism)agents_deploy.rs— deploy egress clamp:build_deploy_payloadresolveseffective_parallelismonce fromdescriptor.command; bothlaunch.policy_env["BUZZ_ACP_AGENTS"]and the legacy top-levelparallelismfield use that value — the two are always consistent regardless of stalerecord.agent_commandpinsspawn_snapshot.rs—from_inputsstoreseffective_parallelism(&descriptor.command, record.parallelism)in theparallelismfield. Over-cap edits that don't change the pool (e.g. 10 → 8, both clamp to 5 on OpenClaw) produce equal snapshots; cap crossings (8 → 3) produce different snapshots.AcpRuntimeCatalogEntry.max_parallelism: Option<u32>— derived from the static definition command, not the probedentry.command(which may benullfor unavailable entries), so unavailable OpenClaw entries still carry the cap. Propagated through all four catalog constructors (builtin discovery, preset catalog construction, custom discovery, custom-save response), IPC types (RawAcpRuntimeCatalogEntry.max_parallelism), and the frontend catalog type.UI —
EditAgentAdvancedFieldsandPersonaAdvancedFieldsshow an amber hint whenselectedRuntime.maxParallelismis set and the current value exceeds it. Cap and label come from the catalog entry — no hardcoded 5 in TS. Nomaxattribute on inputs; the input staystype="text"with 1–32 copy.Docs —
docs/remote-agents.md:BUZZ_ACP_AGENTSmoved from the deliberately-non-reserved section to reserved; new contract documented.desktop/src/features/agents/AGENTS.md: command-keyed execution policy documented as the sanctioned second metadata source feeding the catalog projection.Tests
Rust (
parallelism.rs):policy_table—harness_max_parallelismandeffective_parallelismacross all openclaw variants and uncapped harnessesacp_agents_value_openclaw_above_cap_is_capped— spawn-env seamoverride_direction_*— both override directions (openclaw runtime + goose override; goose runtime + openclaw override)summary_persona_inherited_*— live persona wins over staleagent_commandsnapshot_export_carries_requested_definition_parallelism— requested value travels wire/sync unchangedRust (
spawn_snapshot/tests.rs):openclaw_above_cap_parallelism_snapshots_equal— stored 10 vs 8, both clamp to 5 → snapshots equalopenclaw_cap_crossing_parallelism_snapshots_differ— 8 (clamps to 5) vs 3 → snapshots differRust (
discovery/presets.rs):openclaw_preset_unavailable_carries_max_parallelism/openclaw_preset_available_carries_max_parallelism— catalog metadata present withcommand: nulland with a resolved pathRust (
agents_deploy.rs):launch_block_openclaw_over_cap_policy_env_is_capped— directlaunch.policy_envseamdeploy_payload_json_stale_goose_record_live_openclaw_descriptor_both_capped— stalerecord.agent_command=goose, live descriptor=openclaw: both fields cap to 5deploy_payload_json_stale_openclaw_record_live_goose_descriptor_both_uncapped— stalerecord.agent_command=openclaw, live descriptor=goose: both fields pass through requesteddeploy_payload_json_explicit_openclaw_override_both_capped— explicitagent_command_override=openclaw: both fields cap to 5Rust (
persona_events/stale_pin_tests.rs):apply_persona_snapshot_goose_to_custom_harness_drops_stale_goose_pin— custom-direction stale-pin drop (builtin pin → loaded custom harness viaupdate_loaded_harness_registry)TypeScript (
agentParallelism.test.mjs):parallelismCapHint— at/below cap (null), above cap (hint includes label and cap value), singular form for cap=1, uncapped harness (null)TypeScript (
tauri.test.mjs):fromRawAcpRuntimeCatalogEntryround-tripsmax_parallelism→maxParallelism; absent whenundefined