/** * mega-trim.ts — the LIVE compaction view builder (S16). * * Produces the message list returned from the `context` event so the model sees * a compacted window every LLM call WITHOUT aborting the turn (ctx.compact() * would abort; the context-event return feeds pi's transformContext per call). * * Shape: [compactSummaryMessage, ...recentAnchor]. The compacted region * [0, compactedFrom) is collapsed to a single user-role summary; the recent * anchor [compactedFrom, end) is kept verbatim. Honors PREVENT-PI-002 (never * splits a toolCall/toolResult pair) by snapping compactedFrom back to a * boundary-safe index, and PREVENT-PI-001 (anchor floor) via the anchor knob. * * Pure + pi-agnostic: takes EngineMessage[], returns EngineMessage[]. No pi * imports. Non-destructive: the caller still owns the real messages. */ import type { EngineMessage } from "../src/types.js"; import { isBoundarySafe } from "../src/boundary.js"; import { formatCompactSummary } from "../src/compact.js"; export interface BuildLiveTrimViewOpts { /** Index where the compacted region ends (the recent anchor starts here). */ compactedFrom: number; /** The compacted-region summary text (already generated by runCompact). */ summary: string; /** Min recent user messages to keep as the anchor (PREVENT-PI-001). */ anchorUserMessages: number; /** CRITICAL-OVER ESCAPE HATCH: when true, context is over ~90% of the window * and relief takes priority over the anchor floor. computeLiveTrimCut returns * the boundary-safe cut even if it keeps fewer than `anchorUserMessages` user * messages, rather than bailing to null (which would feed the model a raw * overflow). A compacted session with a thin anchor is recoverable; an * overflowed session that errors every turn is not. Default false. */ criticalOver?: boolean; } /** * Compute the safe cut index for the live trim. Snaps `compactedFrom` back to a * boundary-safe index (PREVENT-PI-002: never start the preserved run on an * orphaned tool result), and enforces the anchor floor (PREVENT-PI-001: keep at * least `anchorUserMessages` user-role messages). Returns `null` when no trim is * safe this call (empty summary, unsafe boundary, or below the anchor floor) so * the caller keeps the original view and retries on the next context event. * * Exposed separately from `buildLiveTrimmedView` so the context handler can map * the cut back onto the original pi `AgentMessage[]` (lossless index alignment, * mirroring `dropCompactedRange` in src/adapt.ts). */ export function computeLiveTrimCut(view: EngineMessage[], opts: BuildLiveTrimViewOpts): number | null { if (!opts.summary || !opts.summary.trim()) return null; let cut = opts.compactedFrom; while (cut > 0 && !isBoundarySafe(view, cut)) cut--; if (cut <= 0) return null; // nothing safe to cut — keep everything this call const recent = view.slice(cut); const userCount = recent.filter((m) => m.role === "user").length; // ANCHOR FLOOR (PREVENT-PI-001): the recent window must keep at least // `anchorUserMessages` user messages. The original compactedFrom can land on a // run that starts with fewer than that (e.g. the preserved region begins on a // tool pair, or the session's tail is tool-heavy). Instead of bailing out and // skipping the live trim entirely this call (which left the model fed a // 150k-context window during long team runs), walk `cut` backward until the // preserved run contains enough user messages — bounded by the boundary-safe // constraint so we never split a tool pair. Falls back to null only when the // whole view can't satisfy the floor (tiny sessions) — the next context event // retries. if (userCount < opts.anchorUserMessages) { let c = cut; while (c > 1) { c--; if (!isBoundarySafe(view, c)) continue; const recentNow = view.slice(c); const usersNow = recentNow.filter((m) => m.role === "user").length; if (usersNow >= opts.anchorUserMessages) { cut = c; break; } } if (cut > 1) { const finalRecent = view.slice(cut); if (finalRecent.filter((m) => m.role === "user").length < opts.anchorUserMessages) { // CRITICAL-OVER ESCAPE HATCH: when context is critically over the window, // return the boundary-safe cut even though it's below the anchor floor. // Bailing to null here would feed the model the raw overflow and trap the // session in an unrecoverable error loop ("Already compacted" + overflow). // A thin anchor is recoverable; an overflowed session is not. if (opts.criticalOver && cut > 0) return cut; return null; // cannot satisfy the floor without dropping too much — retry next call } } else { // Same escape hatch for the cut > 1 false branch. if (opts.criticalOver && cut > 0) return cut; return null; } } return cut; } /** The formatted compacted-region summary as a user-role engine message. */ export function liveTrimSummaryMessage(opts: BuildLiveTrimViewOpts): EngineMessage { return { role: "user", text: formatCompactSummary(opts.summary), toolName: undefined, input: undefined, output: undefined, }; } /** Build the live trimmed view. Returns the original view if summary is empty * or the boundary is unsafe (no trim this call — try next). Pure + tested. */ export function buildLiveTrimmedView( view: EngineMessage[], opts: BuildLiveTrimViewOpts, ): EngineMessage[] { const cut = computeLiveTrimCut(view, opts); if (cut === null) return view; const recent = view.slice(cut); const summaryMsg = liveTrimSummaryMessage(opts); return [summaryMsg, ...recent]; }