feat(hooks): add delegate-task-english-directive hook to enforce English for subagents

Appends bold uppercase English-only directive to explore, librarian,
oracle, and plan subagent prompts via tool.execute.before on the task tool.
This commit is contained in:
YeonGyu-Kim
2026-03-13 14:11:38 +09:00
parent 0303488906
commit f3de122147
6 changed files with 132 additions and 0 deletions

View File

@@ -51,6 +51,7 @@ export const HookNameSchema = z.enum([
"anthropic-effort",
"hashline-read-enhancer",
"read-image-resizer",
"delegate-task-english-directive",
])
export type HookName = z.infer<typeof HookNameSchema>

View File

@@ -0,0 +1,24 @@
export const TARGET_SUBAGENT_TYPES = ["explore", "librarian", "oracle", "plan"] as const
export const ENGLISH_DIRECTIVE =
"**YOU MUST ALWAYS THINK, REASON, AND RESPOND IN ENGLISH REGARDLESS OF THE USER'S QUERY LANGUAGE.**"
export function createDelegateTaskEnglishDirectiveHook() {
return {
"tool.execute.before": async (
input: { tool: string; sessionID: string; callID: string; input: Record<string, unknown> },
_output: { title: string; output: string; metadata: unknown }
) => {
if (input.tool.toLowerCase() !== "task") return
const args = input.input
const subagentType = args.subagent_type
if (typeof subagentType !== "string") return
if (!TARGET_SUBAGENT_TYPES.includes(subagentType as (typeof TARGET_SUBAGENT_TYPES)[number])) return
if (typeof args.prompt === "string") {
args.prompt = `${args.prompt}\n\n${ENGLISH_DIRECTIVE}`
}
},
}
}

View File

@@ -0,0 +1,98 @@
import { describe, expect, it } from "bun:test"
import { createDelegateTaskEnglishDirectiveHook, ENGLISH_DIRECTIVE, TARGET_SUBAGENT_TYPES } from "./index"
describe("delegate-task-english-directive", () => {
const hook = createDelegateTaskEnglishDirectiveHook()
const handler = hook["tool.execute.before"]
describe("#given a task tool call with a targeted subagent_type", () => {
const targetTypes = ["explore", "librarian", "oracle", "plan"]
for (const subagentType of targetTypes) {
describe(`#when subagent_type is "${subagentType}"`, () => {
it(`#then should append English directive to prompt`, async () => {
const originalPrompt = "Find auth patterns in the codebase"
const input = { tool: "Task", sessionID: "ses_123", callID: "call_1", input: { subagent_type: subagentType, prompt: originalPrompt } }
const output = { title: "", output: "", metadata: undefined }
await handler(input, output)
expect(input.input.prompt).toBe(`${originalPrompt}\n\n${ENGLISH_DIRECTIVE}`)
})
})
}
})
describe("#given a task tool call with a non-targeted subagent_type", () => {
describe("#when subagent_type is 'metis'", () => {
it("#then should not modify the prompt", async () => {
const originalPrompt = "Analyze this request"
const input = { tool: "Task", sessionID: "ses_123", callID: "call_1", input: { subagent_type: "metis", prompt: originalPrompt } }
const output = { title: "", output: "", metadata: undefined }
await handler(input, output)
expect(input.input.prompt).toBe(originalPrompt)
})
})
})
describe("#given a task tool call using category instead of subagent_type", () => {
describe("#when only category is provided", () => {
it("#then should not modify the prompt", async () => {
const originalPrompt = "Fix the button styling"
const input = { tool: "Task", sessionID: "ses_123", callID: "call_1", input: { category: "visual-engineering", prompt: originalPrompt } }
const output = { title: "", output: "", metadata: undefined }
await handler(input, output)
expect(input.input.prompt).toBe(originalPrompt)
})
})
})
describe("#given a non-task tool call", () => {
describe("#when tool is 'Bash'", () => {
it("#then should not modify anything", async () => {
const input = { tool: "Bash", sessionID: "ses_123", callID: "call_1", input: { command: "ls" } }
const output = { title: "", output: "", metadata: undefined }
await handler(input, output)
expect(input.input).toEqual({ command: "ls" })
})
})
})
describe("#given a task tool call with empty prompt", () => {
describe("#when prompt is empty string", () => {
it("#then should still append directive", async () => {
const input = { tool: "Task", sessionID: "ses_123", callID: "call_1", input: { subagent_type: "explore", prompt: "" } }
const output = { title: "", output: "", metadata: undefined }
await handler(input, output)
expect(input.input.prompt).toBe(`\n\n${ENGLISH_DIRECTIVE}`)
})
})
})
describe("#given TARGET_SUBAGENT_TYPES constant", () => {
it("#then should contain exactly explore, librarian, oracle, and plan", () => {
expect(TARGET_SUBAGENT_TYPES).toEqual(["explore", "librarian", "oracle", "plan"])
})
})
describe("#given ENGLISH_DIRECTIVE constant", () => {
it("#then should be bold uppercase text", () => {
expect(ENGLISH_DIRECTIVE).toContain("**")
expect(ENGLISH_DIRECTIVE).toMatch(/[A-Z]/)
})
it("#then should instruct English-only thinking and responding", () => {
const lower = ENGLISH_DIRECTIVE.toLowerCase()
expect(lower).toContain("english")
})
})
})

View File

@@ -0,0 +1 @@
export { createDelegateTaskEnglishDirectiveHook, TARGET_SUBAGENT_TYPES, ENGLISH_DIRECTIVE } from "./hook"

View File

@@ -52,3 +52,4 @@ export { createWriteExistingFileGuardHook } from "./write-existing-file-guard";
export { createHashlineReadEnhancerHook } from "./hashline-read-enhancer";
export { createJsonErrorRecoveryHook, JSON_ERROR_TOOL_EXCLUDE_LIST, JSON_ERROR_PATTERNS, JSON_ERROR_REMINDER } from "./json-error-recovery";
export { createReadImageResizerHook } from "./read-image-resizer"
export { createDelegateTaskEnglishDirectiveHook } from "./delegate-task-english-directive"

View File

@@ -16,6 +16,7 @@ import {
createRalphLoopHook,
createEditErrorRecoveryHook,
createDelegateTaskRetryHook,
createDelegateTaskEnglishDirectiveHook,
createTaskResumeInfoHook,
createStartWorkHook,
createPrometheusMdOnlyHook,
@@ -60,6 +61,7 @@ export type SessionHooks = {
taskResumeInfo: ReturnType<typeof createTaskResumeInfoHook> | null
anthropicEffort: ReturnType<typeof createAnthropicEffortHook> | null
runtimeFallback: ReturnType<typeof createRuntimeFallbackHook> | null
delegateTaskEnglishDirective: ReturnType<typeof createDelegateTaskEnglishDirectiveHook> | null
}
export function createSessionHooks(args: {
@@ -215,6 +217,10 @@ export function createSessionHooks(args: {
? safeHook("delegate-task-retry", () => createDelegateTaskRetryHook(ctx))
: null
const delegateTaskEnglishDirective = isHookEnabled("delegate-task-english-directive")
? safeHook("delegate-task-english-directive", () => createDelegateTaskEnglishDirectiveHook())
: null
const startWork = isHookEnabled("start-work")
? safeHook("start-work", () => createStartWorkHook(ctx))
: null
@@ -276,6 +282,7 @@ export function createSessionHooks(args: {
ralphLoop,
editErrorRecovery,
delegateTaskRetry,
delegateTaskEnglishDirective,
startWork,
prometheusMdOnly,
sisyphusJuniorNotepad,