Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
8 changes: 3 additions & 5 deletions src/openhuman/agent/agents/orchestrator/prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,15 @@ Follow this sequence for every user message:
- Yes: use direct tools first (`current_time`, `cron_*`, `memory_*`, `composio_list_connections`, etc.).
- No: continue.
3. **Does this need specialised execution?**
- If external SaaS integration work is required, use `delegate_{toolkit}` (e.g. `delegate_gmail`, `delegate_notion`).
- If external SaaS integration work is required, use `delegate_to_integrations_agent` with `toolkit` set to the relevant slug (see the **Connected Integrations** section for the current list).
- If code writing/execution/debugging is required, use `delegate_run_code`.
- If web/doc crawling is required, use `delegate_researcher`.
- If complex multi-step decomposition is required, use `delegate_plan`.
- If code review is requested, use `delegate_critic`.
- If memory archiving or distillation is required, use `delegate_archivist`.
4. **After delegation**, summarise results clearly and concisely.

Default bias: **do not spawn a sub-agent when a direct response or direct tool call is sufficient**.

When delegating: use `delegate_researcher` for web/doc lookups, `delegate_run_code` for coding, `delegate_plan` for complex decomposition, `delegate_critic` for reviews, `delegate_archivist` for memory writes, `delegate_{toolkit}` for external integrations. Use `spawn_worker_thread` for long tasks that need their own thread.
Default bias: **do not spawn a sub-agent when a direct response or direct tool call is sufficient**. Use `spawn_worker_thread` for long tasks that need their own thread.

## Rules

Expand Down Expand Up @@ -59,7 +57,7 @@ multi-file refactors, or batch integration work. It creates a persisted **worker
thread the user can open from the thread list, and returns a compact `[worker_thread_ref]`
(thread id + brief summary) to the parent instead of the full transcript.

For routine delegation use the matching `delegate_*` tool and surface the result inline.
For routine delegation use the matching specialist `delegate_*` tool (or `delegate_to_integrations_agent` for external services) and surface the result inline.

Worker threads are one level deep by design: a sub-agent spawned via `spawn_worker_thread`
cannot itself call `spawn_worker_thread`, so workers never nest.
Expand Down
23 changes: 14 additions & 9 deletions src/openhuman/agent/agents/orchestrator/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,16 @@ fn render_delegation_guide(integrations: &[ConnectedIntegration]) -> String {
}
let mut out = String::from(
"## Connected Integrations\n\n\
Delegate tasks for these services using the matching `delegate_{toolkit}` tool:\n\n",
Delegate tasks for these services with `delegate_to_integrations_agent`, passing the toolkit slug as `toolkit`:\n\n",
);
for ci in connected {
// Use the same slug canonicalisation as `collect_orchestrator_tools`
// so the tool name in the prompt always matches the synthesised tool.
// so the `toolkit` arg the orchestrator emits always matches the
// enum the synthesised tool accepts.
let slug = sanitise_slug(&ci.toolkit);
let _ = writeln!(
out,
"- **{}** (delegate via `delegate_{}`): {}",
"- **{}** (`toolkit: \"{}\"`): {}",
ci.toolkit, slug, ci.description
);
}
Expand Down Expand Up @@ -161,7 +162,7 @@ mod tests {
}

#[test]
fn build_emits_delegation_guide_with_spawn_snippet() {
fn build_emits_delegation_guide_with_collapsed_tool() {
let integrations = vec![ConnectedIntegration {
toolkit: "gmail".into(),
description: "Email access.".into(),
Expand All @@ -170,15 +171,18 @@ mod tests {
}];
let body = build(&ctx_with(&integrations)).unwrap();
assert!(body.contains("## Connected Integrations"));
assert!(body.contains("delegate_gmail"));
assert!(body.contains("delegate_to_integrations_agent"));
assert!(body.contains("toolkit: \"gmail\""));
// Must NOT contain the old per-toolkit fan-out tool names.
assert!(!body.contains("delegate_gmail"));
// Must NOT contain the old verbose spawn_subagent snippet.
assert!(!body.contains("spawn_subagent(agent_id=\"integrations_agent\""));
// Delegator voice must NOT use the skill-executor wording.
assert!(!body.contains("You have direct access"));
}

#[test]
fn delegation_guide_uses_compact_delegate_format() {
fn delegation_guide_uses_compact_collapsed_format() {
let integrations = vec![ConnectedIntegration {
toolkit: "gmail".into(),
description: "Email access.".into(),
Expand All @@ -187,15 +191,16 @@ mod tests {
}];
let body = build(&ctx_with(&integrations)).unwrap();
assert!(body.contains("## Connected Integrations"));
assert!(body.contains("delegate_gmail"));
// Must NOT contain the old verbose spawn_subagent snippet.
assert!(body.contains("delegate_to_integrations_agent"));
// Old verbose / per-toolkit forms must be gone.
assert!(!body.contains("delegate_gmail"));
assert!(!body.contains("spawn_subagent(agent_id=\"integrations_agent\""));
}

#[test]
fn build_hides_unconnected_integrations() {
// Only connected toolkits make it into the Delegation Guide
// — unconnected entries would just trigger a spawn_subagent
// — unconnected entries would just trigger a downstream
// pre-flight rejection, so keeping them out keeps the prompt
// focused on what the orchestrator can actually delegate.
let integrations = vec![
Expand Down
Loading
Loading