@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
324 lines (300 loc) • 13 kB
text/typescript
import { existsSync, lstatSync } from "node:fs";
import { join, resolve } from "node:path";
import { DatabaseSync, openReadOnlyDatabase } from "./sqlite.ts";
import { discoverKxmProjectRoot } from "./project-config.ts";
import { kxmRuntimePaths, projectRuntimeKey } from "./runtime-paths.ts";
import { parseRouteSwitchRecord, type RouteSwitchRecord } from "./route-switch.ts";
import { parseRoutingRecordV2, ROUTING_RECORD_V2_SCHEMA, type RoutingRecord, type RoutingRecordV2 } from "./routing.ts";
import { readRoutingRecords, telemetryPath } from "./telemetry.ts";
/**
* Routing-record sources for `kxm improve` and `kxm routing report`.
*
* Each Runtime event store is read read-only: one SELECT over the events
* table, never the runs table, the run plans, or the prompts sidecar, and
* never a write or a migration. When the user-state registry is present and
* this checkout is registered, the home store and every lane store for that
* project id are read. The registry itself is opened read-only at schema
* version 2: no constructor, migration, write lock, directory, or sidecar.
* Each settled attempt's outcome is resolved in memory from the event log;
* nothing resolved here is stored.
*/
export interface RoutingSourceSummary {
kind: "engine" | "telemetry" | "file";
path: string;
exists: boolean;
/** Records this source contributed before cross-source de-duplication. */
records: number;
skippedInvalid?: number;
excludedSimulated?: number;
undecided?: number;
duplicatesDropped?: number;
}
export interface EngineRoutingRead {
records: RoutingRecordV2[];
source: RoutingSourceSummary;
routeSwitches: RouteSwitchRecord[];
}
export interface LoadedRoutingSources {
projectRoot?: string;
records: Array<RoutingRecord | RoutingRecordV2>;
sources: RoutingSourceSummary[];
routeSwitches: RouteSwitchRecord[];
}
const ENGINE_EVENTS_SQL = "SELECT run_id, sequence, event_type, payload FROM events"
+ " WHERE event_type IN ('routing.attempt.recorded','routing.route_switched','step.entered','run.status_changed')"
+ " ORDER BY run_id, sequence";
/** Home row first, then lanes by registration time and project key. One query. */
const REGISTRY_SIBLING_SQL = "SELECT sibling.project_key AS project_key, sibling.lane_of AS lane_of"
+ " FROM projects AS current"
+ " JOIN projects AS sibling ON sibling.project_id = current.project_id"
+ " WHERE current.project_key = ?"
+ " ORDER BY CASE WHEN sibling.lane_of IS NULL THEN 0 ELSE 1 END, sibling.registered_at, sibling.project_key";
const PROJECT_KEY = /^[0-9a-f]{24}$/;
/** The simulated producer's harness label; its attempts measure nothing. */
const SIMULATED_HARNESS = "driver-simulated";
interface RunLog {
lastStatus?: string;
/** Latest step.entered sequence per step. */
lastEntered: Map<string, number>;
routing: Array<{ sequence: number; payload: string }>;
}
function parsePayload(text: string): Record<string, unknown> | undefined {
try {
const value = JSON.parse(text) as unknown;
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
} catch {
return undefined;
}
}
/**
* Read every Runtime-settled routing record from one project's event store.
*
* Outcomes resolve per record, first rule that applies: a record-time
* 'blocked' or 'failed' stays; a later step.entered for the same step is
* 'reworked'; a completed run is 'accepted'; a failed run is 'failed';
* anything else (cancelled, still running) is left unset and counted
* undecided. Simulated attempts are dropped and counted.
*/
export function readEngineRoutingRecords(path: string): EngineRoutingRead {
const source: RoutingSourceSummary = {
kind: "engine",
path,
exists: false,
records: 0,
skippedInvalid: 0,
excludedSimulated: 0,
undecided: 0,
duplicatesDropped: 0,
};
if (!existsSync(path)) return { records: [], source, routeSwitches: [] };
source.exists = true;
let rows: Array<Record<string, unknown>>;
let database: DatabaseSync | undefined;
try {
database = new DatabaseSync(path, { readOnly: true });
database.exec("PRAGMA busy_timeout = 5000");
rows = database.prepare(ENGINE_EVENTS_SQL).all();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`improve_source_unreadable: ${path}: ${message}`, { cause: error });
} finally {
try {
database?.close();
} catch {
// Closing a read-only handle has nothing to flush.
}
}
const runs = new Map<string, RunLog>();
const routeSwitches: RouteSwitchRecord[] = [];
for (const row of rows) {
const runId = String(row.run_id);
const sequence = Number(row.sequence);
const eventType = String(row.event_type);
const payload = typeof row.payload === "string" ? row.payload : "";
let run = runs.get(runId);
if (!run) {
run = { lastEntered: new Map(), routing: [] };
runs.set(runId, run);
}
if (eventType === "routing.attempt.recorded") {
run.routing.push({ sequence, payload });
} else if (eventType === "routing.route_switched") {
const parsed = parsePayload(payload);
const record = parseRouteSwitchRecord(parsed, {
attemptId: typeof parsed?.attemptId === "string" ? parsed.attemptId : undefined,
stepId: typeof parsed?.stepId === "string" ? parsed.stepId : undefined,
runId,
});
if (record) routeSwitches.push(record);
} else if (eventType === "step.entered") {
const stepId = parsePayload(payload)?.stepId;
if (typeof stepId === "string") run.lastEntered.set(stepId, sequence);
} else if (eventType === "run.status_changed") {
const status = parsePayload(payload)?.status;
if (typeof status === "string") run.lastStatus = status;
}
}
let skippedInvalid = 0;
let excludedSimulated = 0;
let undecided = 0;
const records: RoutingRecordV2[] = [];
for (const run of runs.values()) {
for (const entry of run.routing) {
let record: RoutingRecordV2;
try {
record = parseRoutingRecordV2(parsePayload(entry.payload)?.routing);
} catch {
skippedInvalid += 1;
continue;
}
if (record.harness === SIMULATED_HARNESS) {
excludedSimulated += 1;
continue;
}
const recorded = record.finalOutcome;
if (recorded === "blocked" || recorded === "failed") {
// A gate-negative verdict known at settle time stands.
} else if ((run.lastEntered.get(record.stepId) ?? -1) > entry.sequence) {
record.finalOutcome = "reworked";
} else if (run.lastStatus === "completed") {
record.finalOutcome = "accepted";
} else if (run.lastStatus === "failed") {
record.finalOutcome = "failed";
} else {
delete record.finalOutcome;
undecided += 1;
}
records.push(record);
}
}
return {
records,
routeSwitches,
source: { ...source, records: records.length, skippedInvalid, excludedSimulated, undecided },
};
}
function registryUnreadable(registryDb: string, detail: string): Error {
return new Error(`improve_source_unreadable: ${registryDb}: ${detail}`);
}
/**
* Event stores for this checkout.
*
* An absent registry, or a registry that has no row for this checkout's
* project key, keeps the single store derived from the checkout path. A
* present registry must be a regular schema-v2 database. Paths then come
* from the stored keys and the Runtime projects directory, so a stale
* checkout path in the registry cannot choose the store.
*/
function discoverProjectEventStores(projectRoot: string, env: NodeJS.ProcessEnv): string[] {
const paths = kxmRuntimePaths({ env });
const ownStore = join(paths.projectsDir, projectRuntimeKey(projectRoot), "run-events.db");
const registryDb = paths.registryDb;
const info = lstatSync(registryDb, { throwIfNoEntry: false });
if (!info) return [ownStore];
if (!info.isFile()) throw registryUnreadable(registryDb, "runtime registry must be a regular database file");
for (const suffix of ["-wal", "-shm"]) {
const sidecar = lstatSync(`${registryDb}${suffix}`, { throwIfNoEntry: false });
if (sidecar?.isSymbolicLink()) throw registryUnreadable(registryDb, "runtime registry sidecar must not be a link");
}
let database: DatabaseSync | undefined;
try {
database = openReadOnlyDatabase(registryDb);
database.exec("PRAGMA busy_timeout = 5000");
const versionRow = database.prepare("PRAGMA user_version").get() as { user_version?: number } | undefined;
const version = versionRow?.user_version ?? 0;
if (version !== 2) throw registryUnreadable(registryDb, `registry schema version ${version}; this reader requires 2`);
const rows = database.prepare(REGISTRY_SIBLING_SQL).all(projectRuntimeKey(projectRoot));
if (rows.length === 0) return [ownStore];
return eventStoresFromRegistryRows(registryDb, paths.projectsDir, rows);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (message.startsWith("improve_source_unreadable:")) throw error;
throw new Error(`improve_source_unreadable: ${registryDb}: ${message}`, { cause: error });
} finally {
try {
database?.close();
} catch {
// Closing a read-only handle has nothing to flush.
}
}
}
function eventStoresFromRegistryRows(registryDb: string, projectsDir: string, rows: Array<Record<string, unknown>>): string[] {
const seen = new Set<string>();
const parsed: Array<{ projectKey: string; laneOf: string | null }> = [];
for (const row of rows) {
const projectKey = typeof row.project_key === "string" ? row.project_key : "";
const laneRaw = row.lane_of;
const laneOf = laneRaw === null || laneRaw === undefined ? null : typeof laneRaw === "string" ? laneRaw : "";
if (!PROJECT_KEY.test(projectKey) || seen.has(projectKey) || (laneOf !== null && !PROJECT_KEY.test(laneOf))) {
throw registryUnreadable(registryDb, "registry identity is corrupt");
}
seen.add(projectKey);
parsed.push({ projectKey, laneOf });
}
const homes = parsed.filter((row) => row.laneOf === null);
const homeKey = homes[0]?.projectKey;
if (homes.length !== 1 || homeKey === undefined || parsed.some((row) => row.laneOf !== null && row.laneOf !== homeKey)) {
throw registryUnreadable(registryDb, "registry identity is corrupt");
}
return parsed.map((row) => join(projectsDir, row.projectKey, "run-events.db"));
}
function readJsonlSource(kind: "telemetry" | "file", path: string): { records: Array<RoutingRecord | RoutingRecordV2>; source: RoutingSourceSummary } {
const exists = existsSync(path);
const records = exists ? readRoutingRecords(path).map((entry) => entry.routing) : [];
return { records, source: { kind, path, exists, records: records.length, duplicatesDropped: 0 } };
}
function attemptKey(record: RoutingRecord | RoutingRecordV2): string | undefined {
if (record.schema !== ROUTING_RECORD_V2_SCHEMA) return undefined;
const attemptId = typeof record.attemptId === "string" ? record.attemptId.trim() : "";
return attemptId.length > 0 ? attemptId : undefined;
}
/**
* Load routing records for improvement and routing reports.
*
* With `file`, only that file is read. Otherwise every discovered Runtime
* event store (when cwd is inside a KXM project) comes first, then
* telemetry.jsonl. v2 records are de-duplicated by attemptId: the first
* occurrence wins and the later source counts the drop.
*/
export function loadRoutingSources(options: {
cwd: string;
env: NodeJS.ProcessEnv;
logsDir: string;
file?: string | undefined;
}): LoadedRoutingSources {
const projectRoot = discoverKxmProjectRoot(options.cwd);
const reads: Array<{ records: Array<RoutingRecord | RoutingRecordV2>; source: RoutingSourceSummary; routeSwitches?: RouteSwitchRecord[] }> = [];
if (options.file !== undefined) {
reads.push(readJsonlSource("file", resolve(options.cwd, options.file)));
} else {
if (projectRoot !== undefined) {
for (const path of discoverProjectEventStores(projectRoot, options.env)) {
reads.push(readEngineRoutingRecords(path));
}
}
reads.push(readJsonlSource("telemetry", telemetryPath(options.logsDir)));
}
const seen = new Set<string>();
const records: Array<RoutingRecord | RoutingRecordV2> = [];
for (const read of reads) {
let dropped = 0;
for (const record of read.records) {
const key = attemptKey(record);
if (key !== undefined) {
if (seen.has(key)) {
dropped += 1;
continue;
}
seen.add(key);
}
records.push(record);
}
read.source.duplicatesDropped = dropped;
}
return {
...(projectRoot !== undefined ? { projectRoot } : {}),
records,
sources: reads.map((read) => read.source),
routeSwitches: reads.flatMap((read) => read.routeSwitches ?? []),
};
}