fix(athena): parallelize council launches and gate handoff actions

This commit is contained in:
ismeth
2026-02-18 19:26:20 +01:00
committed by YeonGyu-Kim
parent ca199ee85b
commit 4764d65db1
9 changed files with 433 additions and 411 deletions
+103
View File
@@ -5,6 +5,109 @@ const { builtinTools } = require("../tools")
const { resetStorageClient } = require("../tools/session-manager/storage")
describe("createToolExecuteBeforeHandler", () => {
test("blocks Athena question tool while council members are still running", async () => {
//#given
const ctx = {
client: {
session: {
messages: async () => ({
data: [{ info: { role: "assistant", agent: "Athena (Council)" } }],
}),
},
},
}
const backgroundManager = {
getTasksByParentSession: () => [
{ agent: "council-member", status: "running" },
],
}
const handler = createToolExecuteBeforeHandler({
ctx,
hooks: {},
backgroundManager,
})
//#when
const run = handler(
{ tool: "question", sessionID: "ses_athena", callID: "call_1" },
{ args: { questions: [] } }
)
//#then
await expect(run).rejects.toThrow("Council members are still running")
})
test("blocks Athena switch_agent while council members are still running", async () => {
//#given
const ctx = {
client: {
session: {
messages: async () => ({
data: [{ info: { role: "assistant", agent: "Athena (Council)" } }],
}),
},
},
}
const backgroundManager = {
getTasksByParentSession: () => [
{ agent: "council-member", status: "pending" },
],
}
const handler = createToolExecuteBeforeHandler({
ctx,
hooks: {},
backgroundManager,
})
//#when
const run = handler(
{ tool: "switch_agent", sessionID: "ses_athena", callID: "call_1" },
{ args: { agent: "atlas", context: "ctx" } }
)
//#then
await expect(run).rejects.toThrow("Council members are still running")
})
test("allows Athena question tool when no council members are pending", async () => {
//#given
const ctx = {
client: {
session: {
messages: async () => ({
data: [{ info: { role: "assistant", agent: "Athena (Council)" } }],
}),
},
},
}
const backgroundManager = {
getTasksByParentSession: () => [
{ agent: "council-member", status: "completed" },
{ agent: "council-member", status: "cancelled" },
],
}
const handler = createToolExecuteBeforeHandler({
ctx,
hooks: {},
backgroundManager,
})
//#when
const run = handler(
{ tool: "question", sessionID: "ses_athena", callID: "call_1" },
{ args: { questions: [] } }
)
//#then
await expect(run).resolves.toBeUndefined()
})
test("does not execute subagent question blocker hook for question tool", async () => {
//#given
const ctx = {
+29 -2
View File
@@ -1,10 +1,11 @@
import type { PluginContext } from "./types"
import { randomUUID } from "node:crypto"
import type { BackgroundManager } from "../features/background-agent"
import { getMainSessionID } from "../features/claude-code-session-state"
import { clearBoulderState } from "../features/boulder-state"
import { log } from "../shared"
import { stripInvisibleAgentCharacters } from "../shared/agent-display-names"
import { stripInvisibleAgentCharacters, getAgentConfigKey } from "../shared/agent-display-names"
import { resolveSessionAgent } from "./session-agent-resolver"
import { parseRalphLoopArguments } from "../hooks/ralph-loop/command-arguments"
import { ULTRAWORK_VERIFICATION_PROMISE } from "../hooks/ralph-loop/constants"
@@ -25,11 +26,24 @@ function getLoopCommandArguments(args: Record<string, unknown>, command: "ralph-
export function createToolExecuteBeforeHandler(args: {
ctx: PluginContext
hooks: CreatedHooks
backgroundManager?: Pick<BackgroundManager, "getTasksByParentSession">
}): (
input: { tool: string; sessionID: string; callID: string },
output: { args: Record<string, unknown> },
) => Promise<void> {
const { ctx, hooks } = args
const { ctx, hooks, backgroundManager } = args
function hasPendingCouncilMembers(sessionID: string): boolean {
if (!backgroundManager) {
return false
}
const tasks = backgroundManager.getTasksByParentSession(sessionID)
return tasks.some((task) =>
task.agent === "council-member" &&
(task.status === "pending" || task.status === "running")
)
}
function buildUltraworkOracleVerificationPrompt(prompt: string, originalTask: string, verificationAttemptId: string): string {
const verificationPrompt = [
@@ -62,6 +76,19 @@ export function createToolExecuteBeforeHandler(args: {
}
}
const toolNameLower = input.tool?.toLowerCase()
if (toolNameLower === "question" || toolNameLower === "askuserquestion" || toolNameLower === "ask_user_question" || toolNameLower === "switch_agent") {
const sessionAgent = await resolveSessionAgent(ctx.client, input.sessionID)
const sessionAgentKey = sessionAgent ? getAgentConfigKey(sessionAgent) : undefined
if (sessionAgentKey === "athena" && hasPendingCouncilMembers(input.sessionID)) {
throw new Error(
"Council members are still running. Wait for all launched members to finish and collect their outputs before asking next-step questions or switching agents."
)
}
}
await hooks.writeExistingFileGuard?.["tool.execute.before"]?.(input, output)
await hooks.questionLabelTruncator?.["tool.execute.before"]?.(input, output)
await hooks.claudeCodeHooks?.["tool.execute.before"]?.(input, output)