spawn_parallel_agents: opt out of the 120s per-tool timeout so the fan-out isn't truncated (#4686)

This commit is contained in:
Mega Mind
2026-07-07 23:23:02 -07:00
committed by GitHub
parent bcb9bc4701
commit 63163ea1be
2 changed files with 30 additions and 1 deletions
@@ -14,7 +14,9 @@ use crate::openhuman::agent_orchestration::spawn_parallel_graph::{
SpawnParallelGraphOutcome, SpawnParallelTaskValidationError,
};
use crate::openhuman::tinyagents::current_run_cancellation;
use crate::openhuman::tools::traits::{PermissionLevel, Tool, ToolCallOptions, ToolResult};
use crate::openhuman::tools::traits::{
PermissionLevel, Tool, ToolCallOptions, ToolResult, ToolTimeout,
};
use async_trait::async_trait;
use serde_json::json;
use tinyagents::harness::tool::ToolExecutionContext;
@@ -95,6 +97,20 @@ impl Tool for SpawnParallelAgentsTool {
PermissionLevel::Execute
}
/// Run **without** the global per-tool wall-clock deadline. This is a
/// fan-out primitive: it spawns N independent sub-agents and awaits them
/// concurrently via `spawn_parallel_graph`'s bounded `map_reduce`. Under the
/// default `Inherit` policy the whole fan-out is hard-killed at the
/// single-tool timeout (120s by default) — so a group of long workers is
/// truncated at one worker's budget instead of running to ~the slowest, and
/// each worker's research is cut short. The fan-out is already bounded
/// internally — by `max_concurrency`, the run cancellation token, and each
/// sub-agent's own iteration/turn caps — so it governs its own lifetime,
/// like the long-running scripting tools (`shell`, `node_exec`).
fn timeout_policy(&self, _args: &serde_json::Value) -> ToolTimeout {
ToolTimeout::Unbounded
}
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
self.execute_with_context(args, ToolCallOptions::default(), None)
.await
@@ -16,6 +16,7 @@ use crate::openhuman::inference::provider::{
ChatRequest, ChatResponse, ConversationMessage, Provider, ToolCall,
};
use crate::openhuman::memory::{Memory, MemoryCategory, MemoryEntry, NamespaceSummary, RecallOpts};
use crate::openhuman::tools::traits::ToolTimeout;
use crate::openhuman::tools::{PermissionLevel, Tool, ToolResult};
use async_trait::async_trait;
use parking_lot::Mutex;
@@ -899,3 +900,15 @@ async fn agent_turn_runs_long_parallel_subagent_flow_with_many_nested_tool_calls
"each subagent should run three tool calls plus a final completion iteration"
);
}
#[test]
fn spawn_parallel_agents_opts_out_of_the_global_tool_timeout() {
// A fan-out of N long sub-agents must not be hard-killed at the single-tool
// wall-clock default (120s): that truncates every worker and bounds the whole
// group at one worker's budget. The tool governs its own lifetime via its
// internal max_concurrency, cancellation token, and per-sub-agent caps.
assert_eq!(
SpawnParallelAgentsTool::new().timeout_policy(&json!({})),
ToolTimeout::Unbounded,
);
}