3131 *
3232 * Argument shape intentionally mirrors `task()`'s (description/prompt/
3333 * context/goals/intent/success_criteria/do_not/report_focus) so a
34- * caller can swap one for the other. Scope is deliberately narrower than
35- * `task()` for this first cut: only closed-director dispatch (`agent=` a
36- * director id, or `intent=`) is supported — no custom AgentProfile lookup,
37- * no nested orchestration, no re-dispatch ledger. Those remain `task()`-only
38- * for now; nothing here stops adding them later.
34+ * caller can swap one for the other. Closed-director dispatch also carries
35+ * task()'s isolation and spawn-matrix: worktree cwd, parent allowlist,
36+ * maySpawn nestedDispatch, and deadline. Custom AgentProfile lookup and the
37+ * re-dispatch ledger remain task()-only until task becomes a thin wrapper.
3938 *
4039 */
4140
41+ import { join } from "node:path" ;
42+
4243import { tool } from "@intx/agent" ;
4344import type { AgentTool } from "@intx/agent" ;
4445import { type } from "arktype" ;
4546import type { ToolDefinition , ToolResult } from "@intx/types/runtime" ;
4647import type { ReactorEmittedEvent } from "@intx/inference" ;
48+ import { getLogger } from "@intx/log" ;
4749
50+ import { LOG_NAMESPACE_ROOT } from "../branding.js" ;
4851import type { ProviderCatalogEntry } from "../config/index.js" ;
52+ import { generateSessionId } from "../session/index.js" ;
4953import {
5054 isDirectorId ,
5155 packageToCapabilities ,
@@ -61,13 +65,18 @@ import { isCodexProviderName } from "../config/codex-providers.js";
6165import { buildDispatchBrief , type TaskIntent } from "./report.js" ;
6266import type { SubAgentSessionStore } from "./session-store.js" ;
6367import type {
68+ NestedDispatchDeps ,
6469 RunSubAgentParams ,
6570 RunSubAgentResult ,
6671 SubAgentProvider ,
6772 SubAgentSandboxDeps ,
6873} from "./types.js" ;
74+ import { cleanupSubAgentWorktree , createSubAgentWorktree , WorktreeError } from "./worktree.js" ;
6975import { NOOP_TELEMETRY , type Telemetry } from "../telemetry/index.js" ;
7076import { classifyAgentName } from "../telemetry/classify.js" ;
77+ import type { DirectorPackage } from "../agent/directors/types.js" ;
78+
79+ const log = getLogger ( [ LOG_NAMESPACE_ROOT , "subagent" , "agent-fleet" ] ) ;
7180
7281/** Terminal (or running) record for one spawned agent, keyed by agent id. */
7382interface FleetRecord {
@@ -326,6 +335,14 @@ export type AgentFleetDeps = SubAgentSandboxDeps & {
326335 * Omit on the primary session — its children are top-level.
327336 */
328337 parentSessionId ?: string ;
338+ /** When set, only these director ids may be spawned. */
339+ spawnAllowlist ?: readonly string [ ] ;
340+ /** When false, maySpawn directors cannot remount fleet verbs. Defaults true. */
341+ allowOrchestrator ?: boolean ;
342+ /** Isolate each spawn in a git worktree branched from dispatcher HEAD. */
343+ useWorktree ?: boolean ;
344+ /** Optional wall-clock budget (ms) forwarded to runSubAgent. */
345+ deadlineMs ?: number ;
329346 settings ?: Settings | ( ( ) => Settings | undefined ) ;
330347 catalog ?: readonly ProviderCatalogEntry [ ] | ( ( ) => readonly ProviderCatalogEntry [ ] ) ;
331348 onEvent ?: ( event : ReactorEmittedEvent ) => void ;
@@ -353,6 +370,7 @@ export function resolveDirectorDispatch(
353370 systemPromptRole : string ;
354371 capabilities : ReturnType < typeof packageToCapabilities > ;
355372 roleDefault : ReturnType < typeof defaultEffortForDirector > ;
373+ pkg : DirectorPackage ;
356374 }
357375 | { ok : false ; error : string } {
358376 if ( agentId !== undefined && agentId . length > 0 ) {
@@ -371,6 +389,7 @@ export function resolveDirectorDispatch(
371389 systemPromptRole : formatDirectorSystemPrompt ( pkg ) ,
372390 capabilities : packageToCapabilities ( pkg ) ,
373391 roleDefault : defaultEffortForDirector ( pkg ) ,
392+ pkg,
374393 } ;
375394 }
376395 if ( intent !== undefined ) {
@@ -383,6 +402,7 @@ export function resolveDirectorDispatch(
383402 systemPromptRole : formatDirectorSystemPrompt ( pkg ) ,
384403 capabilities : packageToCapabilities ( pkg ) ,
385404 roleDefault : defaultEffortForDirector ( pkg ) ,
405+ pkg,
386406 } ;
387407 }
388408 return {
@@ -431,12 +451,34 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
431451
432452 const resolved = resolveDirectorDispatch ( agentId , intent ) ;
433453 if ( ! resolved . ok ) return fleetResult ( call . id , resolved . error ) ;
454+ if ( agentId === "skywalker" || resolved . directorId === "skywalker" ) {
455+ return fleetResult (
456+ call . id ,
457+ "Error: skywalker is the primary session identity, not a spawned worker. Pass spawn_agent(agent=…) for a specialist (builder, explorer, counsel, critic, …)." ,
458+ ) ;
459+ }
460+ if ( deps . spawnAllowlist !== undefined && deps . spawnAllowlist . length > 0 ) {
461+ if ( ! deps . spawnAllowlist . includes ( resolved . directorId ) ) {
462+ return fleetResult (
463+ call . id ,
464+ `Error: spawn of "${ resolved . directorId } " is outside this director's allowlist. Allowed: ${ deps . spawnAllowlist . join ( ", " ) } .` ,
465+ ) ;
466+ }
467+ }
434468
435469 const settings = deps . settings !== undefined ? resolveDep ( deps . settings ) : undefined ;
436470
471+ const orchestrator = resolved . pkg . spawn . maySpawn === true && deps . allowOrchestrator !== false ;
472+ const nestedSpawnAllowlist =
473+ orchestrator &&
474+ resolved . pkg . spawn . allowlist !== undefined &&
475+ resolved . pkg . spawn . allowlist . length > 0
476+ ? resolved . pkg . spawn . allowlist
477+ : undefined ;
478+
437479 let provider : SubAgentProvider = resolveDep ( deps . provider ) ;
438480 const effort = resolveEffortForRole ( {
439- orchestrator : false ,
481+ orchestrator,
440482 roleDefault : resolved . roleDefault ,
441483 ...( provider . reasoningEffort !== undefined
442484 ? { parentEffort : provider . reasoningEffort }
@@ -483,6 +525,52 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
483525 } ;
484526
485527 const catalog = deps . catalog !== undefined ? resolveDep ( deps . catalog ) : undefined ;
528+ let worktreeCwd : string | undefined ;
529+ let worktreeStashBaseline : readonly string [ ] | null = [ ] ;
530+ let worktreeHeadAtCreate : string | undefined ;
531+ if ( deps . useWorktree === true ) {
532+ const worktreePath = join ( deps . getWorkdirBase ( ) , "worktrees" , generateSessionId ( ) ) ;
533+ try {
534+ const worktree = await createSubAgentWorktree ( deps . cwd , worktreePath ) ;
535+ worktreeCwd = worktree . path ;
536+ worktreeStashBaseline = worktree . stashBaseline ;
537+ worktreeHeadAtCreate = worktree . headAtCreate ;
538+ } catch ( err ) {
539+ const message =
540+ err instanceof WorktreeError
541+ ? err . message
542+ : `sub-agent worktree setup failed: ${ err instanceof Error ? err . message : String ( err ) } ` ;
543+ deps . fleetRecords . reject ( session . id , message ) ;
544+ deps . sessions . fail ( session . id , message ) ;
545+ return fleetResult ( call . id , `Error: ${ message } ` ) ;
546+ }
547+ }
548+
549+ const nestedDispatch : NestedDispatchDeps | undefined = orchestrator
550+ ? {
551+ permissionGate : deps . permissionGate ,
552+ ...( deps . inheritMcpTools !== undefined
553+ ? { inheritMcpTools : deps . inheritMcpTools }
554+ : { } ) ,
555+ ...( deps . shellTimeout !== undefined ? { shellTimeout : deps . shellTimeout } : { } ) ,
556+ ...( deps . shellEnv !== undefined ? { shellEnv : deps . shellEnv } : { } ) ,
557+ ...( deps . extraToolPlugins !== undefined
558+ ? { extraToolPlugins : deps . extraToolPlugins }
559+ : { } ) ,
560+ ...( deps . getBlobReader !== undefined ? { getBlobReader : deps . getBlobReader } : { } ) ,
561+ getWorkdirBase : deps . getWorkdirBase ,
562+ provider : deps . provider ,
563+ ...( deps . onEvent !== undefined ? { onEvent : deps . onEvent } : { } ) ,
564+ ...( deps . onProgress !== undefined ? { onProgress : deps . onProgress } : { } ) ,
565+ sessions : deps . sessions ,
566+ ...( settings !== undefined ? { settings } : { } ) ,
567+ ...( catalog !== undefined ? { catalog } : { } ) ,
568+ parentSessionId : session . id ,
569+ ...( deps . useWorktree !== undefined ? { useWorktree : deps . useWorktree } : { } ) ,
570+ ...( nestedSpawnAllowlist !== undefined ? { spawnAllowlist : nestedSpawnAllowlist } : { } ) ,
571+ }
572+ : undefined ;
573+
486574 const params : RunSubAgentParams = {
487575 // Name the trace directory after the session-store id so the
488576 // descendant-scoping check behind read_agent_trace can resolve this
@@ -494,7 +582,7 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
494582 ...( deps . shellEnv !== undefined ? { shellEnv : deps . shellEnv } : { } ) ,
495583 ...( deps . extraToolPlugins !== undefined ? { extraToolPlugins : deps . extraToolPlugins } : { } ) ,
496584 ...( deps . getBlobReader !== undefined ? { getBlobReader : deps . getBlobReader } : { } ) ,
497- cwd : deps . cwd ,
585+ cwd : worktreeCwd ?? deps . cwd ,
498586 workdirBase : deps . getWorkdirBase ( ) ,
499587 provider,
500588 ...( settings !== undefined ? { settings } : { } ) ,
@@ -513,6 +601,18 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
513601 ...( resolved . capabilities !== undefined ? { capabilities : resolved . capabilities } : { } ) ,
514602 systemPromptRole : resolved . systemPromptRole ,
515603 directorId : resolved . directorId ,
604+ ...( orchestrator
605+ ? {
606+ orchestrator : true ,
607+ orchestratorTier : resolved . pkg . tier ,
608+ nestedDispatch : nestedDispatch ! ,
609+ }
610+ : { } ) ,
611+ ...( deps . deadlineMs !== undefined ? { deadlineMs : deps . deadlineMs } : { } ) ,
612+ tier : resolved . pkg . tier ,
613+ ...( resolved . pkg . reportContract ?. outputType !== undefined
614+ ? { reportType : resolved . pkg . reportContract . outputType }
615+ : { } ) ,
516616 // Keep the session open after a clean completion, and hand the
517617 // store a bounded close for close_agent to call later.
518618 persist : true ,
@@ -573,6 +673,15 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
573673 status : deps . sessions . get ( session . id ) ?. status ?? "completed" ,
574674 duration_ms : Date . now ( ) - startedAt ,
575675 } ) ;
676+ if ( worktreeCwd === undefined ) return ;
677+ void cleanupSubAgentWorktree ( deps . cwd , worktreeCwd , {
678+ stashBaseline : worktreeStashBaseline ,
679+ ...( worktreeHeadAtCreate !== undefined ? { headAtCreate : worktreeHeadAtCreate } : { } ) ,
680+ } ) . catch ( ( err : unknown ) => {
681+ log . error ( "spawn_agent worktree cleanup failed: {error}" , {
682+ error : err instanceof Error ? err . message : String ( err ) ,
683+ } ) ;
684+ } ) ;
576685 } ) ;
577686
578687 return fleetResult ( call . id , JSON . stringify ( { agent_id : session . id , status : "running" } ) ) ;
0 commit comments