feat(athena): expand council intent system from 4 to 8 intents
Add DIAGNOSE, CREATE, PERSPECTIVES, FREEFORM intents to Athena's council classification system. Refine existing AUDIT (add DIAGNOSE boundary), EVALUATE (improve criteria guidance), and EXPLAIN (remove catch-all status). Fix bug where prepare_council_prompt defaulted to AUDIT when no intent was specified — this caused analytical frameworks to be injected into creative requests (e.g. poetry). Now defaults to no addendum. Derive validIntents from Object.keys(COUNCIL_INTENT_ADDENDUMS) to prevent drift between the addendums record and the validation array.
This commit is contained in:
@@ -75,16 +75,65 @@ describe("createPrepareCouncilPromptTool", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("#when called with intent DIAGNOSE", () => {
|
||||
it("#then produces file containing DIAGNOSE addendum", async () => {
|
||||
tmpDir = await mkdtemp(join(tmpdir(), "council-test-"))
|
||||
const toolDef = createPrepareCouncilPromptTool(tmpDir)
|
||||
const result = await toolDef.execute({ prompt: "Why is the API returning 500 errors?", intent: "DIAGNOSE" }, mockContext)
|
||||
|
||||
const filePath = extractFilePath(result)
|
||||
const content = await readFile(filePath, "utf-8")
|
||||
expect(content).toContain("## Analysis Intent: DIAGNOSE")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#when called with intent CREATE", () => {
|
||||
it("#then produces file containing CREATE addendum", async () => {
|
||||
tmpDir = await mkdtemp(join(tmpdir(), "council-test-"))
|
||||
const toolDef = createPrepareCouncilPromptTool(tmpDir)
|
||||
const result = await toolDef.execute({ prompt: "Write a poem about TypeScript", intent: "CREATE" }, mockContext)
|
||||
|
||||
const filePath = extractFilePath(result)
|
||||
const content = await readFile(filePath, "utf-8")
|
||||
expect(content).toContain("## Analysis Intent: CREATE")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#when called with intent PERSPECTIVES", () => {
|
||||
it("#then produces file containing PERSPECTIVES addendum", async () => {
|
||||
tmpDir = await mkdtemp(join(tmpdir(), "council-test-"))
|
||||
const toolDef = createPrepareCouncilPromptTool(tmpDir)
|
||||
const result = await toolDef.execute({ prompt: "What do you think about microservices?", intent: "PERSPECTIVES" }, mockContext)
|
||||
|
||||
const filePath = extractFilePath(result)
|
||||
const content = await readFile(filePath, "utf-8")
|
||||
expect(content).toContain("## Analysis Intent: PERSPECTIVES")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#when called with intent FREEFORM", () => {
|
||||
it("#then produces file containing FREEFORM addendum", async () => {
|
||||
tmpDir = await mkdtemp(join(tmpdir(), "council-test-"))
|
||||
const toolDef = createPrepareCouncilPromptTool(tmpDir)
|
||||
const result = await toolDef.execute({ prompt: "Tell me something interesting", intent: "FREEFORM" }, mockContext)
|
||||
|
||||
const filePath = extractFilePath(result)
|
||||
const content = await readFile(filePath, "utf-8")
|
||||
expect(content).toContain("## Analysis Intent: FREEFORM")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#when called without intent", () => {
|
||||
it("#then defaults to AUDIT addendum", async () => {
|
||||
it("#then produces file without any intent addendum", async () => {
|
||||
tmpDir = await mkdtemp(join(tmpdir(), "council-test-"))
|
||||
const toolDef = createPrepareCouncilPromptTool(tmpDir)
|
||||
const result = await toolDef.execute({ prompt: "Review this module" }, mockContext)
|
||||
|
||||
expect(result).toContain("intent: AUDIT")
|
||||
expect(result).toContain("intent: none")
|
||||
const filePath = extractFilePath(result)
|
||||
const content = await readFile(filePath, "utf-8")
|
||||
expect(content).toContain("## Analysis Intent: AUDIT")
|
||||
expect(content).not.toContain("## Analysis Intent:")
|
||||
expect(content).toContain("## Analysis Question")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -45,10 +45,16 @@ The "mode" parameter controls whether council members can delegate exploration t
|
||||
- "delegation": Members can delegate to explore/librarian agents. Faster, lighter context.
|
||||
|
||||
The "intent" parameter controls the analysis framework injected into the prompt:
|
||||
- "AUDIT" (default): Find issues, risks, violations with severity ratings.
|
||||
- "EVALUATE": Compare options against criteria, surface tradeoffs.
|
||||
- "DIAGNOSE": Trace a specific problem to its root cause through systematic investigation.
|
||||
- "AUDIT": Find issues, risks, violations with severity ratings (broad sweep).
|
||||
- "PLAN": Define current state, target state, phased path.
|
||||
- "EVALUATE": Compare options against criteria, surface tradeoffs.
|
||||
- "EXPLAIN": Build understanding of mechanisms and relationships.
|
||||
- "CREATE": Produce a deliverable directly (code, prose, design, spec).
|
||||
- "PERSPECTIVES": Surface genuine viewpoints, argue each at its strongest, take a position.
|
||||
- "FREEFORM": No analytical framework imposed — respond naturally.
|
||||
|
||||
If no intent is specified, no analysis framework is injected.
|
||||
|
||||
Returns the file path to reference in subsequent task() calls.`
|
||||
|
||||
@@ -61,7 +67,7 @@ Returns the file path to reference in subsequent task() calls.`
|
||||
args: {
|
||||
prompt: tool.schema.string().describe("The full analysis prompt/question for council members"),
|
||||
mode: tool.schema.string().optional().describe('Analysis mode: "solo" (default) or "delegation"'),
|
||||
intent: tool.schema.string().optional().describe('Question intent: "AUDIT", "EVALUATE", "PLAN", "EXPLAIN"'),
|
||||
intent: tool.schema.string().optional().describe('Question intent: "DIAGNOSE", "AUDIT", "PLAN", "EVALUATE", "EXPLAIN", "CREATE", "PERSPECTIVES", "FREEFORM"'),
|
||||
},
|
||||
async execute(args: { prompt: string; mode?: string; intent?: string }) {
|
||||
if (!args.prompt?.trim()) {
|
||||
@@ -72,12 +78,12 @@ Returns the file path to reference in subsequent task() calls.`
|
||||
return `Invalid mode: "${args.mode}". Valid modes: "solo", "delegation".`
|
||||
}
|
||||
|
||||
const validIntents = ["AUDIT", "EVALUATE", "PLAN", "EXPLAIN"]
|
||||
const validIntents = Object.keys(COUNCIL_INTENT_ADDENDUMS)
|
||||
if (args.intent !== undefined && !validIntents.includes(args.intent.toUpperCase())) {
|
||||
return `Invalid intent: "${args.intent}". Valid intents: "AUDIT", "EVALUATE", "PLAN", "EXPLAIN".`
|
||||
return `Invalid intent: "${args.intent}". Valid intents: ${validIntents.map((i) => `"${i}"`).join(", ")}.`
|
||||
}
|
||||
|
||||
const resolvedIntent = args.intent?.toUpperCase() ?? "AUDIT"
|
||||
const resolvedIntent = args.intent?.toUpperCase()
|
||||
|
||||
const mode = args.mode === "delegation" ? "delegation" : "solo"
|
||||
|
||||
@@ -89,14 +95,10 @@ Returns the file path to reference in subsequent task() calls.`
|
||||
const filePath = join(tmpDir, filename)
|
||||
|
||||
const modeAddendum = mode === "delegation" ? COUNCIL_DELEGATION_ADDENDUM : COUNCIL_SOLO_ADDENDUM
|
||||
const intentAddendum = COUNCIL_INTENT_ADDENDUMS[resolvedIntent] ?? COUNCIL_INTENT_ADDENDUMS["AUDIT"]
|
||||
const content = `${modeAddendum}
|
||||
|
||||
${intentAddendum}
|
||||
|
||||
## Analysis Question
|
||||
|
||||
${args.prompt}`
|
||||
const intentAddendum = resolvedIntent ? (COUNCIL_INTENT_ADDENDUMS[resolvedIntent] ?? "") : ""
|
||||
const content = intentAddendum
|
||||
? `${modeAddendum}\n\n${intentAddendum}\n\n## Analysis Question\n\n${args.prompt}`
|
||||
: `${modeAddendum}\n\n## Analysis Question\n\n${args.prompt}`
|
||||
|
||||
await writeFile(filePath, content, "utf-8")
|
||||
|
||||
@@ -108,7 +110,7 @@ ${args.prompt}`
|
||||
|
||||
log("[prepare-council-prompt] Saved prompt", { filePath, length: args.prompt.length, mode })
|
||||
|
||||
return `Council prompt saved to: ${filePath} (mode: ${mode}, intent: ${resolvedIntent})
|
||||
return `Council prompt saved to: ${filePath} (mode: ${mode}, intent: ${resolvedIntent ?? "none"})
|
||||
|
||||
Use this path in each council member's task() call:
|
||||
- prompt: "Read ${filePath} for your instructions."
|
||||
|
||||
Reference in New Issue
Block a user