diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index d7287ab48..1a3842457 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -1821,21 +1821,27 @@ pub fn run() { // burns through the same 2 MB. In `crahs.log` (2026-05-17, build // 0.53.49) that tower plus the serde-monomorphised `Config` Visitor // frames pushed past the guard page and aborted with - // `SIGBUS / KERN_PROTECTION_FAILURE`. The structural fix - // (`spawn_blocking` for the TOML parse + cache in - // `src/openhuman/config/{schema/load.rs, ops.rs}`) moves the - // largest contributor off the worker; bumping the worker stack - // itself gives the rest of the tower comfortable headroom so future - // additions don't immediately re-tip the same scale. 8 MiB matches - // the OS-default pthread main-thread stack on macOS, so we can - // assume "as much room as the main thread" everywhere. + // `SIGBUS / KERN_PROTECTION_FAILURE`. + // + // The structural fix (`spawn_blocking` for the TOML parse + cache in + // `src/openhuman/config/{schema/load.rs, ops.rs}`) moves the largest + // contributor off the worker. An initial 8 MiB bump shipped here was + // enough for that single tower, but sub-agent delegation (issue #3159 + // / PR #3155) re-tipped the scale: the standalone `openhuman-core` + // CLI server still aborted with `Abort trap: 6 / fatal runtime error: + // stack overflow` once an orchestrator delegated. PR #3155 raised the + // standalone server to 16 MiB; the desktop Tauri host is the *same* + // tower running on a *different* runtime and needs the same headroom. + // Share the constant with the rest of `src/core/*` via + // [`openhuman_core::core::runtime::AGENT_WORKER_STACK_BYTES`] so all + // multi-thread runtimes that may host an agent turn stay in sync. // // Must happen before any `tauri::async_runtime::*` call, otherwise // `set(...)` panics with "runtime already initialized". { let custom_runtime = tokio::runtime::Builder::new_multi_thread() .enable_all() - .thread_stack_size(8 * 1024 * 1024) + .thread_stack_size(openhuman_core::core::runtime::AGENT_WORKER_STACK_BYTES) .build() .expect("build custom tokio runtime for tauri async surface"); let handle = custom_runtime.handle().clone(); diff --git a/src/core/agent_cli.rs b/src/core/agent_cli.rs index 57c5282f7..4a51e47c7 100644 --- a/src/core/agent_cli.rs +++ b/src/core/agent_cli.rs @@ -122,6 +122,7 @@ fn run_dump_all(args: &[String]) -> Result<()> { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() + .thread_stack_size(crate::core::runtime::AGENT_WORKER_STACK_BYTES) .build()?; log::debug!("[agent-cli] run_dump_all: calling dump_all_agent_prompts"); let dumps = rt.block_on(async { @@ -253,6 +254,7 @@ fn run_dump_prompt(args: &[String]) -> Result<()> { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() + .thread_stack_size(crate::core::runtime::AGENT_WORKER_STACK_BYTES) .build()?; log::debug!("[agent-cli] run_dump_prompt: calling dump_agent_prompt"); let dumped = rt.block_on(async { dump_agent_prompt(options).await })?; diff --git a/src/core/cli.rs b/src/core/cli.rs index 5c4d592d2..805edca37 100644 --- a/src/core/cli.rs +++ b/src/core/cli.rs @@ -288,7 +288,7 @@ fn run_server_command(args: &[String]) -> Result<()> { // the JSON-RPC server down mid-request. Give workers a roomier stack. let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() - .thread_stack_size(16 * 1024 * 1024) + .thread_stack_size(crate::core::runtime::AGENT_WORKER_STACK_BYTES) .build()?; rt.block_on(async { crate::core::jsonrpc::run_server(host.as_deref(), port, socketio_enabled).await @@ -337,8 +337,11 @@ fn run_call_command(args: &[String]) -> Result<()> { let method = method.ok_or_else(|| anyhow::anyhow!("--method is required"))?; let params = parse_json_params(¶ms).map_err(anyhow::Error::msg)?; + // `call` invokes a JSON-RPC method that may run an orchestrator turn + // (e.g. `agent.chat`), so it needs the same roomy stack as the server. let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() + .thread_stack_size(crate::core::runtime::AGENT_WORKER_STACK_BYTES) .build()?; let value = rt .block_on(async { invoke_method(default_state(), &method, params).await }) @@ -415,8 +418,11 @@ fn run_namespace_command( let method = all::rpc_method_from_parts(namespace, function) .ok_or_else(|| anyhow::anyhow!("unregistered controller '{namespace}.{function}'"))?; + // Same as the explicit `call` path above — any registered controller may + // ultimately drive an orchestrator turn. let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() + .thread_stack_size(crate::core::runtime::AGENT_WORKER_STACK_BYTES) .build()?; let value = rt .block_on(async { invoke_method(default_state(), &method, Value::Object(params)).await }) diff --git a/src/core/mod.rs b/src/core/mod.rs index a7ae071c7..3a2f185a9 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -20,6 +20,7 @@ pub mod logging; pub mod memory_cli; pub mod observability; pub mod rpc_log; +pub mod runtime; pub mod shutdown; pub mod socketio; pub mod types; diff --git a/src/core/runtime.rs b/src/core/runtime.rs new file mode 100644 index 000000000..1f83bb300 --- /dev/null +++ b/src/core/runtime.rs @@ -0,0 +1,16 @@ +//! Shared tokio runtime tuning constants. +//! +//! A single agent turn is a very large async state machine (system prompt + +//! hundreds of tool specs + the nested provider/tool loop), and delegating +//! to a sub-agent runs another full turn one level down. Even with the inner +//! sub-agent future boxed, that nesting overflows tokio's default 2 MiB +//! worker-thread stack and aborts the whole process (SIGABRT: +//! "thread 'tokio-rt-worker' has overflowed its stack"). +//! +//! PR #3155 set this on the standalone `openhuman-core run` JSON-RPC server. +//! Issue #3159 calls out that every other multi-thread runtime that can host +//! an agent turn (the desktop Tauri host's runtime, `agent_cli`, the rest of +//! `cli.rs`, …) shares the same exposure. Centralising the value keeps them +//! in sync; downstream call sites should set `.thread_stack_size(AGENT_WORKER_STACK_BYTES)` +//! on every multi-thread runtime that may host an agent turn. +pub const AGENT_WORKER_STACK_BYTES: usize = 16 * 1024 * 1024;