fix(athena): prevent recursive council explosion — deny tool for bg tasks + dedup guard

Council members launched as agent='athena' got Athena's system prompt saying
'ALWAYS call athena_council first', plus the tool wasn't denied for bg athena
tasks. Each council member spawned 4 more → exponential explosion (47+ tasks).

Three fixes:
1. Deny athena_council in ATHENA_RESTRICTIONS (agent-tool-restrictions.ts)
   - Only affects background athena tasks (task-starter.ts)
   - Primary Athena (user-selected) still has access via permission field
2. Session-level dedup guard prevents re-calling while council is running
   - If Athena retries during long wait, returns 'already running'
3. Increase wait timeout from 2min to 10min (council members need time
   for real code analysis with Read/Grep/LSP)
This commit is contained in:
ismeth
2026-02-12 16:46:23 +01:00
committed by YeonGyu-Kim
parent 43ea49e523
commit 1c1d09d858
2 changed files with 31 additions and 19 deletions

View File

@@ -14,7 +14,7 @@ const EXPLORATION_AGENT_DENYLIST: Record<string, boolean> = {
}
const ATHENA_RESTRICTIONS = permissionToToolBooleans(
createAgentToolRestrictions(["write", "edit"]).permission
createAgentToolRestrictions(["write", "edit", "athena_council"]).permission
)
const AGENT_RESTRICTIONS: Record<string, Record<string, boolean>> = {

View File

@@ -6,10 +6,13 @@ import { ATHENA_COUNCIL_TOOL_DESCRIPTION } from "./constants"
import { createCouncilLauncher } from "./council-launcher"
import type { AthenaCouncilToolArgs } from "./types"
const WAIT_INTERVAL_MS = 200
const WAIT_TIMEOUT_MS = 120000
const WAIT_INTERVAL_MS = 500
const WAIT_TIMEOUT_MS = 600000
const TERMINAL_STATUSES: Set<BackgroundTaskStatus> = new Set(["completed", "error", "cancelled", "interrupt"])
/** Tracks active council executions per session to prevent duplicate launches. */
const activeCouncilSessions = new Set<string>()
function isCouncilConfigured(councilConfig: CouncilConfig | undefined): councilConfig is CouncilConfig {
return Boolean(councilConfig && councilConfig.members.length > 0)
}
@@ -114,25 +117,34 @@ export function createAthenaCouncilTool(args: {
return "Athena council not configured. Add agents.athena.council.members to your config."
}
const execution = await executeCouncil({
question: toolArgs.question,
council: councilConfig,
launcher: createCouncilLauncher(backgroundManager),
parentSessionID: toolContext.sessionID,
parentMessageID: toolContext.messageID,
parentAgent: toolContext.agent,
})
if (activeCouncilSessions.has(toolContext.sessionID)) {
return "Council is already running for this session. Wait for the current council execution to complete."
}
const taskIds = execution.responses
.map((response) => response.taskId)
.filter((taskId) => taskId.length > 0)
activeCouncilSessions.add(toolContext.sessionID)
try {
const execution = await executeCouncil({
question: toolArgs.question,
council: councilConfig,
launcher: createCouncilLauncher(backgroundManager),
parentSessionID: toolContext.sessionID,
parentMessageID: toolContext.messageID,
parentAgent: toolContext.agent,
})
const latestTasks = await waitForTasksToSettle(taskIds, backgroundManager, toolContext.abort)
const refreshedResponses = execution.responses.map((response) =>
response.taskId ? refreshResponse(response, latestTasks.get(response.taskId)) : response
)
const taskIds = execution.responses
.map((response) => response.taskId)
.filter((taskId) => taskId.length > 0)
return formatCouncilOutput(refreshedResponses, execution.totalMembers)
const latestTasks = await waitForTasksToSettle(taskIds, backgroundManager, toolContext.abort)
const refreshedResponses = execution.responses.map((response) =>
response.taskId ? refreshResponse(response, latestTasks.get(response.taskId)) : response
)
return formatCouncilOutput(refreshedResponses, execution.totalMembers)
} finally {
activeCouncilSessions.delete(toolContext.sessionID)
}
},
})
}