Skip to content

Commit cdd5113

Browse files
feat: Implement advanced agent workflow enhancements
This commit introduces three major architectural enhancements to Roocode: 1. **Asynchronous Operations & Advanced Workflow Control:** * Adds `dispatch_task` for non-blocking sub-task creation. * Adds `get_task_status` to check on active dispatched tasks. * Adds `consolidate_results` to await and gather results from multiple tasks. * Adds `cancel_task` to terminate dispatched tasks. * Adds `release_tasks` for cleaning up terminated task records from memory. * Updates `Task.ts` to manage status, final results, and a global active task list to support these operations. 2. **Centralized Task Lifecycle Management:** * Introduces workspace settings (`taskCompletionMediatorModeEnabled`, `taskCompletionMediatorAgentMode`) to enable a mediator agent. * Modifies `attemptCompletionTool` to intercept task completions, set the original parent to await mediation, and launch a mediator task (as a child of the original parent or new root) in the configured mode. * Adds `resume_parent_task` tool for the mediator to call, which allows the original parent task to resume with the (potentially modified) result. * Updates `Task.ts` and `ClineProvider.ts` to handle the mediation state and result passing. 3. **Conversational Task Framework ("Debate Mode")**: * Adds `start_conversation` tool to manage a structured debate between two dynamically defined AI agents. * The tool handles agent setup with custom system prompts (via enhancements to `Task.ts` and `ClineProvider.ts`), turn-by-turn message passing, and a referee LLM check for a termination condition. * Returns the full conversation transcript. All new tools have been integrated into the system prompt schema and message handling logic. Placeholder i18n keys for new error messages have been added to `tools.json`. Note: `pnpm test` failed due to a pre-commit hook linting issue (ESM/CJS related) that seems to be an environment/tooling setup problem rather than a direct consequence of these code changes. The core feature implementation is complete.
1 parent 922f483 commit cdd5113

22 files changed

+1497
-14
lines changed

src/core/assistant-message/presentAssistantMessage.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ import { askFollowupQuestionTool } from "../tools/askFollowupQuestionTool"
2424
import { switchModeTool } from "../tools/switchModeTool"
2525
import { attemptCompletionTool } from "../tools/attemptCompletionTool"
2626
import { newTaskTool } from "../tools/newTaskTool"
27+
import { dispatchTaskTool } from "../tools/dispatchTaskTool"
28+
import { getTaskStatusTool } from "../tools/getTaskStatusTool"
29+
import { consolidateResultsTool } from "../tools/consolidateResultsTool"
30+
import { cancelTaskTool } from "../tools/cancelTaskTool"
31+
import { releaseTasksTool } from "../tools/releaseTasksTool"
32+
import { resumeParentTaskTool } from "../tools/resumeParentTaskTool"
33+
import { startConversationTool } from "../tools/startConversationTool" // Added startConversationTool
2734

2835
import { checkpointSave } from "../checkpoints"
2936

@@ -211,6 +218,24 @@ export async function presentAssistantMessage(cline: Task) {
211218
const modeName = getModeBySlug(mode, customModes)?.name ?? mode
212219
return `[${block.name} in ${modeName} mode: '${message}']`
213220
}
221+
case "dispatch_task": { // Added for dispatch_task
222+
const mode = block.params.mode ?? defaultModeSlug
223+
const message = block.params.message ?? "(no message)"
224+
const modeName = getModeBySlug(mode, customModes)?.name ?? mode
225+
return `[${block.name} in ${modeName} mode: '${message}']`
226+
}
227+
case "get_task_status":
228+
return `[${block.name} for '${block.params.task_instance_ids}']`
229+
case "consolidate_results":
230+
return `[${block.name} for '${block.params.task_instance_ids}']`
231+
case "cancel_task":
232+
return `[${block.name} for task '${block.params.task_instance_id}']`
233+
case "release_tasks":
234+
return `[${block.name} for tasks '${block.params.task_instance_ids}']`
235+
case "resume_parent_task":
236+
return `[${block.name} for parent '${block.params.original_parent_id}']`
237+
case "start_conversation": // Added for start_conversation
238+
return `[${block.name} with initial prompt: '${block.params.initial_prompt}']`
214239
}
215240
}
216241

