fix(task): append plan delegation prompt requirements

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-03-06 22:56:51 +09:00
parent 898b628d3d
commit 20b185b59f
8 changed files with 61 additions and 6 deletions

View File

@@ -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,

View File

@@ -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"

View File

@@ -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}`
}

View File

@@ -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) {

View File

@@ -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<string | null> {
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 } }
: {}),

View File

@@ -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

View File

@@ -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

View File

@@ -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,