/** * BrainTraceRecorder — the replayable record of HOW a Brain decision was made. * * `BrainDecisionLedger` answers "what did the Brain decide, and did it work * out" — it is a learning loop with a bounded ring and outcome correlation. * This module answers a different question: "what actually happened inside * the ladder", at a granularity that lets a decision be reconstructed and * replayed offline through `runBrainEvaluation`. * * The two are deliberately SEPARATE files. Trace rows are per-call and * high-volume; folding them into the ledger's ring would evict the decision * history that `digestFor()` / `failureStreakFor()` scan, quietly breaking * the learning loop. * * Recorded per decision: * - `brain.tier_transition` — every tier the ladder ran, what it returned, * and why the chain did or did not stop there. * - `brain.llm_call` — every pool target attempted, including the failures * the fallback loop swallows, with model, timing and usage. * - `brain.council_vote` / `brain.council_resolved` — each seat's observable * vote plus the deterministic quorum/veto/majority resolution. * - the request and final decision, which together form the replay fixture. * * ## Content policy * Trace is DISABLED by default. Enabling it is the opt-in; `content: 'full'` * is then the default because a fixture without the question and context * cannot be replayed. `content: 'redacted'` keeps the shape and metadata but * truncates free text, and `'none'` records metadata only. See * `docs/competitive-roadmap-2026-2027/21-brain-evaluation-and-replay.md`. * * @module brain-trace */ import type { EventBus } from '../kernel/events.js'; import type { BrainDecision, BrainDecisionRequest } from './brain.js'; import type { BrainDecisionTier } from './brain-telemetry.js'; /** How much free text a trace may record. */ export type BrainTraceContentMode = 'none' | 'redacted' | 'full'; /** Characters of free text kept per field in `redacted` mode. */ export declare const BRAIN_TRACE_REDACTED_MAX = 120; export interface BrainTraceLlmCall { tier: 'llm' | 'council' | 'judge'; providerId?: string | undefined; model: string; label?: string | undefined; attempt: number; ok: boolean; durationMs: number; error?: string | undefined; responseText?: string | undefined; usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number; } | undefined; at: number; } export interface BrainTraceCouncilVote { seatId: string; persona: string; status: 'valid' | 'invalid' | 'failed' | 'cancelled'; providerId?: string | undefined; model?: string | undefined; optionId?: string | undefined; stance?: string | undefined; rationale?: string | undefined; weight?: number | undefined; veto?: boolean | undefined; durationMs?: number | undefined; error?: string | undefined; at: number; } export interface BrainTraceCouncilResolution { status: string; resolution: string; optionId?: string | undefined; configuredSeatCount: number; validVoteCount: number; distinctTargetCount: number; judgeUsed: boolean; usage?: { calls: number; inputTokens: number; outputTokens: number; totalTokens: number; durationMs: number; } | undefined; /** Panel-integrity warnings (distinctness). Structural — never content-gated. */ warnings?: string[] | undefined; reason?: string | undefined; at: number; } export interface BrainTraceTierStep { tier: BrainDecisionTier; outcome: 'answer' | 'deny' | 'ask_human' | 'error' | 'skipped'; terminal: boolean; reason?: string | undefined; durationMs?: number | undefined; at: number; } export declare const BRAIN_TRACE_VERSION: 1; /** One complete decision, start to finish. Appended as a single JSONL row. */ export interface BrainTraceRecord { version: typeof BRAIN_TRACE_VERSION; requestId: string; sessionId?: string | undefined; startedAt: number; completedAt: number; durationMs: number; /** The request as issued. Free-text fields follow the content mode. */ request: BrainDecisionRequest; /** The decision the caller received. */ decision?: BrainDecision | undefined; /** Tier that produced `decision`, when provenance was recorded. */ tier?: BrainDecisionTier | undefined; steps: BrainTraceTierStep[]; llmCalls: BrainTraceLlmCall[]; councilVotes: BrainTraceCouncilVote[]; councilResolution?: BrainTraceCouncilResolution | undefined; /** Totals across every provider call this decision made. */ totals: { llmCalls: number; failedLlmCalls: number; inputTokens: number; outputTokens: number; totalTokens: number; }; } export interface BrainTraceRecorderOptions { events: EventBus; /** JSONL file trace records are appended to. */ filePath: string; /** Free-text policy. Default 'full' — see the module docs. */ content?: BrainTraceContentMode | undefined; /** * Cap on concurrently open (undecided) decisions. A decision whose * `brain.decision_*` event never arrives would otherwise leak its partial * record forever. Oldest entries are dropped past this. Default 200. */ maxOpenRecords?: number | undefined; } /** Apply the content policy to one free-text field. */ export declare function applyContentMode(value: string | undefined, mode: BrainTraceContentMode): string | undefined; /** * Strip a request down to what the content policy allows. Structure * (ids, risk, fallback, option ids) is always preserved: it is what makes a * fixture runnable, and it carries no free-text production content. */ export declare function sanitizeRequest(request: BrainDecisionRequest, mode: BrainTraceContentMode): BrainDecisionRequest; /** Strip a decision's free text under the content policy. */ export declare function sanitizeDecision(decision: BrainDecision, mode: BrainTraceContentMode): BrainDecision; /** * Correlates the Brain's per-call events into one record per decision and * appends it as JSONL when the decision resolves. */ export declare class BrainTraceRecorder { private readonly opts; private readonly open; private readonly unsubscribers; private writeChain; private pendingWriteCount; private pendingWriteBytes; private static readonly MAX_PENDING_WRITES; private static readonly MAX_PENDING_WRITE_BYTES; private dirReady; private readonly content; private readonly maxOpenRecords; constructor(opts: BrainTraceRecorderOptions); start(): void; /** Unsubscribe and drain pending writes. Safe to call twice. */ stop(): Promise; /** Decisions still awaiting their resolution event. */ get openCount(): number; private evictOverflow; private close; private append; } /** Read trace records back from a JSONL file, skipping corrupt rows. */ export declare function readBrainTrace(filePath: string): Promise; //# sourceMappingURL=brain-trace.d.ts.map