Skip to content

feat: add sub-agent spawning via delegate_task tool#451

Open
shivammittal274 wants to merge 3 commits intomainfrom
feat/sub-agent-spawning
Open

feat: add sub-agent spawning via delegate_task tool#451
shivammittal274 wants to merge 3 commits intomainfrom
feat/sub-agent-spawning

Conversation

@shivammittal274
Copy link
Contributor

@shivammittal274 shivammittal274 commented Mar 10, 2026

Summary

  • Adds delegate_task tool that lets the parent agent spawn sub-agents with isolated context windows
  • Sub-agent is an exact replica of the parent — same model, instructions, tools, and compaction — just with a fresh context and 15-step limit
  • Follows the AI SDK subagent pattern (agent-as-tool via ToolLoopAgent.generate())
  • Skipped in chat mode, prevents recursive spawning by filtering delegate_task from sub-agent tools

Changes

File What
apps/server/src/agent/sub-agent.ts New — factory function creating the delegate_task tool
apps/server/src/agent/ai-sdk-agent.ts Wire delegate_task into parent toolset
apps/server/src/agent/prompt.ts Add sub-agents prompt section guiding delegation
packages/shared/src/constants/limits.ts Add SUB_AGENT_MAX_TURNS: 15

Test plan

  • End-to-end test: parent agent delegates browser research task → sub-agent uses browser tools → returns summary
  • Verify chat mode excludes delegate_task
  • Verify sub-agent cannot recursively spawn sub-agents
  • Typecheck passes (bun run typecheck)

Add a `delegate_task` tool that spawns ephemeral sub-agents with isolated
context windows. The sub-agent is an exact replica of the parent — same
model, instructions, tools, and compaction — following the AI SDK
agent-as-tool pattern (ai-sdk.dev/docs/agents/subagents).

- New `sub-agent.ts` with `createDelegateTaskTool` factory
- Sub-agent reuses parent's model, full prompt (soul, memory, skills,
  user prefs, workspace, external integrations), and all tools
- 15-step limit (SUB_AGENT_MAX_TURNS), own compaction, no recursion
- Skipped in chat mode
- New prompt section guiding delegation behavior
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR introduces sub-agent delegation to BrowserOS: a new delegate_task tool lets the parent agent spin up an isolated sub-agent (same model, instructions, and tools; fresh context; 15-step cap) for expensive subtasks like multi-page research or deep filesystem exploration. The implementation follows the AI SDK agent-as-tool pattern and is correctly gated behind non-chat mode.

Key findings:

  • Missing error handlingsubAgent.generate() has no try/catch; an uncaught exception will surface as a raw tool-execution failure in the parent agent's step loop rather than returning a graceful error description.
  • Sub-agent instance reuseToolLoopAgent is constructed once and reused across all delegate_task invocations. If the AI SDK's stepCountIs stop condition tracks steps cumulatively per instance (rather than per generate() call), sequential delegations will each get fewer allowed steps than the intended 15.
  • Empty result.text unhandled — when a sub-agent hits its step limit without emitting a final text response, the parent agent receives an empty string tool result.
  • Prompt ambiguity — the existing strict rule "do not delegate routine actions" may discourage the LLM from using delegate_task at all; the rule's intent (don't push work back to the user) should be clarified.

Confidence Score: 2/5

  • Not safe to merge — the execute path has no error handling and the sub-agent reuse pattern may produce incorrect step limits on repeated delegations.
  • Two logic-level issues in the core sub-agent.ts file need to be resolved before this is production-ready: missing try/catch around subAgent.generate() and the risk of cumulative step-counter state when reusing a single ToolLoopAgent instance. The remaining issues (empty text guard, prompt wording) are lower severity but should also be addressed.
  • apps/server/src/agent/sub-agent.ts requires the most attention — all critical issues are concentrated there.

Important Files Changed

