export type PromoteThreshold = 'execution-only' | 'execution+corroborated'; export interface TuningConfig { batchSize: number; dedupDistance: number; promoteThreshold: PromoteThreshold; } export interface ParamGrid { batchSize?: number[]; dedupDistance?: number[]; promoteThreshold?: PromoteThreshold[]; } export interface TuningCandidate { config: TuningConfig; /** MRR@10 on the inner train-query split (never the true held-out set). */ trainScore: number; trainRecallAt10: number; trainQueryCount: number; patternCount: number; promotedCount: number; distillMs: number; /** Present iff M1 itself skipped this run (corrupt DB, missing tables, ...). */ skipped?: string; } export interface HeldOutScore { mrrAt10: number; recallAt10: number; queryCount: number; /** Same held-out query set scored against raw (undistilled) train entries. */ baselineMrrAt10: number; baselineRecallAt10: number; } export interface TuningProvenance { gridSize: number; corpusSize: number; trainSize: number; heldOutSize: number; metric: 'mrr@10'; /** Stamped from `options.now` (or the orchestrator's own Date.now() at the * I/O boundary) — never read from inside pure scoring logic. */ tunedAt: number; sourceDbPath: string; sourceChecksumSha256: string; } export interface TuningReport { candidates: TuningCandidate[]; winner: TuningCandidate; heldOut: HeldOutScore; /** True when held-out MRR@10 is >20% worse (relative) than the winner's train score. */ overfit: boolean; provenance: TuningProvenance; } export interface TuneDistillationOptions { /** Source DB — read (copied + hashed) only; NEVER opened with a DB connection. */ dbPath: string; grid?: ParamGrid; /** Namespaces to distill / include in the raw baseline pool (default: all). */ namespaces?: string[]; /** Outer train/held-out split fraction (default 0.8). */ trainFraction?: number; /** Namespaces the query set is drawn from (default: feedback + commands). */ queryNamespaces?: string[]; topK?: number; /** Timestamp stamped into `provenance.tunedAt`. Caller-supplied so the core * logic stays a pure function of its inputs; defaults to `Date.now()` at * this I/O boundary if omitted. */ now?: number; tmpDir?: string; verbose?: boolean; } export interface TimeSplit { totalRows: number; trainBoundaryRowid: number; trainRowids: number[]; heldOutRowids: number[]; } export interface TunedConfigFile { batchSize: number; dedupDistance: number; promoteThreshold: PromoteThreshold; provenance: TuningProvenance & { winnerTrainScore: number; heldOutScore: number; baselineHeldOutScore: number; overfit: boolean; }; } export declare const DEFAULT_GRID_BATCH_SIZE: number[]; export declare const DEFAULT_GRID_DEDUP_DISTANCE: number[]; export declare const DEFAULT_GRID_PROMOTE_THRESHOLD: PromoteThreshold[]; /** * Grid-search the distillation config against isolated copies of `dbPath`, * scored on a held-out split. See module header for the full methodology. */ export declare function tuneDistillation(options: TuneDistillationOptions): Promise; /** Shape the winning config + provenance for persistence. Pure — no I/O. */ export declare function buildTunedConfigFile(report: TuningReport): TunedConfigFile; /** Write the winning config to disk. Caller decides whether/where to call this. */ export declare function writeTunedConfigFile(report: TuningReport, filePath: string): void; /** Where the tuned config lives by default, for daemon/CLI callers. */ export declare function defaultTunedConfigPath(cwd?: string): string; /** * Split a table's rows by rowid (a monotonic proxy for insertion time) into * an earliest `trainFraction` chunk and a most-recent remainder. Pure given a * fixed db snapshot. `table`/`extraWhere` are only ever called with internal * literal strings (never external input) — not a SQL-injection surface. */ export declare function computeTimeSplit(db: unknown, trainFraction: number, opts?: { table?: string; extraWhere?: string; }): TimeSplit; //# sourceMappingURL=distill-tuning.d.ts.map