- Add state-graph.ts: typed state machine for inference lifecycle - Add supervisor.ts: agent supervisor pattern for multi-agent coordination - Add export-formatter.ts: structured export formatting - Add manage_memory.ts: memory CRUD tool for agent persistence - Add get_wiki_article.ts: codecontext wiki article retrieval - Extend memory/index.ts: 3-tier memory (context/daily/core) - Extend MCP client: mcp-config.ts env-var substitution - Update schema.sql: agent_sessions, tasks, pending_changes extensions - Update API types: MessageMetadata, ErrorReason, AgentSessionConfig - Update routes: chats, messages, sessions — column renames and agent_session_id - Update inference: error handler, payload builder, stream phase, turn orchestrator
76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
// Supervisor agent: routes user requests to the best agent via a cheap LLM
|
|
// classification call. Activated when session.agent_id === 'supervisor'.
|
|
|
|
import type { Agent } from '../../types/api.js';
|
|
import { taskModelCompletion } from '../task-model.js';
|
|
|
|
export interface SupervisorRoute {
|
|
agent_id: string;
|
|
confidence: number;
|
|
reasoning: string;
|
|
}
|
|
|
|
const SUPERVISOR_SYSTEM_PROMPT = `You are a router. Given the user's request and the available agents, choose the best agent to handle the request.
|
|
|
|
Rules:
|
|
- Match the request to the agent whose description and toolset best fits the task.
|
|
- For code review / bug finding requests → code-reviewer
|
|
- For debugging / diagnosing failures → debugger
|
|
- For refactoring / simplifying code → refactorer
|
|
- For architecture / design / planning → architect or planner
|
|
- For security audits → security-auditor
|
|
- For building prompts for other agents → prompt-builder
|
|
- For exploring / understanding unfamiliar code → recon
|
|
- For implementing / writing code changes → builder
|
|
- Respond with ONLY the agent id (e.g. "builder") or "none" if no agent fits.
|
|
- Do not include any other text, punctuation, or explanation.`;
|
|
|
|
const MAX_ROUTING_TOKENS = 30;
|
|
|
|
/**
|
|
* Given the user's latest message and available agents, classifies which agent
|
|
* should handle this turn. Returns null to fall through to default (no agent).
|
|
*/
|
|
export async function resolveSupervisorTurn(
|
|
latestUserMessage: string,
|
|
agents: Agent[],
|
|
fallbackModel?: string,
|
|
): Promise<SupervisorRoute | null> {
|
|
// Build agent listing — skip the supervisor itself to avoid self-routing.
|
|
const agentList = agents
|
|
.filter((a) => a.id !== 'supervisor')
|
|
.map((a) => `- ${a.id}: ${a.description} (${a.tools.length} tools)`)
|
|
.join('\n');
|
|
|
|
if (!agentList) {
|
|
return null;
|
|
}
|
|
|
|
const userPrompt = `Available agents:\n${agentList}\n\nUser request: ${latestUserMessage.slice(0, 2000)}`;
|
|
|
|
const response = await taskModelCompletion({
|
|
system: SUPERVISOR_SYSTEM_PROMPT,
|
|
user: userPrompt,
|
|
maxTokens: MAX_ROUTING_TOKENS,
|
|
temperature: 0.1,
|
|
fallbackModel,
|
|
});
|
|
|
|
const agentId = response.trim().toLowerCase();
|
|
if (!agentId || agentId === 'none') {
|
|
return null;
|
|
}
|
|
|
|
// Map back to a real agent to validate the id.
|
|
const matched = agents.find((a) => a.id === agentId);
|
|
if (!matched) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
agent_id: matched.id,
|
|
confidence: 1,
|
|
reasoning: `supervisor routed to "${matched.name}" based on request classification`,
|
|
};
|
|
}
|