UNPKG

@kontextmind/kxm

Version:

KXM local-first multi-agent orchestration and operator dashboard

663 lines (635 loc) • 26.7 kB
/** * Interactive post-init setup for workflow-guide agents and workflows. * * Source of truth: `docs/reference/workflow-catalog.md`. The catalog below transcribes the * software-engineering area (workflows, stages, role slugs, and the guide's * ordered candidate lists). Guide candidates are dated research; each role's * first candidate whose harness is installed AND authenticated wins. No * candidate is admitted without an authenticated harness (fail closed). * * This module writes only current KXM project resources: * - `.kxm/agents/<role-slug>.yaml` (kxm.agent.v1) * - `.kxm/workflows/<slug>.yaml` (kxm.workflow.v1) * - `.kxm/roles/<role-slug>.yaml` (kxm.role.v2) * - `.kxm/models/<route-id>.yaml` (kxm.model.v2) * - `plans/evidence/route-guide-qwen-pi.md` when a Pi binding matches that note * - admitted selectors appended to `.kxm/routes.yaml` * It never writes retired legacy authority (`.kxm/config` or * `.kxm/roster.json`). Role and model files are the dispatch authority. * * Guide research ids are not dispatch ids. Only the admitted map below is * written, and only when that harness is authenticated. Unmapped ids are skipped. * * Google candidates are unmapped on purpose. Google's route is the Pi * `antigravity` provider, which only the KXM Pi extension registers. The * Runtime's Pi one-shot runs with `--no-extensions`, and `pi auth check` never * loads extensions, so a drive of an `antigravity/…` role fails at dispatch. * Mapping Google to `agy` instead would contradict the routing decision. Add * Google back only when drive can reach `antigravity` and a reviewed * admission decision says so. */ import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { stringify } from "yaml"; import { type HarnessInventory } from "./harness.ts"; import { loadRoutePolicy } from "./routes.ts"; export interface GuideCandidate { readonly vendor: string; readonly model: string; } export interface GuideStage { readonly slug: string; readonly title: string; readonly role: string; readonly domain: string; readonly candidates: readonly GuideCandidate[]; } export interface GuideWorkflow { readonly slug: string; readonly area: string; readonly name: string; readonly summary: string; readonly stages: readonly GuideStage[]; } /** Research id → the admitted harness/provider/model drive will actually accept. * Anything absent is skipped, even when its harness is logged in. */ const ADMITTED_GUIDE_BINDINGS: Readonly<Record<string, AgentBinding>> = Object.freeze({ "anthropic/claude-fable-5.1": { harness: "claude", provider: "anthropic", model: "fable" }, "anthropic/fable": { harness: "claude", provider: "anthropic", model: "fable" }, "openai/gpt-5.6-sol": { harness: "codex", provider: "openai", model: "gpt-5.6-sol" }, "x-ai/grok-4.6": { harness: "grok", provider: "xai", model: "grok-4.6" }, "xai/grok-4.6": { harness: "grok", provider: "xai", model: "grok-4.6" }, "qwen/qwen3-coder-plus": { harness: "pi", provider: "openrouter", model: "qwen/qwen3-coder-plus" }, }); /** Guide candidates are ordered by preference; the first eligible wins. */ export const GUIDE_WORKFLOWS: readonly GuideWorkflow[] = [ { slug: "build-feature", area: "software-engineering", name: "Build Feature", summary: "PRD ingestion → system planning → spec/contract → scaffold → backend → frontend → SDK integration", stages: [ { slug: "system-planning", title: "System planning", role: "lead-systems-planner", domain: "Decomposes requirements into an executable, dependency-ordered DAG of modules, tasks, and verification gates.", candidates: [ { vendor: "anthropic", model: "claude-fable-5.1" }, { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, { vendor: "openai", model: "gpt-6-astra-pro" }, { vendor: "x-ai", model: "grok-4.6" }, { vendor: "qwen", model: "qwen3.8-max-0902" }, ], }, { slug: "spec-contract", title: "Spec and contract", role: "spec-contract-generator", domain: "Authors zero-ambiguity API contracts, type definitions, and schema validation rules before implementation begins.", candidates: [ { vendor: "openai", model: "gpt-5.6-sol" }, { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, { vendor: "google", model: "gemini-3.8-flash" }, { vendor: "qwen", model: "qwen3-coder-plus" }, { vendor: "anthropic", model: "claude-fable-5.1" }, ], }, { slug: "scaffold", title: "Scaffold", role: "scaffold-build-specialist", domain: "Provisions monorepo workspace topologies, linters, Docker multi-stage builds, and CI pipelines.", candidates: [ { vendor: "x-ai", model: "grok-4.6" }, { vendor: "mistralai", model: "devstral-2512" }, { vendor: "qwen", model: "qwen3-coder-plus" }, { vendor: "openai", model: "gpt-5.3-codex" }, { vendor: "kwaipilot", model: "kat-coder-pro-v2.5" }, ], }, { slug: "backend-data", title: "Backend and data", role: "backend-data-implementer", domain: "Implements domain entities, SQL queries/migrations, ORM persistence, concurrency primitives, and transactional boundaries.", candidates: [ { vendor: "x-ai", model: "grok-4.6" }, { vendor: "qwen", model: "qwen3-coder-plus" }, { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, { vendor: "openai", model: "gpt-5.3-codex" }, { vendor: "kwaipilot", model: "kat-coder-pro-v2.5" }, ], }, { slug: "frontend", title: "Frontend", role: "frontend-fullstack-implementer", domain: "Constructs stateful UI components, routing, client cache management, responsive styles, and accessible interactions.", candidates: [ { vendor: "anthropic", model: "claude-sonnet-5" }, { vendor: "google", model: "gemini-3.8-flash" }, { vendor: "x-ai", model: "grok-4.6" }, { vendor: "bytedance-seed", model: "seed-2.0-code" }, { vendor: "openai", model: "gpt-5.3-codex" }, ], }, { slug: "sdk-integration", title: "SDK integration", role: "sdk-integration-specialist", domain: "Wires external APIs, authentication flows (OAuth2/OIDC), webhook validation, cloud SDKs, and async worker queues.", candidates: [ { vendor: "openai", model: "gpt-5.6-sol" }, { vendor: "x-ai", model: "grok-4.6" }, { vendor: "qwen", model: "qwen3-coder-plus" }, { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, { vendor: "google", model: "gemini-3.8-flash" }, ], }, ], }, { slug: "refactor-repair-regressions", area: "software-engineering", name: "Refactor and Repair Regressions", summary: "Codebase smell analysis → modular decomposition → surgical multi-file transforms → equivalence verification", stages: [ { slug: "refactor-architecture", title: "Refactor architecture", role: "refactoring-architect", domain: "Analyzes code coupling, designs clean modular boundaries, and authors step-by-step non-breaking migration plans.", candidates: [ { vendor: "anthropic", model: "claude-fable-5.1" }, { vendor: "z-ai", model: "glm-5.3" }, { vendor: "anthropic", model: "claude-opus-5" }, { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, { vendor: "moonshotai", model: "kimi-k3" }, ], }, { slug: "code-transform", title: "Code transform", role: "code-transform-implementer", domain: "Executes surgical multi-file edits, applying design patterns while preserving comments and formatting.", candidates: [ { vendor: "x-ai", model: "grok-4.6" }, { vendor: "relace", model: "relace-apply-3" }, { vendor: "openai", model: "gpt-5.3-codex" }, { vendor: "morph", model: "morph-v3-large" }, { vendor: "anthropic", model: "claude-sonnet-5" }, ], }, { slug: "equivalence-verification", title: "Equivalence verification", role: "semantic-equivalence-verifier", domain: "Critic-with-gate: reviews public contract compliance and behavioral equivalence; deterministic checks remain the fixed witness gate and are never delegated to an LLM.", candidates: [ { vendor: "openai", model: "gpt-5.6-sol" }, { vendor: "openai", model: "o3-mini-high" }, { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, { vendor: "anthropic", model: "claude-opus-5" }, { vendor: "mistralai", model: "devstral-2512" }, ], }, ], }, { slug: "stabilize-flaky-tests", area: "software-engineering", name: "Stabilize Flaky Tests", summary: "Flakiness root-cause extraction → deterministic mock/async hardening", stages: [ { slug: "flakiness-diagnosis", title: "Flakiness diagnosis", role: "concurrency-flakiness-detective", domain: "Untangles timing hazards, async wait conditions, shared state pollution, and unhandled promise rejections.", candidates: [ { vendor: "openai", model: "o3" }, { vendor: "deepseek", model: "deepseek-r1-0528" }, { vendor: "google", model: "gemini-3.8-flash" }, { vendor: "anthropic", model: "claude-opus-5" }, { vendor: "openai", model: "gpt-6-astra" }, ], }, ], }, { slug: "design-software-system", area: "software-engineering", name: "Design Software System", summary: "Architecture → database topology → threat modeling → IaC → reliability review → protocol audit", stages: [ { slug: "distributed-architecture", title: "Distributed architecture", role: "principal-distributed-architect", domain: "Evaluates CAP/PACELC trade-offs, consensus protocols (Raft, Sagas), partition boundaries, and authors primary RFCs.", candidates: [ { vendor: "anthropic", model: "claude-fable-5.1" }, { vendor: "openai", model: "o3" }, { vendor: "openai", model: "gpt-6-astra-pro" }, { vendor: "anthropic", model: "claude-opus-5" }, { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, ], }, { slug: "database-topology", title: "Database topology", role: "scalability-database-topologist", domain: "Designs relational/NoSQL schemas, horizontal sharding keys, caching topologies, and zero-downtime migration plans.", candidates: [ { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, { vendor: "openai", model: "gpt-6-astra-pro" }, { vendor: "anthropic", model: "claude-fable-5.1" }, { vendor: "qwen", model: "qwen3.8-2.4t-a95b" }, { vendor: "x-ai", model: "grok-4.6" }, ], }, { slug: "threat-modeling", title: "Threat modeling", role: "security-architect-threat-modeler", domain: "Conducts STRIDE threat assessments, reviews IAM least-privilege policies, mTLS boundaries, and zero-trust architectures.", candidates: [ { vendor: "anthropic", model: "claude-fable-5.1" }, { vendor: "openai", model: "gpt-5.6-sol" }, { vendor: "openai", model: "gpt-6-astra-pro" }, { vendor: "anthropic", model: "claude-opus-5" }, { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, ], }, { slug: "iac-topology", title: "IaC topology", role: "iac-cloud-topology-engineer", domain: "Generates declarative Terraform/OpenTofu, Pulumi, Kubernetes CRDs, and VPC networking definitions.", candidates: [ { vendor: "x-ai", model: "grok-4.6" }, { vendor: "qwen", model: "qwen3-coder-plus" }, { vendor: "openai", model: "gpt-5.3-codex" }, { vendor: "google", model: "gemini-3.8-flash" }, { vendor: "mistralai", model: "devstral-2512" }, ], }, { slug: "reliability-review", title: "Reliability review", role: "reliability-chaos-reviewer", domain: "Evaluates single points of failure, RPO/RTO metrics, cascading timeouts, circuit breakers, and active-active failovers.", candidates: [ { vendor: "anthropic", model: "claude-fable-5.1" }, { vendor: "openai", model: "o3" }, { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, { vendor: "openai", model: "gpt-6-astra-pro" }, { vendor: "moonshotai", model: "kimi-k3" }, ], }, { slug: "protocol-audit", title: "Protocol audit", role: "architecture-critic-protocol-auditor", domain: "Independent peer-review; challenges unvalidated assumptions, prevents architectural drift, and verifies contracts.", candidates: [ { vendor: "anthropic", model: "claude-fable-5.1" }, { vendor: "openai", model: "gpt-5.6-sol" }, { vendor: "z-ai", model: "glm-5.3" }, { vendor: "openai", model: "gpt-6-astra-pro" }, { vendor: "qwen", model: "qwen3.8-max-0902" }, ], }, ], }, { slug: "maintain-documentation", area: "software-engineering", name: "Maintain Documentation", summary: "Accuracy audit → documentation write → review", stages: [ { slug: "accuracy-audit", title: "Accuracy audit", role: "documentation-accuracy-auditor", domain: "Read-only audit of developer and operator documentation against CLI --help outputs, source environment variables, tool schemas, and repository configuration.", candidates: [ { vendor: "anthropic", model: "claude-fable-5.1" }, { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, { vendor: "openai", model: "gpt-5.6-sol" }, { vendor: "qwen", model: "qwen3.8-max-0902" }, { vendor: "z-ai", model: "glm-5.3" }, ], }, { slug: "documentation-write", title: "Documentation write", role: "documentation-writer", domain: "Authors concise, accurate developer and operator documentation with clear terminology, correct command flags, and consistent formatting without marketing drift.", candidates: [ { vendor: "x-ai", model: "grok-4.6" }, { vendor: "qwen", model: "qwen3-coder-plus" }, { vendor: "anthropic", model: "claude-sonnet-5" }, { vendor: "openai", model: "gpt-5.3-codex" }, { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, ], }, { slug: "review", title: "Review", role: "documentation-reviewer", domain: "Critic-with-gate: the architecture critic verifies authority and permission wording; the CLI critic verifies command/flag accuracy; deterministic docs lint remains the fixed witness gate.", candidates: [ { vendor: "anthropic", model: "claude-fable-5.1" }, { vendor: "openai", model: "gpt-5.6-sol" }, { vendor: "anthropic", model: "claude-opus-5" }, { vendor: "deepseek", model: "deepseek-v4-pro-0813" }, { vendor: "mistralai", model: "devstral-2512" }, ], }, ], }, ]; export interface AgentBinding { readonly harness: string; readonly provider: string; readonly model: string; } export interface SkippedStage { readonly workflow: string; readonly stage: string; readonly role: string; readonly reason: string; } export interface GuideSetupPlan { readonly agents: ReadonlyMap<string, AgentBinding>; readonly workflows: GuideWorkflow[]; readonly skipped: SkippedStage[]; } export interface GuideSetupFile { readonly path: string; readonly content: string; } function authenticatedHarnesses(inventory: HarnessInventory): ReadonlySet<string> { return new Set( inventory.harnesses .filter((entry) => entry.detected && entry.authenticated === true) .map((entry) => entry.id), ); } /** * Resolve a guide candidate to an admitted dispatch binding, or undefined when * no candidate is both on the admitted map and authenticated on its harness. */ export function resolveCandidate( candidates: readonly GuideCandidate[], eligible: ReadonlySet<string>, ): AgentBinding | undefined { for (const candidate of candidates) { const mapped = ADMITTED_GUIDE_BINDINGS[`${candidate.vendor}/${candidate.model}`]; if (!mapped || !eligible.has(mapped.harness)) continue; return mapped; } return undefined; } /** Plan agents/workflows for the selected guide slugs without touching disk. */ export function planGuideSetup(options: { inventory: HarnessInventory; selected: readonly string[]; }): GuideSetupPlan { const eligible = authenticatedHarnesses(options.inventory); const agents = new Map<string, AgentBinding>(); const workflows: GuideWorkflow[] = []; const skipped: SkippedStage[] = []; const wanted = new Set(options.selected); for (const workflow of GUIDE_WORKFLOWS) { if (!wanted.has(workflow.slug)) continue; let covered = true; for (const stage of workflow.stages) { if (agents.has(stage.role)) continue; const binding = resolveCandidate(stage.candidates, eligible); if (!binding) { skipped.push({ workflow: workflow.slug, stage: stage.slug, role: stage.role, reason: "no admitted guide candidate has an authenticated harness (see `kxm harness list`)", }); covered = false; continue; } agents.set(stage.role, binding); } if (covered) workflows.push(workflow); else skipped.push({ workflow: workflow.slug, stage: "*", role: "-", reason: "workflow skipped: at least one stage has no eligible agent" }); } return { agents, workflows, skipped }; } const WRITER_ROLE_PATTERN = /(writer|implementer|scaffold|backend|frontend|sdk-integration|iac-cloud|code-transform|build-specialist)/; function isWriterRole(roleSlug: string): boolean { return WRITER_ROLE_PATTERN.test(roleSlug); } function agentDocument(roleSlug: string, stage: GuideStage): Record<string, unknown> { const writer = isWriterRole(roleSlug); return { schema: "kxm.agent.v1", purpose: `${stage.domain} (workflow-guide ${stage.title}; candidate verification is the operator's responsibility)`, role: roleSlug, tools: { preset: writer ? "workspace-writer" : "read-only" }, defaultRepositoryAccess: writer ? "none" : "read", repositories: { control: writer ? "write" : "read" }, network: "provider-only", resultSchema: "kxm.assignment-result.v1", }; } function workflowDocument(workflow: GuideWorkflow): Record<string, unknown> { const steps = workflow.stages.map((stage, index) => { const last = index === workflow.stages.length - 1; return { id: stage.slug, kind: "agent", agent: stage.role, repositories: { control: isWriterRole(stage.role) ? "write" : "read" }, maxAttempts: 2, on: { passed: last ? { target: "$terminal", terminalStatus: "completed" } : workflow.stages[index + 1]!.slug, failed: { target: "$terminal", terminalStatus: "failed" }, }, }; }); return { schema: "kxm.workflow.v1", description: `${workflow.name} (${workflow.area}, workflow-guide): ${workflow.summary}`, coordinator: "coordinator", limits: { maxTransitions: Math.max(4, workflow.stages.length * 4) }, steps, }; } /** * Evidence written into the user's project for the one admitted guided Pi model. * The hash is of these exact bytes. A Pi binding that is not this vendor, model * id, and harness gets no model file and no origin. */ const GUIDE_PI_EVIDENCE_SOURCE = "plans/evidence/route-guide-qwen-pi.md"; const GUIDE_PI_EVIDENCE_SHA256 = "109f39728b251dd0565e275ae689a6e1d9b889a488d996b157d500c538115759"; const GUIDE_PI_NOTE = { harness: "pi", vendor: "openrouter", model: "qwen/qwen3-coder-plus", reason: "Qwen has no supported native harness on this runner, and the operator admitted that exact model for guided setup.", provingCommand: "pi auth check --provider openrouter", } as const; const GUIDE_PI_EVIDENCE_NOTE = `--- schema: "kxm.doc.v1" id: "RES-GUIDE-QWEN-PI" type: "research" title: "Research: guided setup Pi Qwen origin" project: "kxm" status: "approved" owner: "@operator" created: "2026-09-26" updated: "2026-09-26" authority: "evidence" confidence: "verified" summary: "Guided setup pins every Pi model origin to this note so a later project.yaml edit cannot change the hash." tags: ["routing", "qwen", "pi", "init-guide"] related: [] details: research_status: "complete" target_decision_date: "2026-09-26" --- # route-guide-qwen-pi Dated 2026-09-26. Route ids are the guided role slugs. Harness \`${GUIDE_PI_NOTE.harness}\`. Model id \`${GUIDE_PI_NOTE.model}\`. Vendor \`${GUIDE_PI_NOTE.vendor}\`. Admitted because ${GUIDE_PI_NOTE.reason} Auth was proved with \`${GUIDE_PI_NOTE.provingCommand}\`. `; function guidePiNoteMatches(binding: AgentBinding): boolean { return binding.harness === GUIDE_PI_NOTE.harness && binding.provider === GUIDE_PI_NOTE.vendor && binding.model === GUIDE_PI_NOTE.model; } /** * Render the planned KXM resource files (`.kxm/agents/*.yaml`, * `.kxm/workflows/*.yaml`, and the Pi evidence note when a binding matches it). * The Pi origin is the hash of `plans/evidence/route-guide-qwen-pi.md` written * into the project, whether or not `.kxm/project.yaml` exists. */ export function renderGuideSetupFiles(projectRoot: string, plan: GuideSetupPlan): GuideSetupFile[] { const files: GuideSetupFile[] = []; const roleStages = new Map<string, GuideStage>(); for (const workflow of plan.workflows) { for (const stage of workflow.stages) roleStages.set(stage.role, stage); } let evidenceQueued = false; for (const [role, binding] of [...plan.agents.entries()].sort(([a], [b]) => a.localeCompare(b))) { const stage = roleStages.get(role); if (!stage) continue; const routeId = role.replaceAll("_", "-"); const writer = isWriterRole(role); const piMatch = binding.harness === "pi" && guidePiNoteMatches(binding); if (binding.harness === "pi" && !piMatch) { files.push({ path: join(projectRoot, ".kxm", "agents", `${role}.yaml`), content: stringify(agentDocument(role, stage)), }); continue; } const modelDocument: Record<string, unknown> = { schema: "kxm.model.v2", id: routeId, harness: binding.harness, model: binding.model, vendor: binding.provider, status: "admitted", permissions: [writer ? "edit" : "read-only"], }; if (piMatch) { modelDocument.origin = { source: GUIDE_PI_EVIDENCE_SOURCE, sha256: GUIDE_PI_EVIDENCE_SHA256 }; if (!evidenceQueued) { files.push({ path: join(projectRoot, GUIDE_PI_EVIDENCE_SOURCE), content: GUIDE_PI_EVIDENCE_NOTE, }); evidenceQueued = true; } } files.push({ path: join(projectRoot, ".kxm", "agents", `${role}.yaml`), content: stringify(agentDocument(role, stage)), }); files.push({ path: join(projectRoot, ".kxm", "models", `${routeId}.yaml`), content: stringify(modelDocument), }); files.push({ path: join(projectRoot, ".kxm", "roles", `${role}.yaml`), content: stringify({ schema: "kxm.role.v2", id: role, purpose: writer ? "writer" : "experiment", permission: writer ? "edit" : "read-only", description: stage.domain.slice(0, 2000), roster: [{ route: routeId }], }), }); } for (const workflow of plan.workflows) { files.push({ path: join(projectRoot, ".kxm", "workflows", `${workflow.slug}.yaml`), content: stringify(workflowDocument(workflow)), }); } return files; } /** Append the plan's admitted selectors to `.kxm/routes.yaml`. Does not write role files. */ export function mergeGuideRouteAdmission(projectRoot: string, plan: GuideSetupPlan): string[] { const policy = loadRoutePolicy(projectRoot); const added: string[] = []; for (const binding of plan.agents.values()) { const selector = `${binding.provider}/${binding.model}`; if (policy.disabled.includes(selector) || policy.admitted.includes(selector)) continue; policy.admitted.push(selector); added.push(selector); } if (added.length === 0) return added; policy.updatedAt = new Date().toISOString(); const path = join(projectRoot, ".kxm", "routes.yaml"); mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, stringify(policy), "utf8"); return added; } export interface WriteReport { readonly written: string[]; readonly existed: string[]; } /** Write rendered files, creating directories as needed; never overwrites. */ export function writeGuideSetupFiles(files: readonly GuideSetupFile[]): WriteReport { const written: string[] = []; const existed: string[] = []; for (const file of files) { if (existsSync(file.path)) { existed.push(file.path); continue; } mkdirSync(dirname(file.path), { recursive: true }); writeFileSync(file.path, file.content, "utf8"); written.push(file.path); } return { written, existed }; } /** Parse a prompt answer like "1,3", "build-feature, all", or "none". */ export function parseGuideSelection(input: string, catalog: readonly GuideWorkflow[] = GUIDE_WORKFLOWS): string[] { const trimmed = input.trim().toLowerCase(); if (!trimmed || trimmed === "none" || trimmed === "n" || trimmed === "no") return []; if (trimmed === "all") return catalog.map((workflow) => workflow.slug); const selected = new Set<string>(); for (const token of trimmed.split(/[,\s]+/).filter(Boolean)) { const index = Number.parseInt(token, 10); if (Number.isFinite(index) && index >= 1 && index <= catalog.length) { selected.add(catalog[index - 1]!.slug); continue; } const match = catalog.find((workflow) => workflow.slug === token); if (match) selected.add(match.slug); } return [...selected]; }