/** * router-parallel-recorder.ts — Opt-in SelfEvolvingRouter parallel-logging * recorder (ADR-150 Phase 2). * * Writes one JSON-line per PAIR of routing decisions (Thompson bandit pick * + SelfEvolvingRouter pick + measured outcome) to a shared * `.swarm/router-parallel.jsonl`. Designed to be matched by * `plugins/ruflo-metaharness/scripts/router-parallel-analyze.mjs` which * computes the 3-criteria AND-gate from ADR-150 review-round-1: * * (a) qualityScore improvement > 2% * (b) usdPerDecision increase < 1% * (c) p95 routing-decision latency increase < 5% * * ARCHITECTURAL CONSTRAINTS (ADR-150) * ----------------------------------- * 1. REMOVABLE — `@metaharness/kernel` (which provides SelfEvolvingRouter) * is in optionalDependencies. This module's exports are pure-shape; the * `SerPick` carries pre-computed prediction fields. The caller is * responsible for dynamic-importing the kernel and computing the SER * pick — this module ONLY records the pair. * 2. OPTIONAL — every write goes through the `CLAUDE_FLOW_ROUTER_PARALLEL_LOG=1` * env gate. When unset (default), `recordPair()` is a no-op. * 3. GRACEFUL DEGRADATION — every fs operation is try/caught at the * appendFileSync boundary. A failed write logs to stderr (gated by * DEBUG) but never throws — the routing decision continues. * 4. CI GATE — the analyzer + recorder are both exercised by smoke * fixtures; no production data required for the contract to hold. * * SCHEMA (versioned, additive) * ---------------------------- * One JSONL row per paired decision: * { * v: 1, * ts: ISO-8601, * task_hash: , * task?: , * bandit: { pick, predictedQuality, predictedCostUsd, ... }, * ser: { pick, predictedQuality, predictedCostUsd, ... }, * outcome?: { actualModel, actualQuality, actualUsd, actualLatencyMs } * } * * Outcomes can be filled in a separate `recordPairOutcome()` call (matched * by task_hash) so the routing path doesn't have to wait for execution. * * @module router-parallel-recorder */ import type { ClaudeModel } from './model-router.js'; /** Per-arm prediction for one routing decision. */ export interface ArmPick { pick: ClaudeModel; predictedQuality: number; predictedCostUsd: number; /** Optional: backend identifier (bandit | metaharness-knn | metaharness-krr | self-evolving). */ backend?: string; } /** Optional outcome — filled in after execution via recordPairOutcome(). */ export interface PairOutcome { /** Whichever model actually executed (typically the bandit's pick). */ actualModel: ClaudeModel; /** Measured quality score from the verdict ladder (0..1). */ actualQuality: number; /** Measured spend at actualModel's prices. */ actualUsd: number; /** Wall-clock latency from route() to response complete (ms). */ actualLatencyMs: number; } /** Discriminator for JSONL rows. */ export type RowType = 'pair' | 'pair-outcome'; /** One paired-decision row. */ export interface PairRow { v: 1; type: RowType; ts: string; task_hash: string; task?: string; bandit: ArmPick; ser: ArmPick; outcome?: PairOutcome; } /** * Record one paired routing decision. Cheap — single appendFileSync of a * JSONL row. No-op when CLAUDE_FLOW_ROUTER_PARALLEL_LOG is unset (the * default). Never throws. * * The caller dynamic-imports `@metaharness/kernel` (or any alternative * predictor) and computes the SER pick BEFORE calling this. The recorder * deliberately knows nothing about how either pick was computed — it just * writes the pair. */ export declare function recordPair(args: { task: string; bandit: ArmPick; ser: ArmPick; }): { recorded: boolean; taskHash: string; }; /** * Record the outcome of a previously-paired decision. Matched to its * pair row by task_hash. Writes a separate JSONL row of type 'outcome' * which the analyzer joins client-side (same pattern as the existing * router-trajectory recorder). * * Returns the task_hash that was matched (caller can verify against * what recordPair() returned). */ export declare function recordPairOutcome(args: { task: string; outcome: PairOutcome; }): { recorded: boolean; taskHash: string; }; /** * Status helper for diagnostics / smoke tests. */ export declare function parallelRecorderStatus(): { enabled: boolean; path: string; includeTaskText: boolean; taskCharLimit: number; maxBytes: number; }; /** @internal — test helper. */ export declare function __resetParallelRecorderForTests(): void; //# sourceMappingURL=router-parallel-recorder.d.ts.map