diff --git a/src/tools/delegate-task/background-task.ts b/src/tools/delegate-task/background-task.ts index aedd0b3eb..fd273fb95 100644 --- a/src/tools/delegate-task/background-task.ts +++ b/src/tools/delegate-task/background-task.ts @@ -2,6 +2,7 @@ import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types" import type { ExecutorContext, ParentContext } from "./executor-types" import type { FallbackEntry } from "../../shared/model-requirements" import { getTimingConfig } from "./timing" +import { buildTaskPrompt } from "./prompt-builder" import { storeToolMetadata } from "../../features/tool-metadata-store" import { formatDetailedError } from "./error-formatting" import { getSessionTools } from "../../shared/session-tools-store" @@ -20,9 +21,10 @@ export async function executeBackgroundTask( const { manager } = executorCtx try { + const effectivePrompt = buildTaskPrompt(args.prompt, agentToUse) const task = await manager.launch({ description: args.description, - prompt: args.prompt, + prompt: effectivePrompt, agent: agentToUse, parentSessionID: parentContext.sessionID, parentMessageID: parentContext.messageID, diff --git a/src/tools/delegate-task/index.ts b/src/tools/delegate-task/index.ts index 5e8f7c817..5c50066e9 100644 --- a/src/tools/delegate-task/index.ts +++ b/src/tools/delegate-task/index.ts @@ -1,4 +1,4 @@ -export { createDelegateTask, resolveCategoryConfig, buildSystemContent } from "./tools" +export { createDelegateTask, resolveCategoryConfig, buildSystemContent, buildTaskPrompt } from "./tools" export type { DelegateTaskToolOptions, SyncSessionCreatedEvent, BuildSystemContentInput } from "./tools" export type * from "./types" export * from "./constants" diff --git a/src/tools/delegate-task/prompt-builder.ts b/src/tools/delegate-task/prompt-builder.ts index 8230fed78..ddae1ce59 100644 --- a/src/tools/delegate-task/prompt-builder.ts +++ b/src/tools/delegate-task/prompt-builder.ts @@ -3,6 +3,14 @@ import { buildPlanAgentSystemPrepend, isPlanAgent } from "./constants" import { buildSystemContentWithTokenLimit } from "./token-limiter" const FREE_OR_LOCAL_PROMPT_TOKEN_LIMIT = 24000 +const PLAN_AGENT_PROMPT_APPEND = ` + +Additional requirements for this planning request: +- Answer in English. +- Write the plan in English. +- Plan well for ultrawork execution. +- Use TDD-oriented planning. +- Include a clear atomic commit strategy.` function usesFreeOrLocalModel(model: { providerID: string; modelID: string; variant?: string } | undefined): boolean { if (!model) { @@ -52,3 +60,11 @@ export function buildSystemContent(input: BuildSystemContentInput): string | und effectiveMaxPromptTokens ) } + +export function buildTaskPrompt(prompt: string, agentName: string | undefined): string { + if (!isPlanAgent(agentName)) { + return prompt + } + + return `${prompt}${PLAN_AGENT_PROMPT_APPEND}` +} diff --git a/src/tools/delegate-task/sync-continuation.ts b/src/tools/delegate-task/sync-continuation.ts index b716b3948..d24ba4574 100644 --- a/src/tools/delegate-task/sync-continuation.ts +++ b/src/tools/delegate-task/sync-continuation.ts @@ -11,6 +11,7 @@ import { formatDuration } from "./time-formatter" import { syncContinuationDeps, type SyncContinuationDeps } from "./sync-continuation-deps" import { setSessionTools } from "../../shared/session-tools-store" import { normalizeSDKResponse } from "../../shared" +import { buildTaskPrompt } from "./prompt-builder" export async function executeSyncContinuation( args: DelegateTaskArgs, @@ -82,6 +83,7 @@ export async function executeSyncContinuation( } const allowTask = isPlanFamily(resumeAgent) + const effectivePrompt = buildTaskPrompt(args.prompt, resumeAgent) const tools = { ...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}), task: allowTask, @@ -97,7 +99,7 @@ export async function executeSyncContinuation( ...(resumeModel !== undefined ? { model: resumeModel } : {}), ...(resumeVariant !== undefined ? { variant: resumeVariant } : {}), tools, - parts: [{ type: "text", text: args.prompt }], + parts: [{ type: "text", text: effectivePrompt }], }, }) } catch (promptError) { diff --git a/src/tools/delegate-task/sync-prompt-sender.ts b/src/tools/delegate-task/sync-prompt-sender.ts index 49ee2e2e7..fe4f8a693 100644 --- a/src/tools/delegate-task/sync-prompt-sender.ts +++ b/src/tools/delegate-task/sync-prompt-sender.ts @@ -1,5 +1,6 @@ import type { DelegateTaskArgs, OpencodeClient } from "./types" import { isPlanFamily } from "./constants" +import { buildTaskPrompt } from "./prompt-builder" import { promptSyncWithModelSuggestionRetry, promptWithModelSuggestionRetry, @@ -43,6 +44,7 @@ export async function sendSyncPrompt( deps: SendSyncPromptDeps = sendSyncPromptDeps ): Promise { const allowTask = isPlanFamily(input.agentToUse) + const effectivePrompt = buildTaskPrompt(input.args.prompt, input.agentToUse) const tools = { task: allowTask, call_omo_agent: true, @@ -57,7 +59,7 @@ export async function sendSyncPrompt( agent: input.agentToUse, system: input.systemContent, tools, - parts: [createInternalAgentTextPart(input.args.prompt)], + parts: [createInternalAgentTextPart(effectivePrompt)], ...(input.categoryModel ? { model: { providerID: input.categoryModel.providerID, modelID: input.categoryModel.modelID } } : {}), diff --git a/src/tools/delegate-task/tools.test.ts b/src/tools/delegate-task/tools.test.ts index bb0b5ec29..8f7ebb79f 100644 --- a/src/tools/delegate-task/tools.test.ts +++ b/src/tools/delegate-task/tools.test.ts @@ -2896,6 +2896,37 @@ describe("sisyphus-task", () => { }) }) + describe("buildTaskPrompt", () => { + test("appends English ULW TDD and commit guidance for plan agent", () => { + // given + const { buildTaskPrompt } = require("./tools") + const prompt = "Create a work plan for this feature" + + // when + const result = buildTaskPrompt(prompt, "plan") + + // then + expect(result).toContain(prompt) + expect(result).toContain("Answer in English.") + expect(result).toContain("Write the plan in English.") + expect(result).toContain("Plan well for ultrawork execution.") + expect(result).toContain("Use TDD-oriented planning.") + expect(result).toContain("Include a clear atomic commit strategy.") + }) + + test("does not append plan guidance for non-plan agents", () => { + // given + const { buildTaskPrompt } = require("./tools") + const prompt = "Investigate this module" + + // when + const result = buildTaskPrompt(prompt, "explore") + + // then + expect(result).toBe(prompt) + }) + }) + describe("modelInfo detection via resolveCategoryConfig", () => { test("catalog model is used for category with catalog entry", () => { // given - ultrabrain has catalog entry diff --git a/src/tools/delegate-task/tools.ts b/src/tools/delegate-task/tools.ts index fcd691f1f..68c7dc4af 100644 --- a/src/tools/delegate-task/tools.ts +++ b/src/tools/delegate-task/tools.ts @@ -23,7 +23,7 @@ import { export { resolveCategoryConfig } from "./categories" export type { SyncSessionCreatedEvent, DelegateTaskToolOptions, BuildSystemContentInput } from "./types" -export { buildSystemContent } from "./prompt-builder" +export { buildSystemContent, buildTaskPrompt } from "./prompt-builder" export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefinition { const { userCategories } = options diff --git a/src/tools/delegate-task/unstable-agent-task.ts b/src/tools/delegate-task/unstable-agent-task.ts index 9a0b16c69..c455d7311 100644 --- a/src/tools/delegate-task/unstable-agent-task.ts +++ b/src/tools/delegate-task/unstable-agent-task.ts @@ -1,6 +1,7 @@ import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types" import type { ExecutorContext, ParentContext, SessionMessage } from "./executor-types" import { DEFAULT_SYNC_POLL_TIMEOUT_MS, getTimingConfig } from "./timing" +import { buildTaskPrompt } from "./prompt-builder" import { storeToolMetadata } from "../../features/tool-metadata-store" import { formatDuration } from "./time-formatter" import { formatDetailedError } from "./error-formatting" @@ -20,9 +21,10 @@ export async function executeUnstableAgentTask( const { manager, client, syncPollTimeoutMs } = executorCtx try { + const effectivePrompt = buildTaskPrompt(args.prompt, agentToUse) const task = await manager.launch({ description: args.description, - prompt: args.prompt, + prompt: effectivePrompt, agent: agentToUse, parentSessionID: parentContext.sessionID, parentMessageID: parentContext.messageID,