Problem
When an agent identity is served by an externally-provisioned runtime (e.g. a VPS running buzz-acp as a systemd unit), Buzz Desktop still spawns a local process for that agent on every reactive trigger, channel open, member-sidebar load, @mention click. The local spawn competes with the remote instance: both connect to the relay under the same identity key, both receive channel events, and both may respond.
start_on_app_launch: false does not help. It only gates reconcile_managed_agent_runtimes at startup. The reactive path...
start_managed_agent_runtime_pair_lazy → start_pair in runtime_commands.rs, checks only backend == BackendKind::Local and fires regardless.
Repro
- Configure an agent in Buzz Desktop (Local backend, start_on_app_launch: false).
- Run the same identity on an external host (e.g. VPS via systemd, same nsec).
- Mention the agent in any channel the Desktop user has open.
- Desktop spawns a local buzz-acp process alongside the external one. Both connect. Both may reply.
Expected behaviour
Desktop should provide a way to say: "this identity is owned by a remote runtime, do not spawn locally." The persona must stay in managed-agents.json (on Windows the nsec lives in Credential Manager, keyed by pubkey; deleting the persona loses the key). Only the local process spawn is suppressed, and it is trivially reversible.
Relevant design context
VISION_REMOTE_AGENTS.md (PR #3924) describes exactly this use case as the intended long-term feature. The planned mechanism (Provider backend + deploy handshake) would address it eventually, but:
Provider mode is not usable for self-hosted/VPS deployments yet (it requires a buzz-backend-* binary on PATH)
Switching to Provider backend today triggers deploy_to_provider on channel mention clicks, breaking mentions with "provider not found on PATH"
The vision doc targets a Kubernetes-based managed deploy; self-provisioned VPS is out of scope for that path
A minimal flag is the correct stepping stone.
Proposed fix
Add disable_local_spawn: bool (default false, serde default) to ManagedAgentRecord in types.rs. Add one guard in start_pair() before the spawn:
if record.disable_local_spawn {
return Err("local spawn disabled, runtime is externally managed".into());
}
Expose as a toggle in Settings → Agents alongside start_on_app_launch. This is intentionally narrower than the Provider-deploy path, it is a flag for operators who are already running agents on external infrastructure and simply need Desktop to step aside.
Notes
The fix must be in start_pair(), not only in reconcile_managed_agent_runtimes. Two paths reach start_pair: the boot reconciliation (gated today by start_on_app_launch) and the lazy reactive spawn (start_managed_agent_runtime_pair_lazy), which is the unfixed one.
The flag should default false so existing behaviour is unchanged for everyone not explicitly opting in.
Toggling back to false restores normal local-spawn behaviour immediately, no persona or key changes.
Problem
When an agent identity is served by an externally-provisioned runtime (e.g. a VPS running buzz-acp as a systemd unit), Buzz Desktop still spawns a local process for that agent on every reactive trigger, channel open, member-sidebar load, @mention click. The local spawn competes with the remote instance: both connect to the relay under the same identity key, both receive channel events, and both may respond.
start_on_app_launch: false does not help. It only gates reconcile_managed_agent_runtimes at startup. The reactive path...
start_managed_agent_runtime_pair_lazy → start_pair in runtime_commands.rs, checks only backend == BackendKind::Local and fires regardless.
Repro
Expected behaviour
Desktop should provide a way to say: "this identity is owned by a remote runtime, do not spawn locally." The persona must stay in managed-agents.json (on Windows the nsec lives in Credential Manager, keyed by pubkey; deleting the persona loses the key). Only the local process spawn is suppressed, and it is trivially reversible.
Relevant design context
VISION_REMOTE_AGENTS.md (PR #3924) describes exactly this use case as the intended long-term feature. The planned mechanism (Provider backend + deploy handshake) would address it eventually, but:
Provider mode is not usable for self-hosted/VPS deployments yet (it requires a buzz-backend-* binary on PATH)
Switching to Provider backend today triggers deploy_to_provider on channel mention clicks, breaking mentions with "provider not found on PATH"
The vision doc targets a Kubernetes-based managed deploy; self-provisioned VPS is out of scope for that path
A minimal flag is the correct stepping stone.
Proposed fix
Add disable_local_spawn: bool (default false, serde default) to ManagedAgentRecord in types.rs. Add one guard in start_pair() before the spawn:
Expose as a toggle in Settings → Agents alongside start_on_app_launch. This is intentionally narrower than the Provider-deploy path, it is a flag for operators who are already running agents on external infrastructure and simply need Desktop to step aside.
Notes
The fix must be in start_pair(), not only in reconcile_managed_agent_runtimes. Two paths reach start_pair: the boot reconciliation (gated today by start_on_app_launch) and the lazy reactive spawn (start_managed_agent_runtime_pair_lazy), which is the unfixed one.
The flag should default false so existing behaviour is unchanged for everyone not explicitly opting in.
Toggling back to false restores normal local-spawn behaviour immediately, no persona or key changes.