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 {
@@ -346,6 +355,14 @@ export type AgentFleetDeps = SubAgentSandboxDeps & {
346355 * Omit on the primary session — its children are top-level.
347356 */
348357 parentSessionId ?: string ;
358+ /** When set, only these director ids may be spawned. */
359+ spawnAllowlist ?: readonly string [ ] ;
360+ /** When false, maySpawn directors cannot remount fleet verbs. Defaults true. */
361+ allowOrchestrator ?: boolean ;
362+ /** Isolate each spawn in a git worktree branched from dispatcher HEAD. */
363+ useWorktree ?: boolean ;
364+ /** Optional wall-clock budget (ms) forwarded to runSubAgent. */
365+ deadlineMs ?: number ;
349366 settings ?: Settings | ( ( ) => Settings | undefined ) ;
350367 catalog ?: readonly ProviderCatalogEntry [ ] | ( ( ) => readonly ProviderCatalogEntry [ ] ) ;
351368 onEvent ?: ( event : ReactorEmittedEvent ) => void ;
@@ -373,6 +390,7 @@ export function resolveDirectorDispatch(
373390 systemPromptRole : string ;
374391 capabilities : ReturnType < typeof packageToCapabilities > ;
375392 roleDefault : ReturnType < typeof defaultEffortForDirector > ;
393+ pkg : DirectorPackage ;
376394 }
377395 | { ok : false ; error : string } {
378396 if ( agentId !== undefined && agentId . length > 0 ) {
@@ -391,6 +409,7 @@ export function resolveDirectorDispatch(
391409 systemPromptRole : formatDirectorSystemPrompt ( pkg ) ,
392410 capabilities : packageToCapabilities ( pkg ) ,
393411 roleDefault : defaultEffortForDirector ( pkg ) ,
412+ pkg,
394413 } ;
395414 }
396415 if ( intent !== undefined ) {
@@ -403,6 +422,7 @@ export function resolveDirectorDispatch(
403422 systemPromptRole : formatDirectorSystemPrompt ( pkg ) ,
404423 capabilities : packageToCapabilities ( pkg ) ,
405424 roleDefault : defaultEffortForDirector ( pkg ) ,
425+ pkg,
406426 } ;
407427 }
408428 return {
@@ -451,12 +471,34 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
451471
452472 const resolved = resolveDirectorDispatch ( agentId , intent ) ;
453473 if ( ! resolved . ok ) return fleetResult ( call . id , resolved . error ) ;
474+ if ( agentId === "skywalker" || resolved . directorId === "skywalker" ) {
475+ return fleetResult (
476+ call . id ,
477+ "Error: skywalker is the primary session identity, not a spawned worker. Pass spawn_agent(agent=…) for a specialist (builder, explorer, counsel, critic, …)." ,
478+ ) ;
479+ }
480+ if ( deps . spawnAllowlist !== undefined && deps . spawnAllowlist . length > 0 ) {
481+ if ( ! deps . spawnAllowlist . includes ( resolved . directorId ) ) {
482+ return fleetResult (
483+ call . id ,
484+ `Error: spawn of "${ resolved . directorId } " is outside this director's allowlist. Allowed: ${ deps . spawnAllowlist . join ( ", " ) } .` ,
485+ ) ;
486+ }
487+ }
454488
455489 const settings = deps . settings !== undefined ? resolveDep ( deps . settings ) : undefined ;
456490
491+ const orchestrator = resolved . pkg . spawn . maySpawn === true && deps . allowOrchestrator !== false ;
492+ const nestedSpawnAllowlist =
493+ orchestrator &&
494+ resolved . pkg . spawn . allowlist !== undefined &&
495+ resolved . pkg . spawn . allowlist . length > 0
496+ ? resolved . pkg . spawn . allowlist
497+ : undefined ;
498+
457499 let provider : SubAgentProvider = resolveDep ( deps . provider ) ;
458500 const effort = resolveEffortForRole ( {
459- orchestrator : false ,
501+ orchestrator,
460502 roleDefault : resolved . roleDefault ,
461503 ...( provider . reasoningEffort !== undefined
462504 ? { parentEffort : provider . reasoningEffort }
@@ -503,6 +545,52 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
503545 } ;
504546
505547 const catalog = deps . catalog !== undefined ? resolveDep ( deps . catalog ) : undefined ;
548+ let worktreeCwd : string | undefined ;
549+ let worktreeStashBaseline : readonly string [ ] | null = [ ] ;
550+ let worktreeHeadAtCreate : string | undefined ;
551+ if ( deps . useWorktree === true ) {
552+ const worktreePath = join ( deps . getWorkdirBase ( ) , "worktrees" , generateSessionId ( ) ) ;
553+ try {
554+ const worktree = await createSubAgentWorktree ( deps . cwd , worktreePath ) ;
555+ worktreeCwd = worktree . path ;
556+ worktreeStashBaseline = worktree . stashBaseline ;
557+ worktreeHeadAtCreate = worktree . headAtCreate ;
558+ } catch ( err ) {
559+ const message =
560+ err instanceof WorktreeError
561+ ? err . message
562+ : `sub-agent worktree setup failed: ${ err instanceof Error ? err . message : String ( err ) } ` ;
563+ deps . fleetRecords . reject ( session . id , message ) ;
564+ deps . sessions . fail ( session . id , message ) ;
565+ return fleetResult ( call . id , `Error: ${ message } ` ) ;
566+ }
567+ }
568+
569+ const nestedDispatch : NestedDispatchDeps | undefined = orchestrator
570+ ? {
571+ permissionGate : deps . permissionGate ,
572+ ...( deps . inheritMcpTools !== undefined
573+ ? { inheritMcpTools : deps . inheritMcpTools }
574+ : { } ) ,
575+ ...( deps . shellTimeout !== undefined ? { shellTimeout : deps . shellTimeout } : { } ) ,
576+ ...( deps . shellEnv !== undefined ? { shellEnv : deps . shellEnv } : { } ) ,
577+ ...( deps . extraToolPlugins !== undefined
578+ ? { extraToolPlugins : deps . extraToolPlugins }
579+ : { } ) ,
580+ ...( deps . getBlobReader !== undefined ? { getBlobReader : deps . getBlobReader } : { } ) ,
581+ getWorkdirBase : deps . getWorkdirBase ,
582+ provider : deps . provider ,
583+ ...( deps . onEvent !== undefined ? { onEvent : deps . onEvent } : { } ) ,
584+ ...( deps . onProgress !== undefined ? { onProgress : deps . onProgress } : { } ) ,
585+ sessions : deps . sessions ,
586+ ...( settings !== undefined ? { settings } : { } ) ,
587+ ...( catalog !== undefined ? { catalog } : { } ) ,
588+ parentSessionId : session . id ,
589+ ...( deps . useWorktree !== undefined ? { useWorktree : deps . useWorktree } : { } ) ,
590+ ...( nestedSpawnAllowlist !== undefined ? { spawnAllowlist : nestedSpawnAllowlist } : { } ) ,
591+ }
592+ : undefined ;
593+
506594 const params : RunSubAgentParams = {
507595 // Name the trace directory after the session-store id so the
508596 // descendant-scoping check behind read_agent_trace can resolve this
@@ -514,7 +602,7 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
514602 ...( deps . shellEnv !== undefined ? { shellEnv : deps . shellEnv } : { } ) ,
515603 ...( deps . extraToolPlugins !== undefined ? { extraToolPlugins : deps . extraToolPlugins } : { } ) ,
516604 ...( deps . getBlobReader !== undefined ? { getBlobReader : deps . getBlobReader } : { } ) ,
517- cwd : deps . cwd ,
605+ cwd : worktreeCwd ?? deps . cwd ,
518606 workdirBase : deps . getWorkdirBase ( ) ,
519607 provider,
520608 ...( settings !== undefined ? { settings } : { } ) ,
@@ -533,6 +621,18 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
533621 ...( resolved . capabilities !== undefined ? { capabilities : resolved . capabilities } : { } ) ,
534622 systemPromptRole : resolved . systemPromptRole ,
535623 directorId : resolved . directorId ,
624+ ...( orchestrator
625+ ? {
626+ orchestrator : true ,
627+ orchestratorTier : resolved . pkg . tier ,
628+ nestedDispatch : nestedDispatch ! ,
629+ }
630+ : { } ) ,
631+ ...( deps . deadlineMs !== undefined ? { deadlineMs : deps . deadlineMs } : { } ) ,
632+ tier : resolved . pkg . tier ,
633+ ...( resolved . pkg . reportContract ?. outputType !== undefined
634+ ? { reportType : resolved . pkg . reportContract . outputType }
635+ : { } ) ,
536636 // Keep the session open after a clean completion, and hand the
537637 // store a bounded close for close_agent to call later.
538638 persist : true ,
@@ -594,6 +694,15 @@ export function createSpawnAgentTool(deps: AgentFleetDeps): AgentTool {
594694 status : deps . sessions . get ( session . id ) ?. status ?? "completed" ,
595695 duration_ms : Date . now ( ) - startedAt ,
596696 } ) ;
697+ if ( worktreeCwd === undefined ) return ;
698+ void cleanupSubAgentWorktree ( deps . cwd , worktreeCwd , {
699+ stashBaseline : worktreeStashBaseline ,
700+ ...( worktreeHeadAtCreate !== undefined ? { headAtCreate : worktreeHeadAtCreate } : { } ) ,
701+ } ) . catch ( ( err : unknown ) => {
702+ log . error ( "spawn_agent worktree cleanup failed: {error}" , {
703+ error : err instanceof Error ? err . message : String ( err ) ,
704+ } ) ;
705+ } ) ;
597706 } ) ;
598707
599708 return fleetResult ( call . id , JSON . stringify ( { agent_id : session . id , status : "running" } ) ) ;
0 commit comments