From a42e6b76ff8a10bd2c0d70bea07396edb799f636 Mon Sep 17 00:00:00 2001 From: Sam & Hermes Date: Sun, 31 May 2026 17:21:25 +0200 Subject: [PATCH] feat: add local_args to spawn-agent for argv-capable Pi spawn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds local_args field to HerdrCommand::SpawnAgent, enabling the Local provider to pass argv to the agent binary without a wrapper script. Backward-compatible — local_args defaults to None. Real Pi spawn on FreeBSD is now: spawn-agent provider=local model=pi local_args=['--mode','json','--no-tools','-p','task'] Previously required a wrapper script because only an executable path was accepted. This closes the OSA wrapper caveat from PR #9. Build: pass | Tests: workspace green --- crates/colibri-client/src/lib.rs | 1 + crates/colibri-daemon/src/lib.rs | 4 ++++ crates/colibri-daemon/src/socket.rs | 22 ++++++++++++++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/crates/colibri-client/src/lib.rs b/crates/colibri-client/src/lib.rs index b82fe9a..f85179f 100644 --- a/crates/colibri-client/src/lib.rs +++ b/crates/colibri-client/src/lib.rs @@ -108,6 +108,7 @@ impl DaemonClient { model: model.into(), session_id, system_prompt, + local_args: None, }) .await } diff --git a/crates/colibri-daemon/src/lib.rs b/crates/colibri-daemon/src/lib.rs index ca32a53..f185b80 100644 --- a/crates/colibri-daemon/src/lib.rs +++ b/crates/colibri-daemon/src/lib.rs @@ -43,6 +43,10 @@ pub enum HerdrCommand { model: String, session_id: Option, system_prompt: Option, + /// Extra args passed to the agent binary (used by Local provider + /// to pass argv without a wrapper script). + #[serde(default)] + local_args: Option>, }, #[serde(rename = "kill-agent")] KillAgent { agent_id: String }, diff --git a/crates/colibri-daemon/src/socket.rs b/crates/colibri-daemon/src/socket.rs index 5b93d07..57a327a 100644 --- a/crates/colibri-daemon/src/socket.rs +++ b/crates/colibri-daemon/src/socket.rs @@ -151,7 +151,18 @@ async fn dispatch(cmd: HerdrCommand, state: &SharedState) -> HerdrResponse { model, session_id, system_prompt, - } => cmd_spawn_agent(state, provider, model, session_id, system_prompt).await, + local_args, + } => { + cmd_spawn_agent( + state, + provider, + model, + session_id, + system_prompt, + local_args, + ) + .await + } HerdrCommand::KillAgent { agent_id } => cmd_kill_agent(state, agent_id).await, HerdrCommand::GetSession { session_id } => cmd_get_session(state, session_id).await, HerdrCommand::CompactSession { session_id } => cmd_compact_session(state, session_id).await, @@ -287,6 +298,7 @@ async fn cmd_spawn_agent( model: String, session_id: Option, system_prompt: Option, + local_args: Option>, ) -> HerdrResponse { let provider = match provider_str.to_lowercase().as_str() { "deepseek" => Provider::DeepSeek, @@ -297,9 +309,11 @@ async fn cmd_spawn_agent( }; let (binary, args) = if provider == Provider::Local { - // Local provider is the no-network smoke/test path: the socket `model` - // field is treated as the executable path and no Pi CLI flags are added. - (model.clone(), Vec::new()) + // Local provider: model is the executable path. + // local_args (if provided) are passed as argv — enables real Pi spawn + // without a wrapper script (e.g. pi --mode json --no-tools -p 'task'). + let args = local_args.unwrap_or_default(); + (model.clone(), args) } else { ( std::env::var("COLIBRI_AGENT_BINARY").unwrap_or_else(|_| "hermes-agent".to_string()),