@@ -504,6 +529,27 @@ export async function presentAssistantMessage(cline: Task) {
504529
case "new_task":
505530
await newTaskTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
506531
break
532+
case "dispatch_task":
533+
await dispatchTaskTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag, toolDescription)
534+
break
535+
case "get_task_status":
536+
await getTaskStatusTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
537+
break
538+
case "consolidate_results":
539+
await consolidateResultsTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
540+
break
541+
case "cancel_task":
542+
await cancelTaskTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
543+
break
544+
case "release_tasks":
545+
await releaseTasksTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
546+
break
547+
case "resume_parent_task":
548+
await resumeParentTaskTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
549+
break
550+
case "start_conversation": // Added for start_conversation
551+
await startConversationTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
552+
break
507553
case "attempt_completion":
508554
await attemptCompletionTool(
509555
cline,
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ToolDefinition } from "./types"
2+
3+
export const cancelTaskToolDefinition: ToolDefinition = {
4+
name: "cancel_task",
5+
description:
6+
"Requests the cancellation of an actively running sub-task previously dispatched by `dispatch_task`. The target task will be moved to an 'aborted' state. This does not immediately remove the task record; use `release_tasks` for cleanup after confirming cancellation if needed.",
7+
parameters: [
8+
{
9+
name: "task_instance_id",
10+
description: "The instance ID of the task to be cancelled.",
11+
type: "string",
12+
required: true,
13+
},
14+
],
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ToolDefinition } from "./types"
2+
3+
export const consolidateResultsToolDefinition: ToolDefinition = {
4+
name: "consolidate_results",
5+
description:
6+
"Waits for one or more dispatched sub-tasks (identified by their instance IDs) to complete, fail, or be aborted. This is a blocking tool; the current task will pause until all specified sub-tasks have terminated. It then returns an aggregated list of their final statuses and results. After using this, consider calling `release_tasks` to clean up terminated task records.",
7+
parameters: [
8+
{
9+
name: "task_instance_ids",
10+
description:
11+
"A comma-separated string of task instance IDs whose results are to be consolidated (e.g., 'id1,id2,id3').",
12+
type: "string",
13+
required: true,
14+
},
15+
],
16+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ToolDefinition } from "./types"
2+
3+
export const dispatchTaskToolDefinition: ToolDefinition = {
4+
name: "dispatch_task",
5+
description:
6+
"Dispatches a new sub-task to be executed in parallel. This tool is non-blocking; the current task will continue to execute immediately after dispatching the sub-task. The sub-task will run independently. Use `get_task_status` to check its progress and `consolidate_results` to retrieve its output once completed.",
7+
parameters: [
8+
{
9+
name: "mode",
10+
description:
11+
"The mode (persona or capability set) in which the new sub-task should operate (e.g., 'code', 'debug', 'architect', or a custom mode slug).",
12+
type: "string",
13+
required: true,
14+
},
15+
{
16+
name: "message",
17+
description: "The initial message or instruction for the new sub-task.",
18+
type: "string",
19+
required: true,
20+
},
21+
],
22+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ToolDefinition } from "./types"
2+
3+
export const getTaskStatusToolDefinition: ToolDefinition = {
4+
name: "get_task_status",
5+
description:
6+
"Checks the current status of one or more actively running sub-tasks that were previously dispatched using `dispatch_task`. Tasks are identified by their instance IDs. If a task ID is not found among active tasks, its status will be reported as 'unknown'. To get final results of completed/failed tasks, use `consolidate_results`.",
7+
parameters: [
8+
{
9+
name: "task_instance_ids",
10+
description:
11+
"A comma-separated string of task instance IDs for which to retrieve the status (e.g., 'id1,id2,id3').",
12+
type: "string",
13+
required: true,
14+
},
15+
],
16+
}

src/core/prompts/tools/index.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,128 @@ import { getUseMcpToolDescription } from "./use-mcp-tool"
2121
import { getAccessMcpResourceDescription } from "./access-mcp-resource"
2222
import { getSwitchModeDescription } from "./switch-mode"
2323
import { getNewTaskDescription } from "./new-task"
24+
import { dispatchTaskToolDefinition } from "./dispatchTask"
25+
import { getTaskStatusToolDefinition } from "./getTaskStatus"
26+
import { consolidateResultsToolDefinition } from "./consolidateResults"
27+
import { cancelTaskToolDefinition } from "./cancelTask"
28+
import { releaseTasksToolDefinition } from "./releaseTasks"
29+
import { resumeParentTaskToolDefinition } from "./resumeParentTask"
30+
import { startConversationToolDefinition } from "./startConversation" // Import new definition
2431
import { getCodebaseSearchDescription } from "./codebase-search"
2532
import { CodeIndexManager } from "../../../services/code-index/manager"
2633

