Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
5883dfe
refactor(orchestrator-tools): replace collapsed delegation tool with …
senamakel Sep 22, 2026
3fd5aa6
fix(orchestrator): remove stale reference to sanitise_slug in doc com…
senamakel Sep 22, 2026
6cb0fd0
refactor(orchestration): remove collapsed skill-delegation tool
senamakel Sep 22, 2026
09729c4
refactor(orchestration): remove unused skill delegation tool
senamakel Sep 22, 2026
1afd2a0
refactor(agent): consolidate allowed subagent id logic into AgentDefi…
senamakel Sep 22, 2026
0ab0d7f
fix: remove unused import of SubagentEntry
senamakel Sep 22, 2026
7df470b
feat(agent): flatten integration actions into direct tool search
senamakel Sep 22, 2026
641dfdb
feat(orchestrator): remove integrations sub-agent from prompt
senamakel Sep 22, 2026
de719ef
fix(session_host): correct integration announcement to reference tool…
senamakel Sep 22, 2026
df56a37
refactor(agent): update comments to reflect skills wildcard expansion
senamakel Sep 22, 2026
dec4dc6
feat(agent): replace integrations_agent delegation with direct tool_s…
senamakel Sep 22, 2026
e46cabc
fix(agent): correct tools_agent comment to reference orchestrator
senamakel Sep 22, 2026
fb5a846
chore: update comments to reflect removal of delegate_to_integrations…
senamakel Sep 22, 2026
4fa1adf
fix: collect allowed subagent ids into a Vec
senamakel Sep 22, 2026
32aad8e
test(orchestrator): replace collapsed delegation tool with per-action…
senamakel Sep 22, 2026
f7fe302
refactor(tests): replace delegation-guard tests with search-bridge tests
senamakel Sep 22, 2026
70618db
test(orchestration): replace delegate_to_integrations_agent with rese…
senamakel Sep 22, 2026
e3133c6
test(dispatch): verify retired delegation name no longer selects type…
senamakel Sep 22, 2026
6b1a1a7
test(orchestration): remove obsolete skill delegation e2e test
senamakel Sep 22, 2026
598938d
docs(collapsed_delegation): update module-level doc to reflect curren…
senamakel Sep 22, 2026
1cd6d89
chore(docs): remove stale doc references from collapsed delegation mo…
senamakel Sep 22, 2026
91cd0eb
chore(docs): remove stale cross-reference from collapsed delegation doc
senamakel Sep 22, 2026
0605f17
fix(dispatch): correct doc and test for worker spawn exemption
senamakel Sep 22, 2026
09078cc
fix(tests): update exposure assertion to match hidden archetype deleg…
senamakel Sep 22, 2026
63ed318
test(composio_list_tools_stack_overflow_regression): update comments …
senamakel Sep 22, 2026
e607cd4
test(orchestrator): update integration routing to use tool search ins…
senamakel Sep 22, 2026
2eeea2e
test(raw_coverage): remove SkillDelegationTool tests and update integ…
senamakel Sep 22, 2026
7b08d05
fix(scripts/prompt-eval): update composio-gmail-read case for orchest…
senamakel Sep 22, 2026
050e030
docs(prompt-evals): update composio-gmail-read to reflect orchestrato…
senamakel Sep 22, 2026
b1da249
docs(architecture): clarify that integrations agent is not reachable …
senamakel Sep 22, 2026
5027e3d
feat(timeline): format direct connected-service actions by provider
senamakel Sep 22, 2026
de8a58e
fix(timeline): simplify integration action name validation
senamakel Sep 22, 2026
641db94
fix(imports): reorder ArchetypeDelegationTool import to resolve unuse…
senamakel Sep 22, 2026
d905eee
chore(prompts): simplify wording in orchestrator, planner, and tools …
senamakel Sep 22, 2026
cfc1da5
fix(session): exclude deferred tool schemas from text-dialect prompt
senamakel Sep 22, 2026
c830a43
feat(agent): add bridge prompt tools for text dialect tool discovery
senamakel Sep 22, 2026
a1de5c5
feat(agent): switch PromptTool fields to Cow to support owned tool en…
senamakel Sep 22, 2026
c565771
fix: wrap tool name and description in `Cow::Borrowed` to match expec…
senamakel Sep 22, 2026
9461e44
test(tool_prep): use PromptTool::with_schema in test helper
senamakel Sep 22, 2026
4f81a2c
test: add tests for bridge prompt tools behaviour with deferred catal…
senamakel Sep 22, 2026
06786d9
feat(prompts): extract deferred-tool bridge into shared helper
senamakel Sep 22, 2026
1d25092
test: add tests for deferred tool swapping behaviour
senamakel Sep 22, 2026
263adad
chore: files changed crates/openhuman-core/src/agent/prompts/types.rs…
senamakel Sep 22, 2026
d8f2a9e
fix(orchestrator): tighten tool-search and live-data instructions
senamakel Sep 22, 2026
059a6d3
fix(prompt): clarify tool_search and call sequence for connected serv…
senamakel Sep 22, 2026
dcf8363
chore: files changed crates/openhuman-core/src/agent/registry/agents/…
senamakel Sep 22, 2026
6d6bd1d
chore(deps): update tinyagents subproject commit
senamakel Sep 22, 2026
aae205d
Merge upstream/main into remove-integrations-delegate
senamakel Sep 22, 2026
7d9ffca
chore(deps): update tinyagents subproject commit
senamakel Sep 22, 2026
a62a8fe
chore(deps): pin vendor/tinyagents to the DSML tool_call tag fix
senamakel Sep 22, 2026
d7d062a
fix(orchestrator): clarify tool-search instruction in prompt
senamakel Sep 22, 2026
f895a99
fix(prompt): correct ambiguous phrasing in orchestrator prompt
senamakel Sep 22, 2026
3a77549
fix(prompt): remove redundant instruction from orchestrator prompt
senamakel Sep 22, 2026
d34e8e4
fix(orchestrator): update prompt test assertions to match new wording
senamakel Sep 22, 2026
9a8a000
test: reformat test assertions for readability in toolTimelineFormatting
senamakel Sep 22, 2026
f94ffcf
chore(deps): follow the tinyagents pin after the tinytools repoint
senamakel Sep 22, 2026
4573eba
Merge upstream/main into remove-integrations-delegate
senamakel Sep 22, 2026
c745015
chore(deps): update tinyjuice dependencies and reformat test code
senamakel Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 11 additions & 32 deletions app/src/utils/__tests__/toolTimelineFormatting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,43 +68,23 @@ describe('formatTimelineEntry', () => {
});
});

