From 7542c8d34088bed0812ed0023aadd86a3361ce6b Mon Sep 17 00:00:00 2001 From: spoons-and-mirrors <212802214+spoons-and-mirrors@users.noreply.github.com> Date: Thu, 20 Nov 2025 06:14:26 +0100 Subject: [PATCH 1/7] feat(exitTask)+fix(task): add a return instruction to the task tool - fix agent conservation in main session --- packages/opencode/src/command/index.ts | 2 ++ packages/opencode/src/config/config.ts | 1 + packages/opencode/src/session/message-v2.ts | 2 ++ packages/opencode/src/session/prompt.ts | 34 ++++++++++----------- packages/opencode/src/tool/task.ts | 17 ++++++++--- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 5e1ad9dc405..49e429245cd 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -30,6 +30,7 @@ export namespace Command { model: z.string().optional(), template: z.string(), subtask: z.boolean().optional(), + returnPrompt: z.string().optional(), }) .meta({ ref: "Command", @@ -49,6 +50,7 @@ export namespace Command { description: command.description, template: command.template, subtask: command.subtask, + returnPrompt: command.returnPrompt, } } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 779a4e8e2a3..7441f6656a9 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -346,6 +346,7 @@ export namespace Config { agent: z.string().optional(), model: z.string().optional(), subtask: z.boolean().optional(), + returnPrompt: z.string().optional(), }) export type Command = z.infer diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 20b612f5480..6dcf37e16fd 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -156,6 +156,8 @@ export namespace MessageV2 { prompt: z.string(), description: z.string(), agent: z.string(), + returnPrompt: z.string().optional(), + model: z.string().optional(), }) export type SubtaskPart = z.infer diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 741e3cc7e05..0706e3eb712 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -327,6 +327,8 @@ export namespace SessionPrompt { prompt: task.prompt, description: task.description, subagent_type: task.agent, + returnPrompt: task.returnPrompt, + model: task.model, }, time: { start: Date.now(), @@ -339,6 +341,8 @@ export namespace SessionPrompt { prompt: task.prompt, description: task.description, subagent_type: task.agent, + returnPrompt: task.returnPrompt, + model: task.model, }, { agent: task.agent, @@ -1311,7 +1315,7 @@ export namespace SessionPrompt { export async function command(input: CommandInput) { log.info("command", input) const command = await Command.get(input.command) - const agentName = command.agent ?? input.agent ?? "build" + const agentName = input.agent ?? "build" const raw = input.arguments.match(argsRegex) ?? [] const args = raw.map((arg) => arg.replace(quoteTrimRegex, "")) @@ -1349,28 +1353,24 @@ export namespace SessionPrompt { } template = template.trim() - const model = await (async () => { - if (command.model) { - return Provider.parseModel(command.model) - } - if (command.agent) { - const cmdAgent = await Agent.get(command.agent) - if (cmdAgent.model) { - return cmdAgent.model - } - } - if (input.model) return Provider.parseModel(input.model) - return await lastModel(input.sessionID) - })() const agent = await Agent.get(agentName) + const parentModel = input.model + ? Provider.parseModel(input.model) + : (agent.model ?? (await lastModel(input.sessionID))) + + const subtaskAgentName = command.agent ?? agentName + const subtaskAgent = await Agent.get(subtaskAgentName) + const subtaskModel = command.model ? Provider.parseModel(command.model) : (subtaskAgent.model ?? parentModel) const parts = - (agent.mode === "subagent" && command.subtask !== false) || command.subtask === true + (subtaskAgent.mode === "subagent" && command.subtask !== false) || command.subtask === true ? [ { type: "subtask" as const, - agent: agent.name, + agent: subtaskAgent.name, description: command.description ?? "", + returnPrompt: command.returnPrompt, + model: command.model, // TODO: how can we make task tool accept a more complex input? prompt: await resolvePromptParts(template).then((x) => x.find((y) => y.type === "text")?.text ?? ""), }, @@ -1380,7 +1380,7 @@ export namespace SessionPrompt { const result = (await prompt({ sessionID: input.sessionID, messageID: input.messageID, - model, + model: parentModel, agent: agentName, parts, })) as MessageV2.WithParts diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 3bb7fb2bf39..485eff127d8 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -7,6 +7,7 @@ import { MessageV2 } from "../session/message-v2" import { Identifier } from "../id/id" import { Agent } from "../agent/agent" import { SessionPrompt } from "../session/prompt" +import { Provider } from "../provider/provider" import { iife } from "@/util/iife" import { defer } from "@/util/defer" @@ -25,6 +26,8 @@ export const TaskTool = Tool.define("task", async () => { prompt: z.string().describe("The task for the agent to perform"), subagent_type: z.string().describe("The type of specialized agent to use for this task"), session_id: z.string().describe("Existing Task session to continue").optional(), + returnPrompt: z.string().describe("Optional instructions for resuming in the parent session").optional(), + model: z.string().describe("Optional model override in provider/model form").optional(), }), async execute(params, ctx) { const agent = await Agent.get(params.subagent_type) @@ -66,10 +69,12 @@ export const TaskTool = Tool.define("task", async () => { }) }) - const model = agent.model ?? { - modelID: msg.info.modelID, - providerID: msg.info.providerID, - } + const model = params.model + ? Provider.parseModel(params.model) + : (agent.model ?? { + modelID: msg.info.modelID, + providerID: msg.info.providerID, + }) function cancel() { SessionPrompt.cancel(session.id) @@ -100,7 +105,9 @@ export const TaskTool = Tool.define("task", async () => { all = all.flatMap((msg) => msg.parts.filter((x: any) => x.type === "tool") as MessageV2.ToolPart[]) const text = result.parts.findLast((x) => x.type === "text")?.text ?? "" - const output = text + "\n\n" + ["", `session_id: ${session.id}`, ""].join("\n") + const exit = params.returnPrompt ? `\n\n${params.returnPrompt}` : "" + const output = + text + exit + "\n\n" + ["", `session_id: ${session.id}`, ""].join("\n") return { title: params.description, From 6f61ee32b7a52a2f1f8d3647b8eee68835d27609 Mon Sep 17 00:00:00 2001 From: spoons-and-mirrors <212802214+spoons-and-mirrors@users.noreply.github.com> Date: Thu, 20 Nov 2025 07:00:45 +0100 Subject: [PATCH 2/7] sdk gen --- packages/sdk/js/src/gen/types.gen.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 3ba41cc2390..043f7645acc 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -383,6 +383,8 @@ export type Part = prompt: string description: string agent: string + returnPrompt?: string + model?: string } | ReasoningPart | FilePart @@ -1005,6 +1007,7 @@ export type Config = { agent?: string model?: string subtask?: boolean + returnPrompt?: string } } watcher?: { @@ -1294,6 +1297,8 @@ export type SubtaskPartInput = { prompt: string description: string agent: string + returnPrompt?: string + model?: string } export type Command = { @@ -1303,6 +1308,7 @@ export type Command = { model?: string template: string subtask?: boolean + returnPrompt?: string } export type Model = { From 6aa7285efed2d3c741602725d455ddff19b28189 Mon Sep 17 00:00:00 2001 From: spoons-and-mirrors <212802214+spoons-and-mirrors@users.noreply.github.com> Date: Thu, 20 Nov 2025 07:14:19 +0100 Subject: [PATCH 3/7] refactor --- packages/opencode/src/session/prompt.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 0706e3eb712..d1ac9f77cc0 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -291,6 +291,7 @@ export namespace SessionPrompt { // TODO: centralize "invoke tool" logic if (task?.type === "subtask") { const taskTool = await TaskTool.init() + const taskModel = task.model ? Provider.parseModel(task.model) : model const assistantMessage = (await Session.updateMessage({ id: Identifier.ascending("message"), role: "assistant", @@ -308,8 +309,8 @@ export namespace SessionPrompt { reasoning: 0, cache: { read: 0, write: 0 }, }, - modelID: model.modelID, - providerID: model.providerID, + modelID: taskModel.modelID, + providerID: taskModel.providerID, time: { created: Date.now(), }, @@ -1354,20 +1355,16 @@ export namespace SessionPrompt { template = template.trim() const agent = await Agent.get(agentName) - const parentModel = input.model - ? Provider.parseModel(input.model) - : (agent.model ?? (await lastModel(input.sessionID))) + const model = input.model ? Provider.parseModel(input.model) : (agent.model ?? (await lastModel(input.sessionID))) - const subtaskAgentName = command.agent ?? agentName - const subtaskAgent = await Agent.get(subtaskAgentName) - const subtaskModel = command.model ? Provider.parseModel(command.model) : (subtaskAgent.model ?? parentModel) + const cmdAgent = command.agent ? await Agent.get(command.agent) : agent const parts = - (subtaskAgent.mode === "subagent" && command.subtask !== false) || command.subtask === true + (cmdAgent.mode === "subagent" && command.subtask !== false) || command.subtask === true ? [ { type: "subtask" as const, - agent: subtaskAgent.name, + agent: cmdAgent.name, description: command.description ?? "", returnPrompt: command.returnPrompt, model: command.model, @@ -1380,7 +1377,7 @@ export namespace SessionPrompt { const result = (await prompt({ sessionID: input.sessionID, messageID: input.messageID, - model: parentModel, + model, agent: agentName, parts, })) as MessageV2.WithParts From 653466574efb9e1bf5e31cb174620559376411f3 Mon Sep 17 00:00:00 2001 From: spoons-and-mirrors <212802214+spoons-and-mirrors@users.noreply.github.com> Date: Wed, 26 Nov 2025 02:57:29 +0100 Subject: [PATCH 4/7] docs --- packages/web/src/content/docs/commands.mdx | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/web/src/content/docs/commands.mdx b/packages/web/src/content/docs/commands.mdx index 463ad9e498f..d632bb7fa87 100644 --- a/packages/web/src/content/docs/commands.mdx +++ b/packages/web/src/content/docs/commands.mdx @@ -312,6 +312,39 @@ This is an **optional** config option. --- +### Return Prompt + +Use the `returnPrompt` option to provide instructions that are appended to the subagent's response when it returns to the parent session. This is useful for guiding what the parent agent should do next after the subtask completes. + +```json title="opencode.json" +{ + "command": { + "research": { + "agent": "general", + "subtask": true, + "returnPrompt": "Challenge, verify and validate the plan before implementing it." + } + } +} +``` + +Or in markdown format: + +```md title=".opencode/command/research.md" +--- +description: Research and implement +agent: general +subtask: true +returnPrompt: Challenge, verify and validate the plan before implementing it. +--- + +Research how to implement $ARGUMENTS and provide a detailed plan. +``` + +This is an **optional** config option. Only applies when the command runs as a subtask. + +--- + ## Built-in opencode includes several built-in commands like `/init`, `/undo`, `/redo`, `/share`, `/help`; [learn more](/docs/tui#commands). From 6f6475238415934e9887f96c7e455e588454761a Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 26 Nov 2025 01:58:39 +0000 Subject: [PATCH 5/7] chore: format code --- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index f026a7538c1..8b55b06899f 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -24,4 +24,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} \ No newline at end of file +} diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index cf2861858e4..d65d93a4ee4 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -26,4 +26,4 @@ "publishConfig": { "directory": "dist" } -} \ No newline at end of file +} From 0df59284d4da3fa10c4daab941ca2525e9860fab Mon Sep 17 00:00:00 2001 From: spoons-and-mirrors <212802214+spoons-and-mirrors@users.noreply.github.com> Date: Wed, 26 Nov 2025 03:03:05 +0100 Subject: [PATCH 6/7] doc --- packages/web/src/content/docs/commands.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/content/docs/commands.mdx b/packages/web/src/content/docs/commands.mdx index d632bb7fa87..062b42bf033 100644 --- a/packages/web/src/content/docs/commands.mdx +++ b/packages/web/src/content/docs/commands.mdx @@ -314,7 +314,7 @@ This is an **optional** config option. ### Return Prompt -Use the `returnPrompt` option to provide instructions that are appended to the subagent's response when it returns to the parent session. This is useful for guiding what the parent agent should do next after the subtask completes. +Use the `returnPrompt` option to provide instructions that are appended to the subagent's response when it returns to the parent session. This is useful for guiding what the parent agent should do with the subtask results. ```json title="opencode.json" { From 0b6115f1e34067872fd2be419ddf948468d78ab0 Mon Sep 17 00:00:00 2001 From: spoons-and-mirrors <212802214+spoons-and-mirrors@users.noreply.github.com> Date: Wed, 26 Nov 2025 03:25:02 +0100 Subject: [PATCH 7/7] doc --- packages/web/src/content/docs/commands.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/content/docs/commands.mdx b/packages/web/src/content/docs/commands.mdx index 062b42bf033..bc581f3b29d 100644 --- a/packages/web/src/content/docs/commands.mdx +++ b/packages/web/src/content/docs/commands.mdx @@ -314,7 +314,7 @@ This is an **optional** config option. ### Return Prompt -Use the `returnPrompt` option to provide instructions that are appended to the subagent's response when it returns to the parent session. This is useful for guiding what the parent agent should do with the subtask results. +Use the `returnPrompt` option to provide instructions that are appended to the subagent's response when it returns to the parent session. This is useful for guiding what the parent agent should do with the subtask results and keeps the agent loop going. ```json title="opencode.json" {