feat(delegate-task): add prometheus self-delegation block and delegate_task permission

- Block prometheus from delegating to itself via delegate_task
- Grant delegate_task permission to prometheus when called as subagent
- Other subagents still have delegate_task disabled
This commit is contained in:
justsisyphus
2026-01-28 17:52:19 +09:00
parent 315c75c51e
commit dee89c1556
2 changed files with 254 additions and 1 deletions

View File

@@ -1892,4 +1892,250 @@ describe("sisyphus-task", () => {
expect(resolved!.model).toBe(systemDefaultModel)
})
})
describe("prometheus self-delegation block", () => {
test("prometheus cannot delegate to prometheus - returns error with guidance", async () => {
// #given - current agent is prometheus
const { createDelegateTask } = require("./tools")
const mockManager = { launch: async () => ({}) }
const mockClient = {
app: { agents: async () => ({ data: [{ name: "prometheus", mode: "subagent" }] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
status: async () => ({ data: {} }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "prometheus",
abort: new AbortController().signal,
}
// #when - prometheus tries to delegate to prometheus
const result = await tool.execute(
{
description: "Test self-delegation block",
prompt: "Create a plan",
subagent_type: "prometheus",
run_in_background: false,
load_skills: [],
},
toolContext
)
// #then - should return error telling prometheus to create plan directly
expect(result).toContain("prometheus")
expect(result).toContain("directly")
})
test("non-prometheus agent CAN delegate to prometheus - proceeds normally", async () => {
// #given - current agent is sisyphus
const { createDelegateTask } = require("./tools")
const mockManager = { launch: async () => ({}) }
const mockClient = {
app: { agents: async () => ({ data: [{ name: "prometheus", mode: "subagent" }] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_prometheus_allowed" } }),
prompt: async () => ({ data: {} }),
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Plan created successfully" }] }]
}),
status: async () => ({ data: { "ses_prometheus_allowed": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// #when - sisyphus delegates to prometheus
const result = await tool.execute(
{
description: "Test prometheus delegation from non-prometheus agent",
prompt: "Create a plan",
subagent_type: "prometheus",
run_in_background: false,
load_skills: [],
},
toolContext
)
// #then - should proceed normally
expect(result).not.toContain("Cannot delegate")
expect(result).toContain("Plan created successfully")
}, { timeout: 20000 })
test("case-insensitive: Prometheus (capitalized) cannot delegate to prometheus", async () => {
// #given - current agent is Prometheus (capitalized)
const { createDelegateTask } = require("./tools")
const mockManager = { launch: async () => ({}) }
const mockClient = {
app: { agents: async () => ({ data: [{ name: "prometheus", mode: "subagent" }] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
status: async () => ({ data: {} }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "Prometheus",
abort: new AbortController().signal,
}
// #when - Prometheus tries to delegate to prometheus
const result = await tool.execute(
{
description: "Test case-insensitive block",
prompt: "Create a plan",
subagent_type: "prometheus",
run_in_background: false,
load_skills: [],
},
toolContext
)
// #then - should still return error
expect(result).toContain("prometheus")
expect(result).toContain("directly")
})
})
describe("prometheus subagent delegate_task permission", () => {
test("prometheus subagent should have delegate_task permission enabled", async () => {
// #given - sisyphus delegates to prometheus
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const mockClient = {
app: { agents: async () => ({ data: [{ name: "prometheus", mode: "subagent" }] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_prometheus_delegate" } }),
prompt: async (input: any) => {
promptBody = input.body
return { data: {} }
},
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Plan created" }] }]
}),
status: async () => ({ data: { "ses_prometheus_delegate": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// #when - sisyphus delegates to prometheus
await tool.execute(
{
description: "Test prometheus delegate_task permission",
prompt: "Create a plan",
subagent_type: "prometheus",
run_in_background: false,
load_skills: [],
},
toolContext
)
// #then - prometheus should have delegate_task permission
expect(promptBody.tools.delegate_task).toBe(true)
}, { timeout: 20000 })
test("non-prometheus subagent should NOT have delegate_task permission", async () => {
// #given - sisyphus delegates to oracle (non-prometheus)
const { createDelegateTask } = require("./tools")
let promptBody: any
const mockManager = { launch: async () => ({}) }
const mockClient = {
app: { agents: async () => ({ data: [{ name: "oracle", mode: "subagent" }] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
session: {
get: async () => ({ data: { directory: "/project" } }),
create: async () => ({ data: { id: "ses_oracle_no_delegate" } }),
prompt: async (input: any) => {
promptBody = input.body
return { data: {} }
},
messages: async () => ({
data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Consultation done" }] }]
}),
status: async () => ({ data: { "ses_oracle_no_delegate": { type: "idle" } } }),
},
}
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// #when - sisyphus delegates to oracle
await tool.execute(
{
description: "Test oracle no delegate_task permission",
prompt: "Consult on architecture",
subagent_type: "oracle",
run_in_background: false,
load_skills: [],
},
toolContext
)
// #then - oracle should NOT have delegate_task permission
expect(promptBody.tools.delegate_task).toBe(false)
}, { timeout: 20000 })
})
})

View File

@@ -768,6 +768,12 @@ To continue this session: session_id="${sessionID}"`
Sisyphus-Junior is spawned automatically when you specify a category. Pick the appropriate category for your task domain.`
}
if (isPlanAgent(agentName) && isPlanAgent(parentAgent)) {
return `You are prometheus. You cannot delegate to prometheus via delegate_task.
Create the work plan directly - that's your job as the planning agent.`
}
agentToUse = agentName
// Validate agent exists and is callable (not a primary agent)
@@ -927,6 +933,7 @@ To continue this session: session_id="${task.sessionID}"`
})
try {
const allowDelegateTask = isPlanAgent(agentToUse)
await client.session.prompt({
path: { id: sessionID },
body: {
@@ -934,7 +941,7 @@ To continue this session: session_id="${task.sessionID}"`
system: systemContent,
tools: {
task: false,
delegate_task: false,
delegate_task: allowDelegateTask,
call_omo_agent: true,
question: false,
},