it('formats delegate_to_integrations_agent with a known toolkit arg', () => {
it('labels a direct connected-service action by its provider', () => {
expect(
formatTimelineEntry(
entry({
name: 'delegate_to_integrations_agent',
argsBuffer: JSON.stringify({
toolkit: 'gmail',
prompt: 'Find the latest invoice from Stripe.',
}),
})
entry({ name: 'GMAIL_SEND_EMAIL', argsBuffer: JSON.stringify({ to: 'alex@example.com' }) })
)
).toEqual({
title: 'Making requests to your Gmail account',
detail: 'Find the latest invoice from Stripe.',
).toEqual({ title: 'Making requests to your Gmail account', detail: 'Send email' });
expect(formatTimelineEntry(entry({ name: 'GOOGLE_CALENDAR_CREATE_EVENT' }))).toEqual({
title: 'Updating your Google Calendar',
detail: 'Create event',
});
});

it('formats delegate_to_integrations_agent with an unknown toolkit arg', () => {
expect(
formatTimelineEntry(
entry({
name: 'delegate_to_integrations_agent',
argsBuffer: JSON.stringify({ toolkit: 'slack_bot', prompt: 'post update' }),
})
)
).toEqual({ title: 'Checking your Slack Bot', detail: 'post update' });
});

it('formats delegate_to_integrations_agent without a toolkit arg as a generic connected-app label', () => {
expect(
formatTimelineEntry(
entry({
name: 'delegate_to_integrations_agent',
argsBuffer: JSON.stringify({ prompt: 'do something useful' }),
})
)
).toEqual({ title: 'Checking your connected app', detail: 'do something useful' });
it('keeps the generic label for upper-case names on unknown toolkits', () => {
expect(formatTimelineEntry(entry({ name: 'STRIPE_LIST_CHARGES' }))).toEqual({
title: 'STRIPE LIST CHARGES',
detail: undefined,
});
});

it('formats delegate_tools_agent with toolkit context from args', () => {
Expand Down Expand Up @@ -402,7 +382,6 @@ describe('isKnownClientTool', () => {
expect(isKnownClientTool('file_read')).toBe(true);
expect(isKnownClientTool('shell')).toBe(true);
expect(isKnownClientTool('subagent:researcher')).toBe(true);
expect(isKnownClientTool('delegate_to_integrations_agent')).toBe(true);
// The streamed search-slot name, so the client label wins over the
// server's humanized "Web Search Tool".
expect(isKnownClientTool('web_search_tool')).toBe(true);
Expand Down
52 changes: 40 additions & 12 deletions app/src/utils/toolTimelineFormatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,21 +311,23 @@ export function formatTimelineEntry(entry: ToolTimelineEntry): { title: string;
inferIntegrationNameFromPrompt(parsedArgs?.prompt) ??
inferIntegrationName(entry.name);

let title: string;
if (provider) {
title = integrationActivityTitle(provider);
} else if (entry.name === 'delegate_to_integrations_agent') {
const rawToolkit = parsedArgs?.toolkit?.trim();
title = rawToolkit
? integrationActivityTitle(humanizeIdentifier(rawToolkit))
: 'Checking your connected app';
} else {
title = humanizeIdentifier(entry.name);
}

const title = provider ? integrationActivityTitle(provider) : humanizeIdentifier(entry.name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Preserve the integrations-agent delegation label

When entry.name is delegate_to_integrations_agent and no toolkit can be inferred from the arguments or prompt, provider is undefined and this now renders Delegate To Integrations Agent. The removed branch intentionally displayed Checking your connected app (or the selected integration's activity title), so ordinary integration delegations lose their user-facing label. Keep the special case while adding the direct-action handling.


Additional security observation

priority medium confident

Preserve toolkit-specific labels for integration delegation

[RULE] preserve-existing-behavior

For delegate_to_integrations_agent, provider is derived only from the prompt or the entry name. The removed branch used parsedArgs?.toolkit to label the activity, so requests with a toolkit but no recognizable provider in the prompt now display the generic Delegate to integrations agent title instead of the connected service. Retain the toolkit-based fallback when formatting this delegation entry.

[RULE] behavior-regression ·

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium security confident

Preserve the integrations-agent delegation label

This removes the special handling for delegate_to_integrations_agent. When no provider can be inferred from the prompt, the row now falls back to humanizeIdentifier(entry.name) instead of showing the connected-app/checking label (or the toolkit-specific label) that the previous branch supplied. Restore the integrations-agent delegation handling so delegation rows do not regress to a raw internal tool name.

[RULE] timeline-label-regression ·

return { title, detail: entry.detail ?? parsedArgs?.prompt };
}

// A connected-service action called directly (`GMAIL_SEND_EMAIL`,
// `SLACK_SEND_MESSAGE`): the orchestrator finds these through
// `tool_search` and calls them itself, so this is the row a user sees
// for "send that email". Label it by the service, with the action as
// the detail, rather than a raw humanised slug.
const directAction = inferIntegrationActionName(entry.name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium security confident

Add an e2e test for the new timeline integration action labelling

The new direct-action formatting path has no test coverage in the indexed test graph. Add an end-to-end or focused formatter test covering representative actions, including a multi-word toolkit and the GOOGLECALENDAR form, so future changes cannot silently revert service/action labelling.

[RULE] missing-regression-test ·

if (directAction) {
return {
title: integrationActivityTitle(directAction.provider),
detail: entry.detail ?? directAction.action,
};
}

// ── Tool-specific formatting with args-derived detail ──────────────
// Pass the completed result text so args-aware formatters can surface
// details only known post-execution (e.g. the resolved search provider).
Expand Down Expand Up @@ -646,6 +648,32 @@ function inferIntegrationName(input?: string): string | undefined {
return undefined;
}

/**
* Split a Composio action slug (`GMAIL_SEND_EMAIL`) into its known provider
* and a readable action ("Send email"). `undefined` for anything that is not
* an upper-case `<TOOLKIT>_<ACTION>` name on a known toolkit, so ordinary
* tools and unknown toolkits keep their generic label.
*/
function inferIntegrationActionName(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium e2e confident

Add an e2e test for the new timeline integration action labelling

The new inferIntegrationActionName function changes how connected-service actions (e.g. GMAIL_SEND_EMAIL) are displayed in the timeline UI. The change is covered by unit tests (toolTimelineFormatting.test.ts) but there is no Playwright-style end-to-end test that renders a real timeline entry with an integration action and asserts the title/detail. If the frontend builds or the data shape evolves, this label could regress without automated detection.

[RULE] e2e-uncovered ·

name: string
): { provider: string; action: string } | undefined {
if (!/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/.test(name)) return undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Recognize GOOGLECALENDAR action prefixes

A direct action such as GOOGLECALENDAR_CREATE_EVENT passes the uppercase-slug check, but the prefix loop only matches entries from KNOWN_TOOLKIT_RE, which contains google_calendar and not googlecalendar. The action therefore falls through to the generic humanized slug instead of getting the Google Calendar activity title. Add the actual toolkit spelling used by Composio (or normalize both spellings) before parsing the action.

[RULE] incomplete-toolkit-recognition ·

// Try the longest toolkit prefix first (`GOOGLE_CALENDAR_...`), then the
// shortest (`GMAIL_...`).
const parts = name.split('_');
for (let i = Math.min(parts.length - 1, 2); i >= 1; i -= 1) {
const toolkit = parts.slice(0, i).join('_');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique likely

Recognize the GOOGLECALENDAR action prefix

Composio uses action names such as GOOGLECALENDAR_CREATE_EVENT (the repository already uses the same prefix in trigger slugs), but KNOWN_TOOLKIT_RE only recognizes google_calendar and calendar. For GOOGLECALENDAR_CREATE_EVENT, this loop returns undefined, so the new direct-action formatter is skipped and the timeline falls back to a generic humanized label instead of a Google Calendar activity title. Add the googlecalendar toolkit spelling and normalize it to Google Calendar.

[RULE] incomplete-provider-recognition ·

if (KNOWN_TOOLKIT_RE.test(toolkit)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium security confident

Recognize the GOOGLECALENDAR action prefix

Composio action names can use the GOOGLECALENDAR_* prefix, but the existing known-toolkit pattern only includes google_calendar. For a name such as GOOGLECALENDAR_CREATE_EVENT, neither candidate prefix passes this check, so the new direct-action formatter returns undefined and displays the raw slug instead of a Google Calendar label. Include the actual toolkit prefix used by these action names and cover it with a test.

[RULE] integration-toolkit-parsing ·

const action = parts.slice(i).join(' ').toLowerCase();
return {
provider: normalizeIntegrationName(toolkit),
action: action.charAt(0).toUpperCase() + action.slice(1),
};
}
}
return undefined;
}

function integrationActivityTitle(provider: string): string {
switch (provider) {
case 'GitHub':
Expand Down
3 changes: 2 additions & 1 deletion crates/openhuman-app/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions crates/openhuman-app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2370,8 +2370,9 @@ pub fn run() {
// `core_process::CoreProcessHandle::ensure_running` via
// `tokio::spawn(run_server_embedded(..))`) runs *on* that runtime, so
// every JSON-RPC handler — including the deep tower
// `web channel chat → orchestrator turn → delegate_to_integrations_agent
// → sub-agent → composio_list_tools → load_config_with_timeout` —
// `web channel chat → orchestrator turn → integration action tool
// → composio execute → load_config_with_timeout` (and, at the time, the
// now-removed integrations sub-agent spawn in between) —
// 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
Expand Down
6 changes: 3 additions & 3 deletions crates/openhuman-core/src/agent/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ pub struct AgentTurnRequest {
pub visible_tool_names: Option<HashSet<String>>,

/// Per-turn synthesised tools to splice alongside `tools_registry`.
/// The dispatch path uses this to carry `ArchetypeDelegationTool` /
/// `SkillDelegationTool` instances built fresh each turn from the
/// active agent's `subagents` field and the current Composio
/// The dispatch path uses this to carry `ArchetypeDelegationTool`
/// instances and deferred Composio action tools built fresh each turn
/// from the active agent's `subagents` field and the current Composio
/// integrations — tools that don't exist in the global startup
/// registry because they depend on per-user runtime state.
/// Empty vec for agents that don't delegate.
Expand Down
4 changes: 2 additions & 2 deletions crates/openhuman-core/src/agent/debug/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,8 @@ async fn render_integrations_agent(config: &Config, toolkit: &str) -> Result<Dum
let prompt_tools: Vec<PromptTool<'_>> = rendered_tools
.iter()
.map(|t| PromptTool {
name: t.name(),
description: t.description(),
name: std::borrow::Cow::Borrowed(t.name()),
description: std::borrow::Cow::Borrowed(t.description()),
parameters_schema: Some(t.parameters_schema().to_string()),
})
.collect();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,18 +208,16 @@ pub struct AgentDefinition {
/// agent's `delegate_name` override) and whose description is the
/// target agent's [`AgentDefinition::when_to_use`].
///
/// * [`SubagentEntry::Skills`] — a single collapsed
/// [`SkillDelegationTool`] named `delegate_to_integrations_agent`
/// that takes the toolkit slug as an argument and routes to the
/// generic `integrations_agent` with the corresponding
/// `skill_filter` pre-populated (#1335).
/// * [`SubagentEntry::Skills`] — no delegation tool. The connected
/// Composio toolkits' actions join this agent's `Deferred` catalogue
/// (reached through `tool_search`, called directly), and the entry
/// admits no sub-agent id: see [`AgentDefinition::allowed_subagent_ids`].
///
/// `subagents` is intentionally separate from [`AgentDefinition::tools`]
/// so that reading a TOML makes the distinction obvious: `tools` is
/// "what I execute directly", `subagents` is "what I can delegate to".
///
/// [`ArchetypeDelegationTool`]: crate::agent::orchestration::tools::ArchetypeDelegationTool
/// [`SkillDelegationTool`]: crate::agent::orchestration::tools::SkillDelegationTool
#[serde(default, deserialize_with = "deserialize_subagent_entries")]
pub subagents: Vec<SubagentEntry>,

Expand Down Expand Up @@ -275,6 +273,24 @@ pub struct AgentDefinition {
}

impl AgentDefinition {
/// The agent ids this definition may spawn, derived from
/// [`AgentDefinition::subagents`]. Only [`SubagentEntry::AgentId`]
/// entries admit a target; the `{ skills = "*" }` wildcard used to map to
/// `integrations_agent` here, which is what let a chat agent spin up a
/// sub-agent for one integration action it can now search for and call
/// itself. The runner's spawn gate (`parent.allowed_subagent_ids`) reads
/// this, so a definition without a bare id for an agent cannot reach it
/// through `spawn_async_subagent` either.
pub fn allowed_subagent_ids(&self) -> Vec<String> {
self.subagents
.iter()
.filter_map(|entry| match entry {
SubagentEntry::AgentId(id) => Some(id.clone()),
SubagentEntry::Skills(_) => None,
})
.collect()
}

/// Display name with fallback to id.
pub fn display_name(&self) -> &str {
self.display_name.as_deref().unwrap_or(&self.id)
Expand Down
10 changes: 5 additions & 5 deletions crates/openhuman-core/src/agent/harness/definition/subagents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ use serde::{Deserialize, Deserializer, Serialize};
pub enum SubagentEntry {
/// Delegate to a specific built-in or custom agent by id.
AgentId(String),
/// Expand at build time to a single collapsed
/// `delegate_to_integrations_agent` tool whose `toolkit` argument
/// selects which connected Composio toolkit to route to, with
/// `skill_filter` pre-set on the underlying `integrations_agent`
/// dispatch (#1335).
/// Expand at build time to the connected Composio toolkits' actions as
/// `Deferred` tools — off the wire, found through the harness's
/// `tool_search`, and called directly by this agent. No sub-agent is
/// reachable through this entry: it widens the searchable catalogue,
/// not the spawnable set.
Skills(SkillsWildcard),
}

Expand Down
7 changes: 3 additions & 4 deletions crates/openhuman-core/src/agent/harness/definition/tier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,9 @@ impl std::fmt::Display for AgentTier {
/// pairs at boot (see
/// [`crate::agent::registry::agents::validate_tier_hierarchy`]). The
/// runtime spawn gate (`run_subagent`) reuses it as defense-in-depth, but
/// deliberately exempts worker *parents* — at runtime a worker only reaches the
/// spawn chokepoint via the documented collapsed `delegate_to_integrations_agent`
/// path (→ `integrations_agent`, itself a worker), which the loader intentionally
/// leaves untouched.
/// deliberately exempts worker *parents* — a worker's `subagents` list holds
/// no agent id (the loader rejects one), so the only runtime spawn a worker
/// reaches is one the host dispatched for it, not one it chose.
pub fn validate_tier_transition(parent: AgentTier, child: AgentTier) -> Result<(), String> {
match (parent, child) {
(AgentTier::Worker, _) => Err(format!(
Expand Down
8 changes: 5 additions & 3 deletions crates/openhuman-core/src/agent/orchestration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,11 @@ name:
- Control: `steer_subagent`, `continue_subagent`, `close_subagent`,
`wait_subagent`, `wait`, `wait_loop`, `list_subagents`.
- Delegation: `DelegateGraphTool` (`delegate_graph.rs`),
`ArchetypeDelegationTool` and `SkillDelegationTool` (names set per
instance, e.g. `delegate_to_integrations_agent`), `CollapsedDelegationTool`
(`delegate_to`), and `agent_prepare_context`.
`ArchetypeDelegationTool` (name set per instance, e.g. `research`),
`CollapsedDelegationTool` (`delegate_to`), and `agent_prepare_context`.
There is no integrations delegate: connected Composio actions are

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Keep the integrations delegation description consistent

This assertion conflicts with the existing orchestration surface: spawn_subagent still has an explicit integrations_agent toolkit gate, and the repository's integration-agent tests and prompt definitions continue to exercise that delegated agent. Readers following this README will incorrectly conclude that integrations must always be handled directly by the orchestrator and may remove or bypass the required integrations_agent path. Update this section to describe the current delegated-agent behavior, or remove the claim only if the corresponding code is also changed.

[RULE] inaccurate-documentation ·

`Deferred` tools on the orchestrator's own belt, found through
`tool_search` and called directly (`tools/orchestrator_tools.rs`).

`dispatch.rs` (`dispatch_subagent`, the shared spawn path every tool above
calls), `awaiting_user.rs` (the awaiting-user envelope), and
Expand Down
6 changes: 1 addition & 5 deletions crates/openhuman-core/src/agent/orchestration/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
//! - **Control**: `steer_subagent`, `continue_subagent`, `close_subagent`,
//! `wait_subagent`, `wait` / `wait_loop`, `list_subagents`.
//! - **Delegation**: `DelegateGraphTool`, `ArchetypeDelegationTool`,
//! `SkillDelegationTool`, `CollapsedDelegationTool` (`delegate_to`), and
//! `agent_prepare_context`.
//! `CollapsedDelegationTool` (`delegate_to`), and `agent_prepare_context`.
//!
//! `dispatch.rs`, `awaiting_user.rs`, and `worker_thread.rs` are `pub(crate)`
//! helpers shared by the tools above (the common spawn path, the awaiting-user
Expand Down Expand Up @@ -39,8 +38,6 @@ mod delegate_graph;
mod dispatch;
#[path = "tools/list_subagents.rs"]
mod list_subagents;
#[path = "tools/skill_delegation.rs"]
mod skill_delegation;
#[path = "tools/spawn_async_subagent.rs"]
mod spawn_async_subagent;
#[path = "tools/spawn_parallel_agents.rs"]
Expand Down Expand Up @@ -94,7 +91,6 @@ pub(crate) use delegate_graph::DelegateGraphDispatch;
pub use delegate_graph::DelegateGraphTool;
pub(crate) use list_subagents::ListSubagentsDispatch;
pub use list_subagents::ListSubagentsTool;
pub use skill_delegation::{SkillDelegationTool, INTEGRATIONS_DELEGATE_TOOL_NAME};
pub(crate) use spawn_async_subagent::SpawnAsyncSubagentDispatch;
pub use spawn_async_subagent::{scope_spawn_async_subagent_spec, SpawnAsyncSubagentTool};
pub(crate) use spawn_parallel_agents::SpawnParallelAgentsDispatch;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ fn build_scout_prompt_includes_request_focus_and_catalog() {
let prompt = AgentPrepareContextTool::build_scout_prompt(
"summarise my unread gmail",
Some("last 24h"),
"- delegate_to_integrations_agent: route to a connected integration\n",
"- research: web and docs crawler\n",
);
assert!(prompt.contains("[Request]"));
assert!(prompt.contains("summarise my unread gmail"));
assert!(prompt.contains("[Focus]"));
assert!(prompt.contains("last 24h"));
assert!(prompt.contains("[Orchestrator tools]"));
assert!(prompt.contains("delegate_to_integrations_agent"));
assert!(prompt.contains("research"));
assert!(prompt.contains("[context_bundle]"));
}

Expand Down Expand Up @@ -398,16 +398,13 @@ async fn catalog_lists_the_parents_synthesised_delegates_from_its_visible_specs(
],
vec![
catalog_spec("echo", "durable"),
catalog_spec(
"delegate_to_integrations_agent",
"route to a connected integration",
),
catalog_spec("research", "web and docs crawler"),
catalog_spec("agent_prepare_context", "this tool"),
],
);
let catalog = AgentPrepareContextTool::render_parent_tool_catalog(Some(&ctx));
assert!(
catalog.contains("- delegate_to_integrations_agent: route to a connected integration\n"),
catalog.contains("- research: web and docs crawler\n"),
"the parent's delegate must be recommendable: {catalog:?}"
);
assert!(catalog.contains("- echo: durable\n"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@
//! enum, with each target's `when_to_use` kept verbatim in the description —
//! the routing information survives in full, the repetition does not.
//!
//! This is the same collapse [`SkillDelegationTool`] already applied to the
//! *other* delegation axis (#1335): one `delegate_to_integrations_agent` with
//! a `toolkit` argument, instead of one `delegate_<toolkit>` per connected
//! Composio integration. That change made the schema constant in the
//! integration dimension; this one makes it constant in the sub-agent
//! dimension. The two are now consistent.
//! The integration axis went a step further: connected Composio actions are
//! `Deferred` tools reached through `tool_search` and called directly, so
//! that axis has no delegation tool at all (`orchestrator_tools`). This one
//! makes the sub-agent axis constant in the sub-agent dimension.
//!
//! # Why collapse rather than pack
//!
Expand Down Expand Up @@ -52,12 +50,9 @@
//! builder's collision guard resolves a clash by dropping the *synthesised*
//! tool. Naming this one `delegate` would therefore have removed the
//! orchestrator's entire delegation surface for exactly those users, silently.
//! It also puts this tool in the same family as its sibling
//! `delegate_to_integrations_agent`.
//!
//! [`DelegateTool`]: crate::agent::tools::DelegateTool
//! [`ArchetypeDelegationTool`]: super::ArchetypeDelegationTool
//! [`SkillDelegationTool`]: super::SkillDelegationTool
//! [`ToolExposure::Hidden`]: tinytools::ToolExposure::Hidden

use async_trait::async_trait;
Expand Down Expand Up @@ -102,11 +97,7 @@ impl CollapsedDelegationTool {
/// Build the collapsed tool, or `None` when there is nothing to route to.
///
/// `None` rather than an empty enum: a `delegate` tool whose `agent` has no
/// valid value is a schema the model can only call wrongly, and the
/// sibling [`SkillDelegationTool::for_connected`] already returns `None` on
/// an empty toolkit list for the same reason.
///
/// [`SkillDelegationTool::for_connected`]: super::SkillDelegationTool::for_connected
/// valid value is a schema the model can only call wrongly.
pub fn for_targets(targets: Vec<DelegateTarget>) -> Option<Self> {
if targets.is_empty() {
return None;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn tool() -> CollapsedDelegationTool {
#[test]
fn an_empty_target_list_produces_no_tool() {
// An `agent` enum with no valid value is a schema the model can only call
// wrongly. Mirrors `SkillDelegationTool::for_connected`.
// wrongly.
assert!(CollapsedDelegationTool::for_targets(Vec::new()).is_none());
}

Expand Down
Loading
Loading