mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(agent): route parallel/council fan-out to one concurrent spawn_parallel_agents call (#4754) (#4757)
This commit is contained in:
@@ -75,7 +75,16 @@ impl Tool for SpawnSubagentTool {
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Delegate a task to a specialised sub-agent only when direct \
|
||||
response or direct tools are insufficient. See the Delegation \
|
||||
response or direct tools are insufficient. Handles ONE delegated task \
|
||||
per call: by default it runs as a reusable async worker and returns \
|
||||
immediately — pass `blocking: true` to run it inline and get the \
|
||||
sub-agent's final output back in this turn. To run several independent \
|
||||
workers at once (e.g. \"a separate researcher for each X\", a council \
|
||||
of opinions, or \"fan out over N items\"), use `spawn_parallel_agents` \
|
||||
with one task per worker — a SINGLE call that launches them \
|
||||
concurrently. Do NOT call this tool in a loop to fan out: repeated \
|
||||
`spawn_subagent` calls each delegate a single task and never launch \
|
||||
workers concurrently, which serializes the whole request. See the Delegation \
|
||||
Guide in the system prompt for available agent_ids and when to \
|
||||
use each. When delegating to `integrations_agent`, you MUST also pass \
|
||||
`toolkit=\"<name>\"` naming the Composio integration the \
|
||||
|
||||
@@ -115,6 +115,16 @@ succeeded. Do **not** use it when the subtasks depend on each other's output (se
|
||||
those, or use `rhai_workflows` for real control flow), and don't fan out work that a
|
||||
single delegation or a direct tool already covers.
|
||||
|
||||
**Fan-out is ONE `spawn_parallel_agents` call, never a loop of `spawn_subagent`.** When the
|
||||
user asks for parallel work — "in parallel", "a separate researcher/agent for each X",
|
||||
"convene a council / get multiple independent opinions", "fan out and summarize each of my
|
||||
last N threads" — put **all** the workers into a **single** `spawn_parallel_agents` call
|
||||
(one task per worker). Do **not** call `spawn_subagent` once per worker: those calls run
|
||||
**strictly one-at-a-time** (each sub-agent finishes, ~145s+, before the next even starts),
|
||||
which serializes the whole request and defeats the explicit parallel/council intent.
|
||||
`spawn_parallel_agents` launches every worker at once and returns when the slowest is done.
|
||||
If the user names N targets or asks for N opinions, that is N tasks in **one** call.
|
||||
|
||||
### Async background sub-agents
|
||||
|
||||
Use `spawn_async_subagent` only for low-attention background work where the current user
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
//! Pins the orchestrator's parallel/council fan-out routing (#4754).
|
||||
//!
|
||||
//! An agent-efficiency eval found the orchestrator never fanning out workers
|
||||
//! concurrently: parallel/"separate researcher for each"/council prompts either
|
||||
//! single-spawned or issued serial `spawn_subagent` calls 145-200s apart
|
||||
//! (each sub-agent finishing before the next started), defeating the request.
|
||||
//!
|
||||
//! Root cause is routing, not harness concurrency: `spawn_parallel_agents`
|
||||
//! already fans out concurrently (tinyagents `map_reduce` / `buffer_unordered`)
|
||||
//! as a single tool call, but the orchestrator reached for serial
|
||||
//! `spawn_subagent` instead. The fix steers parallel/council/fan-out requests
|
||||
//! to ONE `spawn_parallel_agents` call — in both the system prompt and the
|
||||
//! `spawn_subagent` tool description the model reads when choosing a tool.
|
||||
//!
|
||||
//! These assertions pin that guidance so a future edit can't silently drop it.
|
||||
|
||||
const ORCHESTRATOR_PROMPT: &str =
|
||||
include_str!("../src/openhuman/agent_registry/agents/orchestrator/prompt.md");
|
||||
|
||||
const SPAWN_SUBAGENT_SRC: &str =
|
||||
include_str!("../src/openhuman/agent_orchestration/tools/spawn_subagent.rs");
|
||||
|
||||
#[test]
|
||||
fn prompt_routes_parallel_and_council_to_spawn_parallel_agents() {
|
||||
// The parallel-fanout guidance must exist and name the concurrent primitive.
|
||||
assert!(
|
||||
ORCHESTRATOR_PROMPT.contains("spawn_parallel_agents"),
|
||||
"orchestrator prompt must route fan-out to `spawn_parallel_agents` (#4754)"
|
||||
);
|
||||
// It must explicitly cover the council / multiple-independent-opinions case
|
||||
// that the eval showed collapsing to a single spawn.
|
||||
assert!(
|
||||
ORCHESTRATOR_PROMPT.to_lowercase().contains("council"),
|
||||
"orchestrator prompt must steer council / multiple-opinions requests to \
|
||||
a parallel fan-out, not a single spawn (#4754)"
|
||||
);
|
||||
// It must warn against the serial anti-pattern (looping `spawn_subagent`),
|
||||
// which is what produced the 145-200s serial gaps.
|
||||
let p = ORCHESTRATOR_PROMPT.to_lowercase();
|
||||
assert!(
|
||||
p.contains("never a loop of") || p.contains("do **not** call `spawn_subagent` once per"),
|
||||
"orchestrator prompt must warn that repeated `spawn_subagent` serializes \
|
||||
fan-out and to use one `spawn_parallel_agents` call instead (#4754)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_subagent_description_redirects_fanout_to_parallel() {
|
||||
// The tool description is what the model reads while picking a tool, so the
|
||||
// redirect has to live there, not only in the system prompt. Anchor on the
|
||||
// description() body to avoid matching an unrelated mention elsewhere.
|
||||
let desc_start = SPAWN_SUBAGENT_SRC
|
||||
.find("fn description(&self)")
|
||||
.expect("spawn_subagent must have a description()");
|
||||
let desc = &SPAWN_SUBAGENT_SRC[desc_start..];
|
||||
let desc_body = &desc[..desc.find("fn parameters_schema").unwrap_or(desc.len())];
|
||||
assert!(
|
||||
desc_body.contains("spawn_parallel_agents"),
|
||||
"spawn_subagent's description must redirect concurrent fan-out to \
|
||||
`spawn_parallel_agents` so the model picks the parallel tool (#4754)"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user