/** * Live observe-only bridge from AgentMessage[] to the ContextItem/policy-engine layer * (Phase 1 audit pass). This module only ever reads messages and the artifact store; it * never mutates messages, the transcript, or artifact references. It is the first thing to * consume the context-item.ts/context-retention.ts/policy-engine.ts contracts against live * session state, but it does not yet change what the model sees -- see * docs/context-management-rework/implementation-phases.md for where this sits. * * Scope for this pass: only `toolResult` messages become `ContextItem`s (kind * "tool_output"). Other roles (user/assistant) are skipped; representing them is later * work once their ContextItemKind mapping (user_instruction/approval/etc.) is designed. * * Retrieval-path semantics (deliberately narrower than context-retention.ts's item-level * `hasReferencedEvidence`): a transcript ref is attached to every item as provenance * evidence (which live message this item came from), but it never counts toward * `HardConstraintFlags.hasAvailableRetrievalPath` on its own -- there is no live mechanism * today for the model to fetch an older message back into context by session-entry id, so * claiming that would overclaim retrievability (the same fail-closed principle * `artifact_retrieve` follows). Only a resolved artifact ref (the store still has the * payload) counts as an available retrieval path. */ import type { AgentMessage } from "@caupulican/pi-agent-core"; import type { ToolResultMessage } from "@caupulican/pi-ai"; import type { ArtifactStore } from "./context-artifacts.ts"; import { type ContextItem } from "./context-item.ts"; import { type RetentionEligibility } from "./context-retention.ts"; import type { PolicyHardConstraintCode } from "./policy-types.ts"; export interface ContextAuditOptions { /** Current turn index (AgentSession's own per-run counter); used as `createdAtTurn`. */ turnIndex: number; /** Session-scoped artifact store, if one has been constructed. Read-only here. */ artifactStore?: ArtifactStore; /** Resolve the persisted session-entry id for a toolResult message's toolCallId, if known. */ sessionEntryIdForToolCallId?: (toolCallId: string) => string | undefined; } export interface ContextAuditItemReport { item: ContextItem; /** The source toolResult message's own id and position, always available (unlike refs). */ toolCallId: string; messageIndex: number; /** Coarse, store-free eligibility from context-retention.ts (treats any ref as retrievable). */ retention: RetentionEligibility; /** Store-aware hard-constraint codes for keep_raw; always empty (no evaluated action restricts it), included for reportability. */ keepRawHardConstraints: PolicyHardConstraintCode[]; /** Store-aware hard-constraint codes for pack_to_artifact; empty means no hard rejection. */ packToArtifactHardConstraints: PolicyHardConstraintCode[]; /** Store-aware hard-constraint codes for drop_from_prompt; empty means no hard rejection. */ dropFromPromptHardConstraints: PolicyHardConstraintCode[]; /** Store-aware hard-constraint codes for summarize; empty means no hard rejection. */ summarizeHardConstraints: PolicyHardConstraintCode[]; } export interface ContextAuditReport { turnIndex: number; items: ContextAuditItemReport[]; } export interface BuiltToolOutputItem { item: ContextItem; /** True only if an artifact ref was found AND resolved against a live store. */ hasResolvedArtifact: boolean; } /** * Per-message memo of the expensive part of {@link buildToolOutputItem} (text extraction + * token/byte estimate + artifact `readRef` metadata), keyed by `AgentMessage` object identity * -- a replaced/rewritten message object is a new key, so it misses and recomputes with no * extra bookkeeping. Entries also carry the `messageIndex` they were built at: a message that * kept its identity but shifted index (e.g. a compaction survivor) is treated as a miss too, * since `messageIndex` feeds the transcript evidence ref. * * Ownership: the caller (`ContextPipeline`) owns a single long-lived instance across calls and * passes it in every pass; `runContextAudit` treats it as the previous pass's cache on read and * replaces its contents with exactly this pass's live entries before returning (a message no * longer present in `messages` is dropped, not retained) -- so the map never grows unbounded * across compaction rewrites or branch switches. Callers that must stay pure full-scans (tests, * read-only recompute paths) simply omit this parameter. */ export type ContextAuditMemo = Map; /** Build the ContextItem for a single toolResult message, for direct unit testing. */ export declare function buildToolResultContextItem(message: ToolResultMessage, messageIndex: number, options: ContextAuditOptions): ContextItem; /** * Read-only audit pass: converts live toolResult messages into ContextItems and runs the * existing pure retention/hard-constraint evaluators over them. Never mutates `messages`, * the transcript, or artifact references -- deterministic given the same messages, * `turnIndex`, and artifact-store state (not a cross-turn stability guarantee: turnIndex * and artifact-backed createdAtTurn values are expected to change turn over turn). * * `memo`, if supplied, is an incremental-memo seam (see {@link ContextAuditMemo}): the * expensive `buildToolOutputItem` work is skipped and reused for a message whose object * identity AND index are unchanged from the previous pass. The turnIndex-dependent tail * (`createdAtTurn` for non-artifact-backed items) is re-derived fresh on every call * regardless of cache hit/miss, so output is byte-identical to a full recompute either way * -- omitting `memo` (or passing a throwaway `new Map()`) makes this a pure full scan, exactly * as before this parameter existed. */ export declare function runContextAudit(messages: AgentMessage[], options: ContextAuditOptions, memo?: ContextAuditMemo): ContextAuditReport; //# sourceMappingURL=context-audit.d.ts.map