From c3b6d8b2a45b0993df84d64c04f04a09a5885288 Mon Sep 17 00:00:00 2001 From: Khaliq Date: Tue, 10 Mar 2026 11:28:38 +0100 Subject: [PATCH 1/5] feat: add skipRelayPrompt flag to skip MCP config injection on spawn When spawning agents for minor tasks where relay messaging isn't needed, the relay MCP configuration and protocol prompt injection can now be skipped by setting skipRelayPrompt: true. This saves time and tokens for lightweight worker agents. Changes: - Rust broker: skip MCP config args and relay env vars when flag is set - TypeScript SDK: add skipRelayPrompt to spawn options and client API - Python SDK: add skip_relay_prompt to spawn methods - Protocol: add skip_relay_prompt to spawn_agent payload Closes #419 Co-Authored-By: Claude Opus 4.6 --- packages/sdk-py/src/agent_relay/client.py | 9 +++ packages/sdk-py/src/agent_relay/relay.py | 2 + packages/sdk/src/client.ts | 13 ++++ packages/sdk/src/protocol.ts | 2 +- packages/sdk/src/relay.ts | 9 +++ src/main.rs | 86 ++++++++++++++--------- 6 files changed, 88 insertions(+), 33 deletions(-) diff --git a/packages/sdk-py/src/agent_relay/client.py b/packages/sdk-py/src/agent_relay/client.py index fbdd7800e..72d76ea7b 100644 --- a/packages/sdk-py/src/agent_relay/client.py +++ b/packages/sdk-py/src/agent_relay/client.py @@ -557,6 +557,7 @@ async def spawn_pty( idle_threshold_secs: Optional[int] = None, restart_policy: Optional[dict[str, Any]] = None, continue_from: Optional[str] = None, + skip_relay_prompt: Optional[bool] = None, ) -> dict[str, Any]: await self.start_client() built_args = _build_pty_args_with_model(cli, args or [], model) @@ -585,6 +586,8 @@ async def spawn_pty( request_payload["idle_threshold_secs"] = idle_threshold_secs if continue_from is not None: request_payload["continue_from"] = continue_from + if skip_relay_prompt is not None: + request_payload["skip_relay_prompt"] = skip_relay_prompt return await self._request_ok("spawn_agent", request_payload) async def spawn_headless( @@ -595,6 +598,7 @@ async def spawn_headless( args: Optional[list[str]] = None, channels: Optional[list[str]] = None, task: Optional[str] = None, + skip_relay_prompt: Optional[bool] = None, ) -> dict[str, Any]: await self.start_client() agent = AgentSpec( @@ -607,6 +611,8 @@ async def spawn_headless( request_payload: dict[str, Any] = {"agent": agent.to_dict()} if task is not None: request_payload["initial_task"] = task + if skip_relay_prompt is not None: + request_payload["skip_relay_prompt"] = skip_relay_prompt return await self._request_ok("spawn_agent", request_payload) async def spawn_provider( @@ -626,6 +632,7 @@ async def spawn_provider( idle_threshold_secs: Optional[int] = None, restart_policy: Optional[dict[str, Any]] = None, continue_from: Optional[str] = None, + skip_relay_prompt: Optional[bool] = None, ) -> dict[str, Any]: resolved_transport: AgentTransport = transport or ( "headless" if provider == "opencode" else "pty" @@ -645,6 +652,7 @@ async def spawn_provider( args=args, channels=channels, task=task, + skip_relay_prompt=skip_relay_prompt, ) return await self.spawn_pty( @@ -661,6 +669,7 @@ async def spawn_provider( idle_threshold_secs=idle_threshold_secs, restart_policy=restart_policy, continue_from=continue_from, + skip_relay_prompt=skip_relay_prompt, ) async def spawn_claude(self, **kwargs: Any) -> dict[str, Any]: diff --git a/packages/sdk-py/src/agent_relay/relay.py b/packages/sdk-py/src/agent_relay/relay.py index 46beb77c8..0bb4b8c65 100644 --- a/packages/sdk-py/src/agent_relay/relay.py +++ b/packages/sdk-py/src/agent_relay/relay.py @@ -51,6 +51,7 @@ class SpawnOptions: shadow_mode: Optional[str] = None idle_threshold_secs: Optional[int] = None restart_policy: Optional[dict[str, Any]] = None + skip_relay_prompt: Optional[bool] = None on_start: LifecycleHook = None on_success: LifecycleHook = None on_error: LifecycleHook = None @@ -512,6 +513,7 @@ async def spawn( shadow_mode=opts.shadow_mode, idle_threshold_secs=opts.idle_threshold_secs, restart_policy=opts.restart_policy, + skip_relay_prompt=opts.skip_relay_prompt, ) except Exception as error: await self._invoke_lifecycle_hook( diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 3ebb5532c..4c65e7b21 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -50,6 +50,9 @@ export interface SpawnPtyInput { restartPolicy?: RestartPolicy; /** Name of a previously released agent whose continuity context should be injected. */ continueFrom?: string; + /** When true, skip injecting the relay MCP configuration and protocol prompt into the spawned agent. + * Useful for minor tasks where relay messaging is not needed, saving tokens. */ + skipRelayPrompt?: boolean; } export interface SpawnHeadlessInput { @@ -58,6 +61,9 @@ export interface SpawnHeadlessInput { args?: string[]; channels?: string[]; task?: string; + /** When true, skip injecting the relay MCP configuration and protocol prompt into the spawned agent. + * Useful for minor tasks where relay messaging is not needed, saving tokens. */ + skipRelayPrompt?: boolean; } export type AgentTransport = 'pty' | 'headless'; @@ -77,6 +83,9 @@ export interface SpawnProviderInput { idleThresholdSecs?: number; restartPolicy?: RestartPolicy; continueFrom?: string; + /** When true, skip injecting the relay MCP configuration and protocol prompt into the spawned agent. + * Useful for minor tasks where relay messaging is not needed, saving tokens. */ + skipRelayPrompt?: boolean; } export interface SendMessageInput { @@ -276,6 +285,7 @@ export class AgentRelayClient { ...(input.task != null ? { initial_task: input.task } : {}), ...(input.idleThresholdSecs != null ? { idle_threshold_secs: input.idleThresholdSecs } : {}), ...(input.continueFrom != null ? { continue_from: input.continueFrom } : {}), + ...(input.skipRelayPrompt != null ? { skip_relay_prompt: input.skipRelayPrompt } : {}), }); return result; } @@ -292,6 +302,7 @@ export class AgentRelayClient { const result = await this.requestOk<{ name: string; runtime: AgentRuntime }>('spawn_agent', { agent, ...(input.task != null ? { initial_task: input.task } : {}), + ...(input.skipRelayPrompt != null ? { skip_relay_prompt: input.skipRelayPrompt } : {}), }); return result; } @@ -310,6 +321,7 @@ export class AgentRelayClient { args: input.args, channels: input.channels, task: input.task, + skipRelayPrompt: input.skipRelayPrompt, }); } @@ -327,6 +339,7 @@ export class AgentRelayClient { idleThresholdSecs: input.idleThresholdSecs, restartPolicy: input.restartPolicy, continueFrom: input.continueFrom, + skipRelayPrompt: input.skipRelayPrompt, }); } diff --git a/packages/sdk/src/protocol.ts b/packages/sdk/src/protocol.ts index 19d120f12..a8bf866a0 100644 --- a/packages/sdk/src/protocol.ts +++ b/packages/sdk/src/protocol.ts @@ -49,7 +49,7 @@ export type SdkToBroker = } | { type: 'spawn_agent'; - payload: { agent: AgentSpec; initial_task?: string }; + payload: { agent: AgentSpec; initial_task?: string; skip_relay_prompt?: boolean }; } | { type: 'send_message'; diff --git a/packages/sdk/src/relay.ts b/packages/sdk/src/relay.ts index 64ae68e77..4839dc3de 100644 --- a/packages/sdk/src/relay.ts +++ b/packages/sdk/src/relay.ts @@ -137,6 +137,9 @@ export interface SpawnOptions extends SpawnLifecycleHooks { shadowMode?: string; idleThresholdSecs?: number; restartPolicy?: RestartPolicy; + /** When true, skip injecting the relay MCP configuration and protocol prompt into the spawned agent. + * Useful for minor tasks where relay messaging is not needed, saving tokens. */ + skipRelayPrompt?: boolean; } export interface SpawnAndWaitOptions extends SpawnOptions { @@ -202,6 +205,9 @@ export interface SpawnerSpawnOptions extends SpawnLifecycleHooks { task?: string; model?: string; cwd?: string; + /** When true, skip injecting the relay MCP configuration and protocol prompt into the spawned agent. + * Useful for minor tasks where relay messaging is not needed, saving tokens. */ + skipRelayPrompt?: boolean; } export type EventHook = ((value: T) => void) | null; @@ -369,6 +375,7 @@ export class AgentRelay { shadowMode: input.shadowMode, idleThresholdSecs: input.idleThresholdSecs, restartPolicy: input.restartPolicy, + skipRelayPrompt: input.skipRelayPrompt, }); } catch (error) { await this.invokeLifecycleHook( @@ -410,6 +417,7 @@ export class AgentRelay { shadowMode: options?.shadowMode, idleThresholdSecs: options?.idleThresholdSecs, restartPolicy: options?.restartPolicy, + skipRelayPrompt: options?.skipRelayPrompt, onStart: options?.onStart, onSuccess: options?.onSuccess, onError: options?.onError, @@ -1225,6 +1233,7 @@ export class AgentRelay { task, model: options?.model, cwd: options?.cwd, + skipRelayPrompt: options?.skipRelayPrompt, onStart: options?.onStart, onSuccess: options?.onSuccess, onError: options?.onError, diff --git a/src/main.rs b/src/main.rs index 57254b3b0..32bd5834d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -447,6 +447,10 @@ struct SpawnPayload { /// Name of a previously released agent whose continuity context should be injected. #[serde(default)] continue_from: Option, + /// When true, skip injecting the relay MCP configuration into the spawned agent. + /// Useful for minor tasks where relay messaging is not needed, saving tokens. + #[serde(default)] + skip_relay_prompt: Option, } #[derive(Debug, Deserialize)] @@ -638,6 +642,7 @@ impl WorkerRegistry { parent: Option, idle_threshold_secs: Option, worker_relay_api_key: Option, + skip_relay_prompt: bool, ) -> Result<()> { if self.workers.contains_key(&spec.name) { anyhow::bail!("agent '{}' already exists", spec.name); @@ -695,20 +700,27 @@ impl WorkerRegistry { }; // Build MCP config args for CLIs that support dynamic MCP configuration. - let cwd = spec.cwd.as_deref().unwrap_or("."); - // Pass the original CLI name (e.g. "cursor") so cursor-specific - // MCP config logic is triggered. `resolved_cli` may differ - // (parse_cli_command maps "cursor" → "agent"). - let mcp_args = configure_relaycast_mcp_with_token( - cli, - &spec.name, - self.env_value("RELAY_API_KEY"), - self.env_value("RELAY_BASE_URL"), - &effective_args, - Path::new(cwd), - worker_relay_api_key.as_deref(), - ) - .await?; + // When skip_relay_prompt is true, skip MCP config injection so the + // spawned agent does not receive relay protocol context (saves tokens + // for minor tasks where messaging is not needed). + let mcp_args = if skip_relay_prompt { + vec![] + } else { + let cwd = spec.cwd.as_deref().unwrap_or("."); + // Pass the original CLI name (e.g. "cursor") so cursor-specific + // MCP config logic is triggered. `resolved_cli` may differ + // (parse_cli_command maps "cursor" → "agent"). + configure_relaycast_mcp_with_token( + cli, + &spec.name, + self.env_value("RELAY_API_KEY"), + self.env_value("RELAY_BASE_URL"), + &effective_args, + Path::new(cwd), + worker_relay_api_key.as_deref(), + ) + .await? + }; let has_extra = bypass_flag.is_some() || !effective_args.is_empty() || !mcp_args.is_empty(); @@ -735,16 +747,20 @@ impl WorkerRegistry { command.arg(headless_provider_cli_name(provider)); // Build MCP config for headless provider agents. - let mcp_args = configure_relaycast_mcp_with_token( - headless_provider_cli_name(provider), - &spec.name, - self.env_value("RELAY_API_KEY"), - self.env_value("RELAY_BASE_URL"), - &spec.args, - Path::new(spec.cwd.as_deref().unwrap_or(".")), - worker_relay_api_key.as_deref(), - ) - .await?; + let mcp_args = if skip_relay_prompt { + vec![] + } else { + configure_relaycast_mcp_with_token( + headless_provider_cli_name(provider), + &spec.name, + self.env_value("RELAY_API_KEY"), + self.env_value("RELAY_BASE_URL"), + &spec.args, + Path::new(spec.cwd.as_deref().unwrap_or(".")), + worker_relay_api_key.as_deref(), + ) + .await? + }; if !spec.args.is_empty() || !mcp_args.is_empty() { command.arg("--"); @@ -765,15 +781,17 @@ impl WorkerRegistry { for (key, value) in &self.worker_env { command.env(key, value); } - if let Some(relay_key) = worker_relay_api_key { - // Keep RELAY_API_KEY as the workspace key and pass the - // pre-registered agent token separately for MCP servers that - // support session bootstrap. - command.env("RELAY_AGENT_TOKEN", relay_key); + if !skip_relay_prompt { + if let Some(relay_key) = worker_relay_api_key { + // Keep RELAY_API_KEY as the workspace key and pass the + // pre-registered agent token separately for MCP servers that + // support session bootstrap. + command.env("RELAY_AGENT_TOKEN", relay_key); + } + command.env("RELAY_AGENT_NAME", &spec.name); + command.env("RELAY_AGENT_TYPE", "agent"); + command.env("RELAY_STRICT_AGENT_NAME", "1"); } - command.env("RELAY_AGENT_NAME", &spec.name); - command.env("RELAY_AGENT_TYPE", "agent"); - command.env("RELAY_STRICT_AGENT_NAME", "1"); // Remove CLAUDECODE env var to prevent "nested session" detection // when spawning Claude Code agents from within a Claude Code session. command.env_remove("CLAUDECODE"); @@ -1505,6 +1523,7 @@ async fn run_init(cmd: InitCommand, telemetry: TelemetryClient) -> Result<()> { Some("Dashboard".to_string()), None, worker_relay_key.clone(), + false, ).await { Ok(()) => { if let Some(ref task_text) = effective_task { @@ -2073,6 +2092,7 @@ async fn run_init(cmd: InitCommand, telemetry: TelemetryClient) -> Result<()> { Some("Relaycast".to_string()), None, worker_relay_key.clone(), + false, ).await { Ok(()) => { if let Some(ref task_text) = effective_task { @@ -3021,6 +3041,7 @@ async fn run_init(cmd: InitCommand, telemetry: TelemetryClient) -> Result<()> { rst.parent.clone(), None, Some(worker_relay_key), + false, ) .await { @@ -3422,6 +3443,7 @@ async fn handle_sdk_frame( None, payload.idle_threshold_secs, worker_relay_key.clone(), + payload.skip_relay_prompt.unwrap_or(false), ) .await?; if let Some(task) = effective_task.clone() { From 643121d8b4d889ed36f3d2978785579b53a15f42 Mon Sep 17 00:00:00 2001 From: Khaliq Date: Tue, 10 Mar 2026 11:32:35 +0100 Subject: [PATCH 2/5] fix: pass skipRelayPrompt through spawner headless path and simplify Rust type - Add missing skipRelayPrompt passthrough in spawner's headless spawnProvider call - Change skip_relay_prompt from Option to bool with serde(default) in SpawnPayload Co-Authored-By: Claude Opus 4.6 --- packages/sdk/src/relay.ts | 1 + src/main.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/relay.ts b/packages/sdk/src/relay.ts index 4839dc3de..902b8aa34 100644 --- a/packages/sdk/src/relay.ts +++ b/packages/sdk/src/relay.ts @@ -1257,6 +1257,7 @@ export class AgentRelay { args, channels, task, + skipRelayPrompt: options?.skipRelayPrompt, }); } catch (error) { await this.invokeLifecycleHook( diff --git a/src/main.rs b/src/main.rs index 32bd5834d..064795c89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -450,7 +450,7 @@ struct SpawnPayload { /// When true, skip injecting the relay MCP configuration into the spawned agent. /// Useful for minor tasks where relay messaging is not needed, saving tokens. #[serde(default)] - skip_relay_prompt: Option, + skip_relay_prompt: bool, } #[derive(Debug, Deserialize)] @@ -3443,7 +3443,7 @@ async fn handle_sdk_frame( None, payload.idle_threshold_secs, worker_relay_key.clone(), - payload.skip_relay_prompt.unwrap_or(false), + payload.skip_relay_prompt, ) .await?; if let Some(task) = effective_task.clone() { From ee2d2c949304a1d8d40ce6a51e3d59a86168847f Mon Sep 17 00:00:00 2001 From: Khaliq Date: Tue, 10 Mar 2026 13:42:49 +0100 Subject: [PATCH 3/5] fix: forward skip_relay_prompt in Python SDK and skip pre-registration in broker 1. Add skip_relay_prompt parameter to AgentSpawner.spawn() in the Python SDK so callers can pass it through to spawn_pty (fixes TypeError when using relay.claude.spawn(skip_relay_prompt=True)). 2. Skip Relaycast agent pre-registration in the broker's spawn_agent handler when skip_relay_prompt is true. There is no need to register an agent that will not use relay messaging, and a registration failure should not abort a spawn that explicitly opted out. Co-Authored-By: Claude Opus 4.6 --- packages/sdk-py/src/agent_relay/relay.py | 2 ++ src/main.rs | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/sdk-py/src/agent_relay/relay.py b/packages/sdk-py/src/agent_relay/relay.py index 0bb4b8c65..f8db2e581 100644 --- a/packages/sdk-py/src/agent_relay/relay.py +++ b/packages/sdk-py/src/agent_relay/relay.py @@ -305,6 +305,7 @@ async def spawn( task: Optional[str] = None, model: Optional[str] = None, cwd: Optional[str] = None, + skip_relay_prompt: Optional[bool] = None, on_start: LifecycleHook = None, on_success: LifecycleHook = None, on_error: LifecycleHook = None, @@ -333,6 +334,7 @@ async def spawn( task=task, model=model, cwd=cwd, + skip_relay_prompt=skip_relay_prompt, ) except Exception as error: await self._relay._invoke_lifecycle_hook( diff --git a/src/main.rs b/src/main.rs index 064795c89..ce839e30e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3377,8 +3377,13 @@ async fn handle_sdk_frame( // was warmed by preflight_agents, this is an instant cache hit (<1ms). // If registration times out or fails retryably, we proceed without a // token — the agent self-registers via MCP on first connect. + // Skip pre-registration when skip_relay_prompt is true — the agent + // won't use relay messaging so there is no need to register it, and + // a registration failure should not abort the spawn. let mut preregistration_warning: Option = None; - let worker_relay_key = if let Some(http) = relaycast_http { + let worker_relay_key = if payload.skip_relay_prompt { + None + } else if let Some(http) = relaycast_http { const REGISTRATION_TIMEOUT: Duration = Duration::from_secs(15); match tokio::time::timeout( REGISTRATION_TIMEOUT, From 65cfa41552a48b3b64cea39ca6ed333e9b9f4c4e Mon Sep 17 00:00:00 2001 From: Khaliq Date: Tue, 10 Mar 2026 13:54:36 +0100 Subject: [PATCH 4/5] fix(supervisor): preserve skip_relay_prompt on restart --- src/main.rs | 53 ++++++++++++++++++++++++++--------------------- src/supervisor.rs | 40 ++++++++++++++++++++++++++++------- 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/src/main.rs b/src/main.rs index ce839e30e..5950543a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3008,30 +3008,34 @@ async fn run_init(cmd: InitCommand, telemetry: TelemetryClient) -> Result<()> { continue; } - let worker_relay_key = match relaycast_http - .register_agent_token(&name, rst.spec.cli.as_deref()) - .await - { - Ok(token) => token, - Err(error) => { - match registration_retry_after_secs(&error) { - Some(retry_after_secs) => { - tracing::warn!( - worker = %name, - retry_after_secs, - error = %error, - "restart blocked by relaycast registration rate limit" - ); - } - None => { - tracing::error!( - worker = %name, - error = %error, - "failed to pre-register worker before restart" - ); + let worker_relay_key = if rst.skip_relay_prompt { + None + } else { + match relaycast_http + .register_agent_token(&name, rst.spec.cli.as_deref()) + .await + { + Ok(token) => Some(token), + Err(error) => { + match registration_retry_after_secs(&error) { + Some(retry_after_secs) => { + tracing::warn!( + worker = %name, + retry_after_secs, + error = %error, + "restart blocked by relaycast registration rate limit" + ); + } + None => { + tracing::error!( + worker = %name, + error = %error, + "failed to pre-register worker before restart" + ); + } } + continue; } - continue; } }; @@ -3040,8 +3044,8 @@ async fn run_init(cmd: InitCommand, telemetry: TelemetryClient) -> Result<()> { rst.spec.clone(), rst.parent.clone(), None, - Some(worker_relay_key), - false, + worker_relay_key, + rst.skip_relay_prompt, ) .await { @@ -3483,6 +3487,7 @@ async fn handle_sdk_frame( payload.agent.clone(), None, initial_task_for_supervisor, + payload.skip_relay_prompt, restart_policy, ); workers.metrics.on_spawn(&name); diff --git a/src/supervisor.rs b/src/supervisor.rs index d9f0c5e5a..4b71653b5 100644 --- a/src/supervisor.rs +++ b/src/supervisor.rs @@ -57,6 +57,7 @@ struct RestartState { pub spec: AgentSpec, pub initial_task: Option, pub parent: Option, + pub skip_relay_prompt: bool, } /// Decision returned by the supervisor after an agent exits. @@ -72,6 +73,7 @@ pub struct PendingRestart { pub parent: Option, pub initial_task: Option, pub restart_count: u32, + pub skip_relay_prompt: bool, } /// Manages restart state for all supervised agents. @@ -99,6 +101,7 @@ impl Supervisor { spec: AgentSpec, parent: Option, initial_task: Option, + skip_relay_prompt: bool, policy: RestartPolicy, ) { self.states.insert( @@ -111,6 +114,7 @@ impl Supervisor { spec, initial_task, parent, + skip_relay_prompt, }, ); } @@ -187,6 +191,7 @@ impl Supervisor { parent: state.parent.clone(), initial_task: state.initial_task.clone(), restart_count: state.total_restarts + 1, + skip_relay_prompt: state.skip_relay_prompt, }, )) } else { @@ -257,7 +262,7 @@ mod tests { #[test] fn register_and_unregister() { let mut sup = Supervisor::new(); - sup.register("w1", test_spec("w1"), None, None, RestartPolicy::default()); + sup.register("w1", test_spec("w1"), None, None, false, RestartPolicy::default()); assert!(sup.is_supervised("w1")); sup.unregister("w1"); @@ -278,6 +283,7 @@ mod tests { test_spec("w1"), Some("lead".into()), Some("do stuff".into()), + true, RestartPolicy::default(), ); @@ -298,7 +304,7 @@ mod tests { max_consecutive_failures: 10, // high so this doesn't trigger ..Default::default() }; - sup.register("w1", test_spec("w1"), None, None, policy); + sup.register("w1", test_spec("w1"), None, None, false, policy); // First crash -> restart assert!(matches!( @@ -327,7 +333,7 @@ mod tests { max_restarts: 10, // high so this doesn't trigger ..Default::default() }; - sup.register("w1", test_spec("w1"), None, None, policy); + sup.register("w1", test_spec("w1"), None, None, false, policy); // Crash 1 -> consecutive=1, restart assert!(matches!( @@ -355,7 +361,7 @@ mod tests { max_restarts: 10, ..Default::default() }; - sup.register("w1", test_spec("w1"), None, None, policy); + sup.register("w1", test_spec("w1"), None, None, false, policy); // Two crashes sup.on_exit("w1", Some(1), None); @@ -378,7 +384,7 @@ mod tests { enabled: false, ..Default::default() }; - sup.register("w1", test_spec("w1"), None, None, policy); + sup.register("w1", test_spec("w1"), None, None, false, policy); let decision = sup.on_exit("w1", Some(1), None).unwrap(); assert!(matches!(decision, RestartDecision::PermanentlyDead { .. })); @@ -387,7 +393,7 @@ mod tests { #[test] fn released_agent_not_restarted() { let mut sup = Supervisor::new(); - sup.register("w1", test_spec("w1"), None, None, RestartPolicy::default()); + sup.register("w1", test_spec("w1"), None, None, false, RestartPolicy::default()); sup.unregister("w1"); // Should return None — not supervised @@ -406,6 +412,7 @@ mod tests { test_spec("w1"), Some("lead".into()), Some("task".into()), + true, policy, ); @@ -418,6 +425,7 @@ mod tests { assert_eq!(pending[0].1.parent.as_deref(), Some("lead")); assert_eq!(pending[0].1.initial_task.as_deref(), Some("task")); assert_eq!(pending[0].1.restart_count, 1); + assert!(pending[0].1.skip_relay_prompt); } #[test] @@ -427,7 +435,7 @@ mod tests { cooldown_ms: 60_000, // 60 seconds ..Default::default() }; - sup.register("w1", test_spec("w1"), None, None, policy); + sup.register("w1", test_spec("w1"), None, None, false, policy); sup.on_exit("w1", Some(1), None); @@ -439,7 +447,7 @@ mod tests { #[test] fn restart_count_tracks_total() { let mut sup = Supervisor::new(); - sup.register("w1", test_spec("w1"), None, None, RestartPolicy::default()); + sup.register("w1", test_spec("w1"), None, None, false, RestartPolicy::default()); assert_eq!(sup.restart_count("w1"), 0); @@ -452,6 +460,22 @@ mod tests { assert_eq!(sup.restart_count("w1"), 2); } + #[test] + fn pending_restarts_preserve_skip_relay_prompt() { + let mut sup = Supervisor::new(); + let policy = RestartPolicy { + cooldown_ms: 0, + ..Default::default() + }; + sup.register("w1", test_spec("w1"), None, None, true, policy); + + sup.on_exit("w1", Some(1), None); + + let pending = sup.pending_restarts(); + assert_eq!(pending.len(), 1); + assert!(pending[0].1.skip_relay_prompt); + } + #[test] fn restart_count_returns_zero_for_unknown() { let sup = Supervisor::new(); From f8925a3b9ef6c70a52763fa077e42ceeb0b9255a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Mar 2026 13:27:26 +0000 Subject: [PATCH 5/5] style: auto-format Rust code with cargo fmt --- src/supervisor.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/supervisor.rs b/src/supervisor.rs index 4b71653b5..20a2c2cff 100644 --- a/src/supervisor.rs +++ b/src/supervisor.rs @@ -262,7 +262,14 @@ mod tests { #[test] fn register_and_unregister() { let mut sup = Supervisor::new(); - sup.register("w1", test_spec("w1"), None, None, false, RestartPolicy::default()); + sup.register( + "w1", + test_spec("w1"), + None, + None, + false, + RestartPolicy::default(), + ); assert!(sup.is_supervised("w1")); sup.unregister("w1"); @@ -393,7 +400,14 @@ mod tests { #[test] fn released_agent_not_restarted() { let mut sup = Supervisor::new(); - sup.register("w1", test_spec("w1"), None, None, false, RestartPolicy::default()); + sup.register( + "w1", + test_spec("w1"), + None, + None, + false, + RestartPolicy::default(), + ); sup.unregister("w1"); // Should return None — not supervised @@ -447,7 +461,14 @@ mod tests { #[test] fn restart_count_tracks_total() { let mut sup = Supervisor::new(); - sup.register("w1", test_spec("w1"), None, None, false, RestartPolicy::default()); + sup.register( + "w1", + test_spec("w1"), + None, + None, + false, + RestartPolicy::default(), + ); assert_eq!(sup.restart_count("w1"), 0);