feat(x): teach copilot when to spawn sub-agents; fence shared surfaces

The spawn-agent tool shipped with mechanics but no judgment layer —
the only guidance was the tool description. This adds the ambient
decision heuristics and closes a child-profile hole.

- Copilot system prompt gains a "Sub-Agents (parallel & heavy work)"
  section (same strong/anti signal structure as Background Tasks):
  the organizing principle is context hygiene — a sub-agent's reads
  and fetches never enter the main conversation, only its distilled
  answer. Research-shaped requests (catch-me-up, dig-into, meeting
  prep) now route through sub-agents by default, with copilot as
  synthesizer. Anti-signals: single quick lookups, journeys-as-answer,
  anything needing mid-task user input, driving shared surfaces.
- Background-task agent gets a matching (shorter) note — children
  have their own model-call budgets, so a bg-task covers more ground
  per run by fanning out.
- The inline default child profile now also excludes app-navigation
  and browser-control: both drive shared visible surfaces (the UI the
  user is watching; the single embedded browser pane), which headless
  or parallel children would corrupt, not just clutter. Still
  available via explicit tools selection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ramnique Singh 2026-07-09 06:20:38 +05:30
parent 3822bda1f7
commit f546d1ad59
4 changed files with 37 additions and 1 deletions

View file

@ -149,6 +149,12 @@ ${codeModeEnabled
*Medium signals (load the skill, answer the one-off, then offer):* one-off questions about decaying info ("what's the weather?", "top HN stories?"), "what's the latest on X / catch me up on X / any updates on X" about a person, company, project, or topic, recurring artifacts ("morning briefing", "weekly review", "Acme deal dashboard"). **Heuristic:** if you reach for \`web-search\` or a news tool to answer a recurring question, the answer is the kind of thing a bg-task would refresh on a schedule.
**Sub-Agents (parallel & heavy work):** The \`spawn-agent\` tool runs a sub-agent in its own isolated, headless thread and returns only its final answer — the sub-agent's tool calls, page fetches, and file reads never enter this conversation. Use it to keep this context clean: a sub-agent can read twenty notes or six web pages and hand you back one paragraph. Issue several spawn-agent calls in ONE response to run them in parallel.
*Strong signals (spawn without asking):* the request decomposes into independent lookups ("prep me on these 3 attendees", "compare these vendors") one sub-agent each; the task needs reading MANY files, notes, pages, or a long document but the user wants a summary ("what do we know about Acme", "summarize this 40-page PDF"); open-ended web research where you don't know the sources upfront. **Research-shaped requests ("catch me up on X", "dig into Y", meeting prep) should route through sub-agents by default** you act as the synthesizer, weaving their findings together with what you know from memory.
*Do NOT spawn for:* single quick lookups (one file read, one search just do it); tasks where the user wants to see the intermediate detail, not a distillation; anything needing user input mid-way (sub-agents run headless and cannot ask questions); driving the app UI or the embedded browser (those are shared surfaces you control, not sub-agents). Remember each sub-agent starts with ZERO context its \`task\` must be fully self-contained (names, dates, constraints, expected output format).
**Rowboat Apps:** When users ask you to build/make/create an *app* or *dashboard* ("build me an app that…", "make a dashboard for…"), load the \`apps\` skill FIRST — it defines the app contract (manifest, dist/, Host API) and the build flow. For ambiguous requests that could be a one-off answer ("show me my open PRs"), the skill's intent gate says to confirm before building. Do not hand-roll app folders without the skill.
**Live Notes:** If the user explicitly says "live note" or "live-note", load the \`live-note\` skill. Otherwise, do not propose live notes — prefer the \`background-task\` skill for anything recurring.

View file

@ -53,6 +53,10 @@ Only available when the run message contains a **"# Coding task"** block (the ta
- Group related items, then call \`launch-code-task\` once per group (\`taskSlug\` is your own slug). It runs full-auto in an isolated worktree and **owns the \`## Code Sessions\` section of \`index.md\`** — never edit those rows yourself. Write a complete, self-contained \`prompt\`: the coding agent has no other context and no human to ask.
- If nothing is actionable, launch nothing and say so in your summary.
# Sub-agents
The \`spawn-agent\` tool runs a sub-agent in its own isolated turn and returns only its final answer — its intermediate reads and fetches never enter your context, and it has its own model-call budget separate from yours. Spawn when your instructions require sweeping many sources (several sites, many notes, a long document) and you only need the conclusions, or when the work splits into independent lookups — issue several spawn-agent calls in ONE message to run them in parallel, then synthesize. Do not spawn for single quick lookups. Each sub-agent starts with zero context: its \`task\` must be fully self-contained.
# Triggers
The run message tells you which trigger fired and how to interpret it:

View file

@ -30,6 +30,16 @@ const fakeBuiltins = {
inputSchema: z.object({ command: z.string() }),
execute: async () => null,
},
"app-navigation": {
description: "Drive the app UI",
inputSchema: z.object({}),
execute: async () => null,
},
"browser-control": {
description: "Drive the embedded browser",
inputSchema: z.object({}),
execute: async () => null,
},
} as unknown as typeof BuiltinTools;
function makeResolver() {
@ -97,5 +107,16 @@ describe("InlineAgentResolver", () => {
// Excluded by policy, not by absence:
expect(names).not.toContain("executeCommand");
expect(names).not.toContain("spawn-agent");
// Shared visible surfaces: a headless child must not drive the UI
// the user is watching or the single embedded browser pane.
expect(names).not.toContain("app-navigation");
expect(names).not.toContain("browser-control");
});
it("shared-surface tools remain available via explicit selection", async () => {
const resolved = await makeResolver().resolve({
inline: { name: "a", instructions: "x", tools: ["browser-control"] },
});
expect(resolved.tools.map((t) => t.name)).toEqual(["browser-control"]);
});
});

View file

@ -15,7 +15,10 @@ import { builtinToolDescriptor } from "./builtin-descriptors.js";
// except the ones that make no sense headlessly or in a child. Mirrors the
// background-task agent's exclusions (no interactive approval surface) plus
// the task/session launchers — an ephemeral child should do its own work,
// not schedule more.
// not schedule more — and the shared visible surfaces: a headless child
// navigating the UI the user is looking at, or parallel children fighting
// over the one embedded browser pane, is broken behavior, not just noise.
// All remain available via an explicit `tools` selection.
const DEFAULT_PROFILE_EXCLUDED = new Set([
"executeCommand", // headless: no interactive approval
"code_agent_run", // headless: needs interactive permission UI
@ -24,6 +27,8 @@ const DEFAULT_PROFILE_EXCLUDED = new Set([
"create-background-task",
"patch-background-task",
"run-live-note-agent",
"app-navigation", // shared surface: drives the UI the user is watching
"browser-control", // shared surface: the single embedded browser pane
SPAWN_AGENT_TOOL_NAME,
]);