Skip to content

Every Bot is an orchestrator, with subagents that inherit all its permissions - #956

Merged
davidmckayv merged 4 commits into
mainfrom
david/subagents
Nov 3, 2026
Merged

davidmckayv merged 4 commits into
mainfrom
david/subagents

Conversation

@davidmckayv

@davidmckayv davidmckayv commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Every Bot is now an orchestrator. It hands work that needs many tool calls or a lot of reading to subagents. Each subagent inherits all of the Bot's permissions, and only its answer comes back, so the Bot's own context stays clean.

run_subagent(task, answer) is offered on every run of every Bot: built-in Bots, background turns, and any AG-UI agent someone brings. It runs a fresh turn of the same Bot, as the same person and initiator, on its own new thread. The subagent has:

  • the Bot's granted tools and its computer;
  • its approval rules and its standing instructions;
  • no component tools;
  • an empty context holding only the task.

The parent gets back a compact result: the answer, plus the subagent's thread id and step count for the card.

For remote AG-UI agents the tool runs on OpenBot through HeadlessToolsMiddleware, the same path OpenBot already uses to run tools for any agent, so no harness changes. That middleware now starts calls marked concurrent together, so a remote Bot's subagents run in parallel; everything else still runs in order.

Limits:

  • Up to 5 subagents per answer.
  • A subagent can't start subagents or reach other Bots or people.
  • Check-ins aren't offered the tool, so they stay read-only. In a task started from a check-in, the tool refuses, so every step is still asked in the conversation.

Approvals stay in the conversation. A subagent never waits on the person. When it reaches an action that needs a yes, it stops, withdraws the approval request it opened so nobody is asked twice, and hands the action back. The main Bot then asks through the normal approval. The parent's abort and the turn timeout stop it too.

For concurrent native tool calls, hand-back waits for the calls already started to settle before withdrawing their approval requests. Parent cancellation, direct Stop, and the child deadline can interrupt that wait.

Default. A new standing instruction (ORCHESTRATION_GUIDANCE in shared/bot-prompt.ts) reaches built-in prompts and remote standing roles, in chat and background turns. It tells the Bot to hand anything needing more than two or three tool calls to a subagent, with a self-contained task and the answer wanted.

Chat. Each call is a "Subtask" card, live from its first step:

  • the task;
  • its steps as they happen, read through the existing thread-history route;
  • then the answer;
  • expandable, and a hand-back is shown plainly.

Tests:

  • subagent.integration.test.ts runs on real Postgres with a real MCP server and a real AG-UI endpoint; the repo's scripted model is the only fake. It covers:
    • same Bot, person and tools;
    • empty starting context;
    • only the answer returned;
    • parallel runs and the cap;
    • no nesting;
    • hand-back with withdrawal;
    • abort;
    • a remote AG-UI agent.
  • approvals.test.ts reproduces two concurrent approval requests with the second commit held, verifies both are withdrawn before hand-back, and checks parent cancellation, direct Stop, and the child deadline while that commit remains held.
  • copilot.test.ts covers the instruction.
  • App tests render the card from recorded shapes.
  • Validation for the approval-race fix: affected suites pass, including the final 25 approval tests; formatting, lint, workspace typechecks, the existing test-type baseline check, and the full root build pass.

Live check with the real model in headless Chrome:

  • Four account lookups ran as four parallel subtasks, each card showing live steps, then the answer.
  • A subtask that reached a step needing approval handed it back, and the main Bot asked in the conversation.
Every Bot, built in or a remote AG-UI agent, is offered run_subagent on every run but a
subagent's own and a check-in. OpenBot runs it: a fresh headless turn of the same Bot, as the same
person and initiator, one level deeper, in its own thread derived from the conversation and the
call. It holds the Bot's granted tools, its computer, its approval rules and standing instructions,
and no components, subagents, message_bot or ask_person. Only its answer, its thread and its step
count go back. Up to five run at once for one answer; the sixth is refused. For a remote agent it
goes through the headless middleware, which now starts calls marked concurrent together.

A subagent never waits on the person: an action that needs a yes stops it, the request it opened
is withdrawn without resuming its thread, and the Bot is told exactly what it wanted to do, so it
asks in the conversation. The parent's abort and the turn timeout stop it.

Every Bot's prompt and remote standing role now tells it to work as an orchestrator. The check-in
prompt for a remote Bot strips the standing guidance it repeats again, which the component rule
had broken.

In the chat, each call is a subtask card: the task, its steps as they happen (read from its thread
through the existing history route), then its answer with the steps folded away, or the action it
handed back.
@davidmckayv
davidmckayv merged commit 947955a into main Nov 3, 2026
19 checks passed
@davidmckayv
davidmckayv deleted the david/subagents branch November 3, 2026 02:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant