Files
boocode/apps/server/src/services/inference/supervisor.ts
indifferentketchup 381b97f78a feat(server): inference state-graph + supervisor, memory tools, MCP client, schema, routes
- 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
2026-06-08 03:48:47 +00:00

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`,
};
}