34+
// Function for the new tool's description
35+
function getDispatchTaskDescription(): string {
36+
return `<tool_description>
37+
<tool_name>${dispatchTaskToolDefinition.name}</tool_name>
38+
<description>${dispatchTaskToolDefinition.description}</description>
39+
<parameters>
40+
${dispatchTaskToolDefinition.parameters
41+
.map(
42+
(param) =>
43+
`<parameter>\n<name>${param.name}</name>\n<type>${param.type}</type>\n<description>${param.description}</description>\n</parameter>`,
44+
)
45+
.join("\n\t\t")}
46+
</parameters>
47+
</tool_description>`
48+
}
49+
50+
// Function for get_task_status tool's description
51+
function getGetTaskStatusDescription(): string {
52+
return `<tool_description>
53+
<tool_name>${getTaskStatusToolDefinition.name}</tool_name>
54+
<description>${getTaskStatusToolDefinition.description}</description>
55+
<parameters>
56+
${getTaskStatusToolDefinition.parameters
57+
.map(
58+
(param) =>
59+
`<parameter>\n<name>${param.name}</name>\n<type>${param.type}</type>\n<description>${param.description}</description>\n</parameter>`,
60+
)
61+
.join("\n\t\t")}
62+
</parameters>
63+
</tool_description>`
64+
}
65+
66+
// Function for consolidate_results tool's description
67+
function getConsolidateResultsDescription(): string {
68+
return `<tool_description>
69+
<tool_name>${consolidateResultsToolDefinition.name}</tool_name>
70+
<description>${consolidateResultsToolDefinition.description}</description>
71+
<parameters>
72+
${consolidateResultsToolDefinition.parameters
73+
.map(
74+
(param) =>
75+
`<parameter>\n<name>${param.name}</name>\n<type>${param.type}</type>\n<description>${param.description}</description>\n</parameter>`,
76+
)
77+
.join("\n\t\t")}
78+
</parameters>
79+
</tool_description>`
80+
}
81+
82+
// Function for cancel_task tool's description
83+
function getCancelTaskDescription(): string {
84+
return `<tool_description>
85+
<tool_name>${cancelTaskToolDefinition.name}</tool_name>
86+
<description>${cancelTaskToolDefinition.description}</description>
87+
<parameters>
88+
${cancelTaskToolDefinition.parameters
89+
.map(
90+
(param) =>
91+
`<parameter>\n<name>${param.name}</name>\n<type>${param.type}</type>\n<description>${param.description}</description>\n</parameter>`,
92+
)
93+
.join("\n\t\t")}
94+
</parameters>
95+
</tool_description>`
96+
}
97+
98+
// Function for release_tasks tool's description
99+
function getReleaseTasksDescription(): string {
100+
return `<tool_description>
101+
<tool_name>${releaseTasksToolDefinition.name}</tool_name>
102+
<description>${releaseTasksToolDefinition.description}</description>
103+
<parameters>
104+
${releaseTasksToolDefinition.parameters
105+
.map(
106+
(param) =>
107+
`<parameter>\n<name>${param.name}</name>\n<type>${param.type}</type>\n<description>${param.description}</description>\n</parameter>`,
108+
)
109+
.join("\n\t\t")}
110+
</parameters>
111+
</tool_description>`
112+
}
113+
114+
// Function for resume_parent_task tool's description
115+
function getResumeParentTaskDescription(): string {
116+
return `<tool_description>
117+
<tool_name>${resumeParentTaskToolDefinition.name}</tool_name>
118+
<description>${resumeParentTaskToolDefinition.description}</description>
119+
<parameters>
120+
${resumeParentTaskToolDefinition.parameters
121+
.map(
122+
(param) =>
123+
`<parameter>\n<name>${param.name}</name>\n<type>${param.type}</type>\n<description>${param.description}</description>\n</parameter>`,
124+
)
125+
.join("\n\t\t")}
126+
</parameters>
127+
</tool_description>`
128+
}
129+
130+
// Function for start_conversation tool's description
131+
function getStartConversationDescription(): string {
132+
return `<tool_description>
133+
<tool_name>${startConversationToolDefinition.name}</tool_name>
134+
<description>${startConversationToolDefinition.description}</description>
135+
<parameters>
136+
${startConversationToolDefinition.parameters
137+
.map(
138+
(param) =>
139+
`<parameter>\n<name>${param.name}</name>\n<type>${param.type}</type>\n<description>${param.description}</description>\n</parameter>`,
140+
)
141+
.join("\n\t\t")}
142+
</parameters>
143+
</tool_description>`
144+
}
145+
27146
// Map of tool names to their description functions
28147
const toolDescriptionMap: Record<string, (args: ToolArgs) => string | undefined> = {
29148
execute_command: (args) => getExecuteCommandDescription(args),
@@ -41,6 +160,13 @@ const toolDescriptionMap: Record<string, (args: ToolArgs) => string | undefined>
41160
codebase_search: () => getCodebaseSearchDescription(),
42161
switch_mode: () => getSwitchModeDescription(),
43162
new_task: (args) => getNewTaskDescription(args),
163+
dispatch_task: () => getDispatchTaskDescription(),
164+
get_task_status: () => getGetTaskStatusDescription(),
165+
consolidate_results: () => getConsolidateResultsDescription(),
166+
cancel_task: () => getCancelTaskDescription(),
167+
release_tasks: () => getReleaseTasksDescription(),
168+
resume_parent_task: () => getResumeParentTaskDescription(),
169+
start_conversation: () => getStartConversationDescription(), // Added start_conversation
44170
insert_content: (args) => getInsertContentDescription(args),
45171
search_and_replace: (args) => getSearchAndReplaceDescription(args),
46172
apply_diff: (args) =>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ToolDefinition } from "./types"
2+
3+
export const releaseTasksToolDefinition: ToolDefinition = {
4+
name: "release_tasks",
5+
description:
6+
"Releases the records of one or more terminated (completed, failed, or aborted) sub-tasks from active memory. This should be called after results have been consolidated and the task records are no longer needed. This tool only affects tasks that are already in a terminal state.",
7+
parameters: [
8+
{
9+
name: "task_instance_ids",
10+
description:
11+
"A comma-separated string of task instance IDs whose records are to be released (e.g., 'id1,id2,id3').",
12+
type: "string",
13+
required: true,
14+
},
15+
],
16+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ToolDefinition } from "./types"
2+
3+
export const resumeParentTaskToolDefinition: ToolDefinition = {
4+
name: "resume_parent_task",
5+
description:
6+
"Called by a mediator agent after it has processed a task's original result. This tool signals that the original parent task (if one exists) should now be resumed using the (potentially modified) result provided by the mediator. If the original task was a root task, this tool indicates the mediation is complete.",
7+
parameters: [
8+
{
9+
name: "original_parent_id",
10+
description:
11+
"The instance ID of the original parent task that was awaiting mediation. Should be 'null' (as a string) if the original task was a root task.",
12+
type: "string", // Will be parsed, 'null' string for actual null
13+
required: true,
14+
},
15+
{
16+
name: "mediated_result",
17+
description: "The final result (potentially modified by the mediator) to be passed to the original parent task or considered the final output if the original was a root task.",
18+
type: "string",
19+
required: true,
20+
},
21+
],
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { ToolDefinition } from "./types"
2+
3+
export const startConversationToolDefinition: ToolDefinition = {
4+
name: "start_conversation",
5+
description:
6+
"Initiates and manages a structured, turn-by-turn conversation (debate) between two dynamically defined AI agents to collaboratively refine ideas, critique plans, or create artifacts. The conversation continues until a specified termination condition is met, as judged by a referee LLM. Returns the full conversation transcript.",
7+
parameters: [
8+
{
9+
name: "participants",
10+
description:
11+
"A JSON string representing an array of two participant agent definitions. Each definition object should have: `base_mode` (optional string, e.g., 'code', 'architect', defaults to general agent mode) and `dynamic_persona_instructions` (required string, specific instructions for this agent in this conversation). Example: '[{\"base_mode\": \"code\", \"dynamic_persona_instructions\": \"You are a senior Python developer. Focus on code clarity and efficiency.\"}, {\"dynamic_persona_instructions\": \"You are a QA engineer. Focus on edge cases and potential bugs.\"}]'",
12+
type: "string", // JSON string
13+
required: true,
14+
},
15+
{
16+
name: "shared_context",
17+
description: "The initial data, document, code snippet, or problem statement that the conversation should be based on. This will be provided to both agents.",
18+
type: "string",
19+
required: true,
20+
},
21+
{
22+
name: "initial_prompt",
23+
description: "The first message to start the conversation, which will be delivered to the first participant.",
24+
type: "string",
25+
required: true,
26+
},
27+
{
28+
name: "termination_condition",
29+
description: "A clear, objective question that a referee LLM will use to determine if the debate is over after each round (e.g., 'Have the participants produced a final, agreed-upon list of changes?'). The referee will answer 'yes' or 'no'.",
30+
type: "string",
31+
required: true,
32+
},
33+
],
34+
}

0 commit comments

Comments
 (0)