Filename Overview
apps/server/src/agent/sub-agent.ts New file implementing the delegate_task tool. Two logic issues: no try/catch around subAgent.generate() means errors crash the parent agent's tool loop; the ToolLoopAgent is instantiated once and reused across invocations, risking cumulative step-counter state leaking between calls. Also missing a guard for empty result.text.
apps/server/src/agent/ai-sdk-agent.ts Wires delegate_task into the parent toolset only in non-chat mode. parentTools: tools is passed before delegate_task is assigned, but the destructuring inside createDelegateTaskTool correctly snapshots the tool set at call time — recursive spawning prevention works as intended. Clean integration.
apps/server/src/agent/prompt.ts Adds the <sub_agents> prompt section with clear delegation guidance. Slight semantic tension with the existing strict rule "do not delegate routine actions," which could discourage the LLM from using delegate_task.
packages/shared/src/constants/limits.ts Adds SUB_AGENT_MAX_TURNS: 15 to AGENT_LIMITS. Straightforward, well-placed constant.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant PA as Parent Agent (AiSdkAgent)
    participant DT as delegate_task tool
    participant SA as Sub-Agent (ToolLoopAgent)
    participant T as Tools (browser/fs/mcp/memory)

    U->>PA: Task prompt
    PA->>PA: Decides to delegate subtask
    PA->>DT: call delegate_task({ task })
    DT->>SA: subAgent.generate({ prompt: task, abortSignal })
    loop Up to SUB_AGENT_MAX_TURNS (15)
        SA->>T: Tool call
        T-->>SA: Tool result
    end
    SA-->>DT: result.text (summary)
    DT-->>PA: text summary as tool result
    PA->>U: Final response incorporating summary
Loading

Last reviewed commit: ff8acb0

Comment on lines +40 to +47
// Create the sub-agent once — reused across invocations
const subAgent = new ToolLoopAgent({
model: deps.model,
instructions,
tools: subAgentTools,
stopWhen: [stepCountIs(AGENT_LIMITS.SUB_AGENT_MAX_TURNS)],
prepareStep,
})
Copy link
Contributor

Choose a reason for hiding this comment

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

Sub-agent instance reused across invocations

The ToolLoopAgent is constructed once at factory-creation time and then reused for every delegate_task call. If ToolLoopAgent maintains any per-instance state — particularly if stepCountIs tracks steps cumulatively across generate() calls rather than resetting per call — the second delegation will inherit leftover step count and may terminate sooner than the intended 15-step limit.

It would be safer to construct the ToolLoopAgent inside execute so each invocation starts from a guaranteed clean state:

execute: async ({ task }, { abortSignal }) => {
  const subAgent = new ToolLoopAgent({
    model: deps.model,
    instructions,
    tools: subAgentTools,
    stopWhen: [stepCountIs(AGENT_LIMITS.SUB_AGENT_MAX_TURNS)],
    prepareStep,
  })
  // ...
}

If the AI SDK guarantees that generate() always resets the step counter, add a brief comment referencing the SDK docs so future readers don't have to investigate this.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/server/src/agent/sub-agent.ts
Line: 40-47

Comment:
**Sub-agent instance reused across invocations**

The `ToolLoopAgent` is constructed once at factory-creation time and then reused for every `delegate_task` call. If `ToolLoopAgent` maintains any per-instance state — particularly if `stepCountIs` tracks steps cumulatively across `generate()` calls rather than resetting per call — the second delegation will inherit leftover step count and may terminate sooner than the intended 15-step limit.

It would be safer to construct the `ToolLoopAgent` inside `execute` so each invocation starts from a guaranteed clean state:

```typescript
execute: async ({ task }, { abortSignal }) => {
  const subAgent = new ToolLoopAgent({
    model: deps.model,
    instructions,
    tools: subAgentTools,
    stopWhen: [stepCountIs(AGENT_LIMITS.SUB_AGENT_MAX_TURNS)],
    prepareStep,
  })
  // ...
}
```

If the AI SDK guarantees that `generate()` always resets the step counter, add a brief comment referencing the SDK docs so future readers don't have to investigate this.

How can I resolve this? If you propose a fix, please make it concise.

shivammittal274 and others added 2 commits March 10, 2026 21:58
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant