// Trace records: the day's surfaced decisions + how the user reacted, // snapshotted with the fairy/taste/bias state that produced them. // This is the molt's training data — exported at Night Bloom, appended // to server/data/traces.jsonl by the daemon. import { sourceBias } from "./learning"; import type { FairyState, FeedItem, Profile, SurfaceOutcome, Taste } from "./types"; export interface TraceRecord { v: 1; ts: string; event: Pick & { channel: FeedItem["channel"] | null; }; /** the dials that shaped this decision, at export time */ fairy: Pick; taste: Taste; sourceBias: number; outcome: SurfaceOutcome | null; } /** Snapshot the day's feed as training traces. Pure — caller decides when/where to ship. */ export function buildTraces( feed: FeedItem[], fs: FairyState, taste: Taste, profile: Profile, now: Date = new Date(), ): TraceRecord[] { const ts = now.toISOString(); const fairy = { mischief: fs.mischief, protectiveness: fs.protectiveness, chattiness: fs.chattiness, curiosity: fs.curiosity, energy: fs.energy, }; return feed.map((e) => ({ v: 1 as const, ts, event: { id: e.id, source: e.source, say: e.say, tier: e.tier, decision: e.decision, channel: e.channel ?? null, }, fairy, taste, sourceBias: sourceBias(profile, e.source), outcome: e.outcome ?? null, })); }