Skip to content

fix(acp): launch ACP/stdio mode for managed agents with no runtime preset - #3804

Open
bradhallett wants to merge 2 commits into
block:mainfrom
bradhallett:fix/acp-managed-agent-launch-args
Open

fix(acp): launch ACP/stdio mode for managed agents with no runtime preset#3804
bradhallett wants to merge 2 commits into
block:mainfrom
bradhallett:fix/acp-managed-agent-launch-args

Conversation

@bradhallett

@bradhallett bradhallett commented Jul 30, 2026

Copy link
Copy Markdown

Summary

Managed agents pinned to a known ACP runtime via agent_command_override (omp, grok, opencode, kimi, …) but with no runtime id launched the agent's interactive TUI instead of its ACP/stdio mode. Under headless Buzz there is no controlling TTY, so the JSON-RPC initialize handshake never completed and the agent timed out at startup:

  • omp / opencode / kimi → Request timeout
  • grok → ENXIO: Device not configured

Root cause

resolve_effective_harness_descriptor (readiness.rs) only resolved launch args when the record carried explicit instance args or a resolvable harness preset (record.runtimelookup_loaded_harness_by_id). A command-override agent with runtime = None satisfied neither, so descriptor.args stayed empty — while the effective command (e.g. omp) was still correct. The bare command then ran the TUI.

The correct per-runtime args already existed as the single source of truth in PRESET_HARNESSES (now in discovery/presets.rs); they just weren't consulted on this path.

Fix

In resolve_effective_harness_descriptor's final else branch, fall back to the PRESET_HARNESSES entry whose command matches the effective command, via a new preset_args_for_command() helper that mirrors the existing single-source preset_harness_ids() helper and lives alongside it in the managed_agents::discovery::presets submodule (re-exported through discovery.rs).

  • Single source of truth — per-runtime args live only in PRESET_HARNESSES; no hand-mirrored copy.
  • Covers every preset at once — omp, grok, opencode, kimi, cursor, openclaw, and devin.
  • descriptor.args now flows correctly to the spawn env (BUZZ_ACP_AGENT_ARGS), the config hash, and the agent summary.
  } else if let Some(ref def) = harness_def {
      normalize_agent_args(&effective_command, def.args.clone())
+ } else if let Some(preset_args) =
+     crate::managed_agents::discovery::preset_args_for_command(&effective_command)
+ {
+     normalize_agent_args(&effective_command, preset_args)
  } else {
      normalize_agent_args(&effective_command, record_args)
  }

Why not in buzz-acp

An earlier iteration mirrored the args into buzz-acp's default_agent_args. That duplicated the preset table in a third place and still left the desktop's descriptor.args (env/hash/summary) empty — relying on the child to repair the parent's omission. Fixing at resolve_effective_harness_descriptor corrects the source and needs no buzz-acp change; buzz-acp's existing default_value = "acp" still covers standalone CLI use with an unset env.

Verification

  • New unit test resolve_effective_harness_descriptor_uses_preset_args_for_pinned_command — asserts omp/grok/opencode/kimi/cursor/openclaw/devin resolve correctly, explicit args win, unknown commands stay empty. ✅
  • cargo fmt --check clean (pinned 1.95.0 toolchain); full managed_agents:: lib suite: 910 passed, 0 failed.
  • Rebased onto latest block/buzz main (resolves the conflict with Add Devin as a preset ACP harness #3225, which relocated PRESET_HARNESSES and its helpers into discovery/presets.rs; this helper follows it there).
  • Confirmed end-to-end in a local app build that an omp managed agent initializes without the ACP timeout.

No regressions

  • Explicit instance args still take precedence.
  • goose (not in PRESET_HARNESSES) still resolves via default_agent_args.
  • amp-acp / hermes-acp presets carry empty args — unchanged.
  • Custom harness ids collide with reserved preset ids, so they cannot shadow a preset.

Resolves #3399
Resolves #3457
Resolves #3729

Managed agents pinned to a known ACP runtime via agent_command_override
(e.g. `omp`, `grok`, `opencode`, `kimi`) but with no `runtime` id resolved
the effective command (e.g. `omp`) yet launched it with empty args. With no
controlling TTY under headless Buzz, the agent's interactive TUI started
instead of its ACP/stdio mode, so the JSON-RPC `initialize` handshake never
completed and the agent timed out at startup (omp/opencode/kimi: Request
timeout; grok: ENXIO "Device not configured").

Root cause: resolve_effective_harness_descriptor only filled args when the
record carried explicit instance args or a resolvable harness preset
(record.runtime -> lookup_loaded_harness_by_id). A command-override agent
with runtime=None hit neither, so descriptor.args stayed empty.

Fix: when no preset resolves, fall back to the PRESET_HARNESSES entry whose
command matches the effective command (new preset_args_for_command, mirroring
the existing single-source preset_harness_ids helper and living alongside it
in the managed_agents::discovery::presets submodule). The PresetHarness
table stays the single source of truth for per-runtime launch args, so this
covers omp, grok, opencode, kimi, cursor, openclaw, and devin at once with no
hand-mirroring, and descriptor.args now flows correctly to the spawn env
(BUZZ_ACP_AGENT_ARGS), the config hash, and the agent summary.

No regressions: explicit instance args still win; goose still resolves via
default_agent_args; amp/hermes presets (empty args) are unchanged; custom
harness ids collide with reserved preset ids so cannot shadow them.

Resolves block#3399
Resolves block#3457
Resolves block#3729

Signed-off-by: Brad Hallett <53977268+bradhallett@users.noreply.github.com>
@Chessing234

Copy link
Copy Markdown
Contributor

good for #3729. maybe add a log line of the resolved launch args at spawn so failed initialize is easier to debug

@bradhallett

Copy link
Copy Markdown
Author

@Chessing234 Implemented in b9825af and pushed to the PR branch. spawn_agent_child now writes the resolved agent command and argument vector to the managed-agent runtime log immediately before spawn, so failed ACP initialization includes the launch values. Verified with the full desktop library suite (1,996 passed, 14 ignored) and Clippy with -D warnings.

@bradhallett

Copy link
Copy Markdown
Author

Maintainer approval requested:

PR #3905 overlaps with #3804 on command-only preset argument resolution. I recommend keeping #3804 as the canonical fix:

  • resolves built-in preset args from normalized PRESET_HARNESSES data;
  • covers all affected presets and explicit-args precedence;
  • includes the requested resolved-command/args runtime-log diagnostic;
  • passes the full desktop library suite, formatting, and Clippy with -D warnings.

#3905 additionally explores loaded-registry matching and runtime-ID/command mismatch diagnostics. I am happy to port the mismatch diagnostic if command-only custom-harness support is required, but I recommend not merging both implementations.

Can we proceed with #3804 as the canonical PR and close #3905 in favor of it?

Signed-off-by: Brad Hallett <53977268+bradhallett@users.noreply.github.com>
@bradhallett
bradhallett force-pushed the fix/acp-managed-agent-launch-args branch from b9825af to 8b2db59 Compare July 31, 2026 13:59
@skeletor-js

Copy link
Copy Markdown

We are running a headless Grok ACP worker under systemd and hit the same failure described in #3457: launching the bare Grok command enters its interactive path without a controlling TTY, while supplying the runtime ACP/stdio arguments works reliably. The preset-argument fallback in this PR addresses the problem at the right layer and the visible checks are green. +1 for merging this as the canonical fix and including it in the next Buzz release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment