From 513a5c90b122abca1792bc0197fc58d632ee206d Mon Sep 17 00:00:00 2001 From: ismeth Date: Tue, 3 Mar 2026 17:14:38 +0100 Subject: [PATCH] fix(athena): force athena-junior to background and exempt from hard TTL Council sessions are long-running: athena-junior launches members then waits for them via background_wait. Two changes prevent premature kills: - Force run_in_background=true when subagent_type resolves to athena-junior, avoiding the 10-minute sync poll timeout (MAX_POLL_TIME_MS) - Exempt athena-junior from the 30-minute TASK_TTL_MS hard cutoff since council members have their own independent TTLs - Fix missing bulk_launch field in AthenaOverrideConfigSchema (type error) --- src/config/schema/agent-overrides.ts | 1 + src/tools/delegate-task/tools.test.ts | 120 ++++++++++++++++++++++++++ src/tools/delegate-task/tools.ts | 13 ++- 3 files changed, 133 insertions(+), 1 deletion(-) diff --git a/src/config/schema/agent-overrides.ts b/src/config/schema/agent-overrides.ts index 9a5b3a3ea..4fef9ffe0 100644 --- a/src/config/schema/agent-overrides.ts +++ b/src/config/schema/agent-overrides.ts @@ -58,6 +58,7 @@ export const AgentOverrideConfigSchema = z.object({ export const AthenaOverrideConfigSchema = AgentOverrideConfigSchema.extend({ council: AthenaConfigSchema.shape.council.optional(), + bulk_launch: AthenaConfigSchema.shape.bulk_launch, non_interactive_mode: AthenaConfigSchema.shape.non_interactive_mode, non_interactive_members: AthenaConfigSchema.shape.non_interactive_members, non_interactive_member_list: AthenaConfigSchema.shape.non_interactive_member_list, diff --git a/src/tools/delegate-task/tools.test.ts b/src/tools/delegate-task/tools.test.ts index 5353ed6d5..ebacffd9b 100644 --- a/src/tools/delegate-task/tools.test.ts +++ b/src/tools/delegate-task/tools.test.ts @@ -742,6 +742,126 @@ describe("sisyphus-task", () => { }) }) + describe("athena-junior forced background execution", () => { + test("athena-junior with run_in_background=false is forced to background", async () => { + //#given + const { createDelegateTask } = require("./tools") + + const tasks = new Map() + const mockManager = { + getTask: (id: string) => tasks.get(id), + launch: async () => { + const task = { id: "bg_athena", status: "pending", description: "Council session", agent: "athena-junior" } + tasks.set(task.id, task) + setTimeout(() => { + tasks.set(task.id, { ...task, status: "running", sessionID: "ses_athena" }) + }, 20) + return task + }, + } + + const mockClient = { + app: { agents: async () => ({ data: [{ name: "athena-junior", mode: "subagent" }] }) }, + config: { get: async () => ({}) }, + session: { + create: async () => ({ data: { id: "ses_athena" } }), + prompt: async () => ({ data: {} }), + promptAsync: async () => ({ data: {} }), + messages: async () => ({ data: [] }), + status: async () => ({ data: {} }), + }, + } + + const tool = createDelegateTask({ + manager: mockManager, + client: mockClient, + connectedProvidersOverride: TEST_CONNECTED_PROVIDERS, + availableModelsOverride: createTestAvailableModels(), + }) + + const toolContext = { + sessionID: "parent-session", + messageID: "parent-message", + agent: "sisyphus", + abort: new AbortController().signal, + } + + //#when — explicitly requesting sync execution + const result = await tool.execute( + { + description: "Run council", + prompt: "Launch council for analysis", + subagent_type: "athena-junior", + run_in_background: false, + load_skills: [], + }, + toolContext + ) + + //#then — should be forced to background despite run_in_background=false + expect(String(result)).toContain("Background task launched") + }, { timeout: 10000 }) + + test("athena-junior with run_in_background=true still runs in background normally", async () => { + //#given + const { createDelegateTask } = require("./tools") + + const tasks = new Map() + const mockManager = { + getTask: (id: string) => tasks.get(id), + launch: async () => { + const task = { id: "bg_athena_bg", status: "pending", description: "Council session", agent: "athena-junior" } + tasks.set(task.id, task) + setTimeout(() => { + tasks.set(task.id, { ...task, status: "running", sessionID: "ses_athena_bg" }) + }, 20) + return task + }, + } + + const mockClient = { + app: { agents: async () => ({ data: [{ name: "athena-junior", mode: "subagent" }] }) }, + config: { get: async () => ({}) }, + session: { + create: async () => ({ data: { id: "ses_athena_bg" } }), + prompt: async () => ({ data: {} }), + promptAsync: async () => ({ data: {} }), + messages: async () => ({ data: [] }), + status: async () => ({ data: {} }), + }, + } + + const tool = createDelegateTask({ + manager: mockManager, + client: mockClient, + connectedProvidersOverride: TEST_CONNECTED_PROVIDERS, + availableModelsOverride: createTestAvailableModels(), + }) + + const toolContext = { + sessionID: "parent-session", + messageID: "parent-message", + agent: "sisyphus", + abort: new AbortController().signal, + } + + //#when — explicitly requesting background (normal path) + const result = await tool.execute( + { + description: "Run council bg", + prompt: "Launch council for analysis", + subagent_type: "athena-junior", + run_in_background: true, + load_skills: [], + }, + toolContext + ) + + //#then — should still be background + expect(String(result)).toContain("Background task launched") + }, { timeout: 10000 }) + }) + describe("resolveCategoryConfig", () => { test("returns null for unknown category without user config", () => { // given diff --git a/src/tools/delegate-task/tools.ts b/src/tools/delegate-task/tools.ts index 5cf062aaf..954c7442e 100644 --- a/src/tools/delegate-task/tools.ts +++ b/src/tools/delegate-task/tools.ts @@ -240,6 +240,17 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini fallbackChain = resolution.fallbackChain } + // Athena-junior council sessions are long-running (members do deep work). + // Force background to avoid the 10-minute sync poll timeout. + const isAthenaJunior = agentToUse.toLowerCase() === "athena-junior" + if (isAthenaJunior && !runInBackground) { + log("[task] Forcing athena-junior to background — council sessions exceed sync poll timeout", { + originalRunInBackground: args.run_in_background, + agentToUse, + }) + } + const effectiveRunInBackground = runInBackground || isAthenaJunior + const systemContent = buildSystemContent({ skillContent, skillContents, @@ -251,7 +262,7 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini availableSkills, }) - if (runInBackground) { + if (effectiveRunInBackground) { return executeBackgroundTask(args, ctx, options, parentContext, agentToUse, categoryModel, systemContent, fallbackChain) }