UNPKG

@kontextmind/kxm

Version:

KXM local-first multi-agent orchestration and operator dashboard

313 lines (279 loc) • 8.58 kB
/** * Supervisory agent control, in-process real-time steering, and work-splitting. * * Implements: * - Dual-tool control plane: `agent` (spawn) and `agent_control` (manage, poll, steer, stop). * - Strict capability narrowing via `allowed_tools[]` allowlists. * - Mid-flight steering instruction injection. * - Subagent state lifecycle management (working, blocked, idle, done, error). */ import { randomUUID } from "node:crypto"; import { assertCommandSeatbelt } from "./safety-integrity.ts"; export type SubagentType = "general" | "Explore" | "Plan" | "Reviewer" | "Auditor"; export type SubagentStatus = "pending" | "running" | "blocked" | "completed" | "stopped" | "failed"; export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high"; export interface AgentSpawnParams { prompt: string; description: string; type?: SubagentType | undefined; model?: string | undefined; thinking?: ThinkingLevel | undefined; allowed_tools?: string[] | undefined; turns?: number | undefined; background?: boolean | undefined; parent_tools?: readonly string[] | undefined; } export interface AgentControlParams { action: "info" | "result" | "steer" | "stop"; kind?: "types" | "models" | "active" | undefined; agent_id?: string | undefined; message?: string | undefined; turns?: number | undefined; verbose?: boolean | undefined; } export interface SubagentRecord { id: string; description: string; type: SubagentType; model: string; thinking: ThinkingLevel; allowedTools: string[]; status: SubagentStatus; turnsMax: number; turnsCompleted: number; prompt: string; createdAt: string; updatedAt: string; steeringMessages: Array<{ text: string; injectedAt: string; processed: boolean }>; output?: string | undefined; error?: string | undefined; } export interface SubagentExecutionReceipt { ok: boolean; action: string; agent_id?: string | undefined; status?: SubagentStatus | undefined; activeAgents?: Array<{ id: string; description: string; type: SubagentType; status: SubagentStatus }> | undefined; types?: readonly SubagentType[] | undefined; result?: string | undefined; steered?: boolean | undefined; stopped?: boolean | undefined; error?: string | undefined; } export const SUBAGENT_TYPES: readonly SubagentType[] = Object.freeze([ "general", "Explore", "Plan", "Reviewer", "Auditor", ]); export const DEFAULT_SUBAGENT_MODELS: Readonly<Record<SubagentType, string>> = Object.freeze({ general: "grok/grok-4.6", Explore: "openrouter/qwen/qwen3.7-plus", Plan: "claude/fable", Reviewer: "openai/gpt-5.6-sol", Auditor: "claude/fable", }); /** * Validates and intersects child allowed tools against the parent tool surface. * Enforces the safety invariant that a child can only narrow, never expand authority. */ export function narrowSubagentTools( requestedTools: readonly string[] | undefined, parentTools?: readonly string[] | undefined, ): string[] { if (!requestedTools || requestedTools.length === 0) { return parentTools ? [...parentTools] : ["read", "grep", "find"]; } if (!parentTools || parentTools.length === 0) { return [...requestedTools]; } const parentSet = new Set(parentTools); const narrowed: string[] = []; for (const tool of requestedTools) { if (parentSet.has(tool)) { narrowed.push(tool); } } if (narrowed.length === 0 && requestedTools.length > 0) { throw new Error( `Cannot spawn subagent: requested tools [${requestedTools.join(", ")}] expand beyond parent capabilities.`, ); } return narrowed; } /** * In-Process Subagent Manager. */ export class SubagentManager { private readonly subagents = new Map<string, SubagentRecord>(); /** * Spawns a new managed subagent with strict capability boundaries. */ spawn(params: AgentSpawnParams): SubagentRecord { if (!params.prompt || !params.prompt.trim()) { throw new Error('Subagent "prompt" is required.'); } if (!params.description || !params.description.trim()) { throw new Error('Subagent "description" is required.'); } const type = params.type ?? "general"; const model = params.model ?? DEFAULT_SUBAGENT_MODELS[type] ?? "grok/grok-4.6"; const thinking = params.thinking ?? "low"; const allowedTools = narrowSubagentTools(params.allowed_tools, params.parent_tools); const id = `ag_${randomUUID().replaceAll("-", "").slice(0, 10)}`; const now = new Date().toISOString(); const record: SubagentRecord = { id, description: params.description.trim(), type, model, thinking, allowedTools, status: params.background === false ? "completed" : "running", turnsMax: params.turns ?? 10, turnsCompleted: 0, prompt: params.prompt.trim(), createdAt: now, updatedAt: now, steeringMessages: [], }; this.subagents.set(id, record); return record; } /** * Dispatches an action from the `agent_control` tool. */ control(params: AgentControlParams): SubagentExecutionReceipt { const action = params.action; // 1. Action: "info" if (action === "info") { const kind = params.kind ?? "active"; if (kind === "types") { return { ok: true, action: "info", types: SUBAGENT_TYPES, }; } if (kind === "models") { return { ok: true, action: "info", result: JSON.stringify(DEFAULT_SUBAGENT_MODELS), }; } const active = Array.from(this.subagents.values()).map((ag) => ({ id: ag.id, description: ag.description, type: ag.type, status: ag.status, })); return { ok: true, action: "info", activeAgents: active, }; } if (!params.agent_id) { return { ok: false, action, error: 'Parameter "agent_id" is required for result, steer, and stop actions.', }; } const agent = this.subagents.get(params.agent_id); if (!agent) { return { ok: false, action, agent_id: params.agent_id, error: `Subagent "${params.agent_id}" not found.`, }; } // 2. Action: "result" if (action === "result") { return { ok: true, action: "result", agent_id: agent.id, status: agent.status, result: agent.output ?? `[Subagent is currently ${agent.status}]`, }; } // 3. Action: "steer" if (action === "steer") { if (!params.message || !params.message.trim()) { return { ok: false, action: "steer", agent_id: agent.id, error: 'Parameter "message" is required for steer action.', }; } if (agent.status === "completed" || agent.status === "stopped" || agent.status === "failed") { return { ok: false, action: "steer", agent_id: agent.id, error: `Cannot steer subagent "${agent.id}" because it is already ${agent.status}.`, }; } agent.steeringMessages.push({ text: params.message.trim(), injectedAt: new Date().toISOString(), processed: false, }); agent.updatedAt = new Date().toISOString(); return { ok: true, action: "steer", agent_id: agent.id, status: agent.status, steered: true, }; } // 4. Action: "stop" if (action === "stop") { agent.status = "stopped"; agent.updatedAt = new Date().toISOString(); return { ok: true, action: "stop", agent_id: agent.id, status: "stopped", stopped: true, }; } return { ok: false, action, error: `Unknown control action "${action}".`, }; } /** * Retrieves an agent record by ID. */ get(agentId: string): SubagentRecord | undefined { return this.subagents.get(agentId); } /** * Marks a subagent as completed with output. */ complete(agentId: string, output: string): void { const agent = this.subagents.get(agentId); if (agent) { agent.status = "completed"; agent.output = output; agent.updatedAt = new Date().toISOString(); } } /** * Returns all active running subagents. */ listActive(): SubagentRecord[] { return Array.from(this.subagents.values()).filter( (ag) => ag.status === "running" || ag.status === "pending" || ag.status === "blocked", ); } }