@@ -4,27 +4,115 @@ import z from "zod"
44import { Session } from "../session"
55import { SessionID , MessageID } from "../session/schema"
66import { MessageV2 } from "../session/message-v2"
7- import { Identifier } from "../id/id"
87import { Agent } from "../agent/agent"
98import { SessionPrompt } from "../session/prompt"
109import { iife } from "@/util/iife"
1110import { defer } from "@/util/defer"
1211import { Config } from "../config/config"
1312import { Permission } from "@/permission"
13+ import { Instance } from "@/project/instance"
14+ import { ModelID , ProviderID } from "@/provider/schema"
15+
16+ const dynamicAgentConfig = z
17+ . object ( {
18+ prompt : z . string ( ) . optional ( ) ,
19+ temperature : z . number ( ) . optional ( ) ,
20+ top_p : z . number ( ) . optional ( ) ,
21+ color : z . string ( ) . optional ( ) ,
22+ steps : z . number ( ) . int ( ) . positive ( ) . optional ( ) ,
23+ permission : z . record ( z . string ( ) , z . any ( ) ) . optional ( ) ,
24+ options : z . record ( z . string ( ) , z . any ( ) ) . optional ( ) ,
25+ } )
26+ . strict ( )
1427
1528const parameters = z . object ( {
1629 description : z . string ( ) . describe ( "A short (3-5 words) description of the task" ) ,
1730 prompt : z . string ( ) . describe ( "The task for the agent to perform" ) ,
1831 subagent_type : z . string ( ) . describe ( "The type of specialized agent to use for this task" ) ,
32+ subagent_description : z
33+ . string ( )
34+ . describe ( "Optional specialization for an ad hoc dynamic subagent" )
35+ . optional ( ) ,
1936 task_id : z
2037 . string ( )
2138 . describe (
2239 "This should only be set if you mean to resume a previous task (you can pass a prior task_id and the task will continue the same subagent session as before instead of creating a fresh one)" ,
2340 )
2441 . optional ( ) ,
2542 command : z . string ( ) . describe ( "The command that triggered this task" ) . optional ( ) ,
43+ model : z . string ( ) . describe ( 'Optional model override in the format "provider/model"' ) . optional ( ) ,
44+ variant : z . string ( ) . describe ( "Optional reasoning or thinking level override" ) . optional ( ) ,
45+ agent_config : dynamicAgentConfig . describe ( "Internal dynamic task agent configuration" ) . optional ( ) ,
2646} )
2747
48+ function parseModel ( model : string ) {
49+ const separator = model . indexOf ( "/" )
50+ if ( separator <= 0 || separator === model . length - 1 ) {
51+ throw new Error ( `Invalid model "${ model } ". Expected "provider/model".` )
52+ }
53+
54+ return {
55+ providerID : ProviderID . make ( model . slice ( 0 , separator ) ) ,
56+ modelID : ModelID . make ( model . slice ( separator + 1 ) ) ,
57+ }
58+ }
59+
60+ function buildDynamicAgentPrompt ( input : {
61+ name : string
62+ description : string
63+ workingDirectory : string
64+ projectRoot : string
65+ prompt ?: string
66+ } ) {
67+ return [
68+ ...( input . prompt ? [ input . prompt , "" ] : [ ] ) ,
69+ `You are @${ input . name } , a dynamic subagent.` ,
70+ `Specialization: ${ input . description } ` ,
71+ "" ,
72+ `Current working directory: ${ input . workingDirectory } ` ,
73+ ...( input . projectRoot !== input . workingDirectory ? [ `Project root: ${ input . projectRoot } ` ] : [ ] ) ,
74+ "" ,
75+ "Treat the specialization as authoritative for this run." ,
76+ "Resolve relative paths from the current working directory shown above." ,
77+ "Do not invent absolute filesystem paths. If the task gives a relative project path, use that exact relative path unless you verify a different path exists first." ,
78+ ] . join ( "\n" )
79+ }
80+
81+ async function buildDynamicAgent ( params : z . infer < typeof parameters > ) {
82+ if ( ! params . subagent_description ) return
83+
84+ const general = await Agent . get ( "general" )
85+ if ( ! general ) {
86+ throw new Error ( 'Dynamic subagents require the native "general" agent to be available.' )
87+ }
88+
89+ return Agent . Info . parse ( {
90+ ...general ,
91+ name : params . subagent_type ,
92+ description : params . subagent_description ,
93+ mode : "subagent" ,
94+ hidden : true ,
95+ prompt : buildDynamicAgentPrompt ( {
96+ name : params . subagent_type ,
97+ description : params . subagent_description ,
98+ workingDirectory : Instance . directory ,
99+ projectRoot : Instance . worktree ,
100+ prompt : params . agent_config ?. prompt ,
101+ } ) ,
102+ temperature : params . agent_config ?. temperature ?? general . temperature ,
103+ topP : params . agent_config ?. top_p ?? general . topP ,
104+ color : params . agent_config ?. color ?? general . color ,
105+ steps : params . agent_config ?. steps ?? general . steps ,
106+ options : {
107+ ...general . options ,
108+ ...( params . agent_config ?. options ?? { } ) ,
109+ } ,
110+ permission : params . agent_config ?. permission
111+ ? Permission . merge ( general . permission , Permission . fromConfig ( params . agent_config . permission ) )
112+ : general . permission ,
113+ } )
114+ }
115+
28116export const TaskTool = Tool . define ( "task" , async ( ctx ) => {
29117 const agents = await Agent . list ( ) . then ( ( x ) => x . filter ( ( a ) => a . mode !== "primary" ) )
30118
@@ -46,6 +134,8 @@ export const TaskTool = Tool.define("task", async (ctx) => {
46134 parameters,
47135 async execute ( params : z . infer < typeof parameters > , ctx ) {
48136 const config = await Config . get ( )
137+ const dynamicAgent = await buildDynamicAgent ( params )
138+ const agent = dynamicAgent ?? ( await Agent . get ( params . subagent_type ) )
49139
50140 // Skip permission check when user explicitly invoked via @ or command subtask
51141 if ( ! ctx . extra ?. bypassAgentCheck ) {
@@ -60,7 +150,6 @@ export const TaskTool = Tool.define("task", async (ctx) => {
60150 } )
61151 }
62152
63- const agent = await Agent . get ( params . subagent_type )
64153 if ( ! agent ) throw new Error ( `Unknown agent type: ${ params . subagent_type } is not a valid agent type` )
65154
66155 const hasTaskPermission = agent . permission . some ( ( rule ) => rule . permission === "task" )
@@ -105,16 +194,20 @@ export const TaskTool = Tool.define("task", async (ctx) => {
105194 const msg = await MessageV2 . get ( { sessionID : ctx . sessionID , messageID : ctx . messageID } )
106195 if ( msg . info . role !== "assistant" ) throw new Error ( "Not an assistant message" )
107196
108- const model = agent . model ?? {
109- modelID : msg . info . modelID ,
110- providerID : msg . info . providerID ,
111- }
197+ const model =
198+ ( params . model ? parseModel ( params . model ) : undefined ) ??
199+ agent . model ?? {
200+ modelID : msg . info . modelID ,
201+ providerID : msg . info . providerID ,
202+ }
203+ const variant = params . variant ?? agent . variant
112204
113205 ctx . metadata ( {
114206 title : params . description ,
115207 metadata : {
116208 sessionId : session . id ,
117209 model,
210+ ...( variant ? { variant } : { } ) ,
118211 } ,
119212 } )
120213
@@ -135,6 +228,8 @@ export const TaskTool = Tool.define("task", async (ctx) => {
135228 providerID : model . providerID ,
136229 } ,
137230 agent : agent . name ,
231+ agentContext : dynamicAgent ,
232+ ...( variant ? { variant } : { } ) ,
138233 tools : {
139234 todowrite : false ,
140235 todoread : false ,
@@ -159,6 +254,7 @@ export const TaskTool = Tool.define("task", async (ctx) => {
159254 metadata : {
160255 sessionId : session . id ,
161256 model,
257+ ...( variant ? { variant } : { } ) ,
162258 } ,
163259 output,
164260 }
0 commit comments