/** * Context pipeline: the session's per-turn context-shaping subsystem — the observe-only context * audit, the shadow prompt-policy plan and its correlation with legacy context-gc, the enforcement * pilot, the relevance-curation queue + the fitness-gated curation model resolver / brain-curation * drain + compaction pre-digest, the legacy context-gc pass and its packed-artifact reference * release, the tool-output artifact store, and the current-context token estimate. * * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the latest * audit/policy/correlation/enforcement/gc reports, the {@link BrainCurator} sidecar and its last * skip reasons, and the lazily-built tool-artifact store. Everything else it needs — the turn * index, the session/settings managers, the model registry, agent/workspace dirs, the active tool * names, the disposed flag, the isolated-completion primitive, spawned-usage accounting, and the * live {@link MemoryManager} — is reached through narrow deps accessors rather than the whole * AgentSession. * * Provider-request planning boundary (deliberate): the per-turn stages ({@link estimateCurrentContextTokens}, * {@link runContextAudit}, {@link runPromptPolicyPlanning}, {@link applyContextGc}, * {@link correlatePromptPolicyWithContextGc}, {@link runPromptEnforcement}, * {@link enqueueRelevanceCuration}, {@link maybeDrainBrainCuration}) are invoked from the session's * request context controller as one-line delegations, so that controller owns the pass * ordering. This controller reaches {@link MemoryController} functionality only through * {@link ContextPipelineDeps.getMemoryManager} (never imports it), and MemoryController never imports * the pipeline — keeping request planning the one place the two subsystems meet. */ import { type SessionManager } from "@caupulican/pi-agent-core/session"; import type { AgentMessage } from "@caupulican/pi-agent-core/types"; import type { Api, AssistantMessage, Model, Usage } from "@caupulican/pi-ai"; import type { IsolatedCompletionOptions, IsolatedCompletionResult } from "./agent-session-contracts.ts"; import { type CurationTelemetrySnapshot } from "./context/brain-curator.ts"; import { type ArtifactStore } from "./context/context-artifacts.ts"; import { type ContextAuditReport } from "./context/context-audit.ts"; import { type PromptEnforcementReport } from "./context/context-prompt-enforcement.ts"; import { type PromptPolicyGcCorrelationReport, type PromptPolicyShadowReport } from "./context/context-prompt-policy.ts"; import { type ContextGcReport } from "./context-gc.ts"; import type { MemoryManager } from "./memory/memory-manager.ts"; import type { ModelRegistry } from "./model-registry.ts"; import type { SettingsManager } from "./settings-manager.ts"; export interface ContextPipelineDeps { /** Current turn index, stamped into audit/policy/enforcement reports. */ getTurnIndex(): number; /** Session log: audit lookup, gc/artifact storage dirs, curation entries, token-estimate compaction anchor. */ getSessionManager(): SessionManager; /** Context-gc / prompt-enforcement / curation settings (all opt-in gates). */ getSettingsManager(): SettingsManager; /** Resolves a configured curation model pattern against configured auth. */ getModelRegistry(): ModelRegistry; /** Foreground model currently executing the transformed request. */ getModel(): Model | undefined; /** Root dir the host-keyed {@link FitnessStore} and per-session gc/artifact storage live under. */ getAgentDir(): string; /** Workspace root, passed to the context-gc pass. */ getCwd(): string; /** Currently-active tool names — enforcement checks whether artifact_retrieve is a live affordance. */ getActiveToolNames(): string[]; /** A disposed session must never persist a curation/pre-digest entry. */ isDisposed(): boolean; /** The live memory manager — the active providers' page markers feed the semantic-gc scan. */ getMemoryManager(): MemoryManager; /** Roll a curation drain's spawned usage into session accounting (idempotent per reportId). * `reportId` is REQUIRED: every caller derives a stable id from the work unit's identity * so a retry cannot double-count. */ addSpawnedUsage(usage: Usage, opts: { label?: string; sourceSessionId?: string; reportId: string; }): string | undefined; /** One-shot LLM call fully isolated from the main session — the curation/pre-digest execution primitive. */ runIsolatedCompletion(opts: IsolatedCompletionOptions): Promise; } export declare class ContextPipeline { private _latestContextGcReport; /** Brain-curation sidecar (design: brain-context-curation-design.md). Inert unless the * contextPolicy.curation setting is enabled AND the model passes the digest fitness gate. */ private readonly _brainCurator; private _lastCurationSkipReason; private _lastPreDigestSkipReason; private _toolArtifactStore; private _contextStoreRetentionLease; private _latestContextAuditReport; private _latestPromptPolicyReport; private _latestPromptPolicyGcCorrelation; private _latestPromptEnforcementReport; private readonly _tokenBudget; private _hasTokenBudgetAnchor; private _tokenBudgetAnchorCompactionId; private _lastTokenBudgetAnchorKey; /** Incremental current-branch lookup used by the per-turn audit. It retains only tool calls * still present in the provider-visible context, so compaction also bounds this cache. */ private _sessionEntryLookupCache; /** Incremental memo for the hot-path audit's expensive per-message work, keyed by * message object identity -- see {@link ContextAuditMemo}'s doc for the invalidation * contract. Rebuilt fresh (stale entries dropped) by `runContextAudit` every pass. */ private readonly _auditMemo; /** Incremental memo for the per-message token estimate `estimateContextTokensMemoized` * relies on -- same object-identity-keyed, fresh-rebuild-per-pass contract as `_auditMemo`, * just for a plain `estimateTokens(message)` number instead of a built ContextItem. */ private readonly _tokenMemo; private readonly deps; constructor(deps: ContextPipelineDeps); private _ensureContextStoreRetention; private _contextGcStorageDir; private _toolArtifactsDir; /** * Session-scoped, filesystem-backed artifact store for first-capture-then-bound tool * output (grep/find/run_toolkit_script -- see tool-output-artifacts.md). Lazily created and * cached so every tool construction in this session shares one store instance. * * `packToolOutput()` registers a reference (the packing tool call's id) at pack time * and fails closed, so packed artifacts are never prematurely collected. * `_releaseGcPackedArtifactReferences()` (called from `applyContextGc()`) releases * that reference once context-gc packs the result out of live context, and * opportunistically reclaims now-unreferenced artifacts via `cleanup()`. * Cross-session retention is enforced when either payload store is first used. Active * sessions hold PID-marked leases; inactive stores are pruned by age, count, and bytes. */ getToolArtifactStore(): ArtifactStore; /** * Best-effort final sweep of any already-released (zero-reference) tool-output artifact at * session dispose. Reads the field (not the getter) so a session that never packed anything * doesn't force-create a store/dir just to sweep it. */ cleanupToolArtifactStoreOnDispose(): void; /** * Map tool-result call ids to persisted session-entry ids. Linear branch growth is handled * incrementally by walking only entries appended since the cached leaf; a branch switch falls * back to one full rebuild so lookups never leak entries from the abandoned branch. */ private _getLatestCompactionEntry; private _buildSessionEntryIdLookup; /** Options shared by the memoized hot path and the no-memo full-scan path. */ private _buildContextAuditOptions; /** * Phase 1 observe-only audit pass (see context/context-audit.ts): converts live * toolResult messages into ContextItems and runs the existing retention/hard-constraint * evaluators over them, storing the latest deterministic report for tests/debugging. * Read-only with respect to messages, the transcript, and artifact references -- uses * `_toolArtifactStore` (the field), not `getToolArtifactStore()` (the getter), so a * session that never packed anything doesn't force-create a store/dir just to audit. * Never throws into a live turn: any failure degrades to an empty report. * * This is the memoized hot path (provider-request planning calls it every * round trip via agent-session.ts's one-line delegation) -- see `_auditMemo`'s doc for the * incremental design. `getContextAuditReport`/`getPromptPolicyReport`'s `messages`-arg * recompute variants deliberately do NOT go through this method (see * `_runContextAuditFullScan`), so a read-only debug/test peek at a hypothetical `messages` * array can never evict a live entry out of the hot-path memo. */ runContextAudit(messages: AgentMessage[]): ContextAuditReport; /** No-memo full-scan variant of {@link runContextAudit}, for read-only recompute callers. */ private _runContextAuditFullScan; /** * Read-only inspection of the context audit. With `messages`, recomputes fresh against * the given array (still no mutation of messages/transcript/artifact refs) -- a pure * full scan, bypassing the hot-path memo (see `_runContextAuditFullScan`); without, * returns the last report computed during a real transform pass. */ getContextAuditReport(messages?: AgentMessage[]): ContextAuditReport; /** * Observe-first shadow/planning pass (see context/context-prompt-policy.ts): re-shapes * the audit report into a per-item policy plan whose `appliedAction` is always * "keep_raw" -- this never enforces anything, it only records what the policy engine * would say. Never throws into a live turn: any failure degrades to an empty report. */ runPromptPolicyPlanning(auditReport: ContextAuditReport): PromptPolicyShadowReport; /** * Read-only inspection of the shadow policy plan. With `messages`, recomputes fresh * (audit + plan) against the given array; without, returns the last plan computed * during a real transform pass. Never mutates messages/transcript/artifact refs. */ getPromptPolicyReport(messages?: AgentMessage[]): PromptPolicyShadowReport; /** * Report-only correlation between the shadow plan just computed this turn and what the * legacy context-gc pass actually packed. Runs after `applyContextGc()` has already * produced its report; never influences context-gc itself. Never throws into a live * turn: any failure degrades to an empty correlation. */ correlatePromptPolicyWithContextGc(gcReport: ContextGcReport): void; /** Read-only inspection of the latest shadow-plan/legacy-gc correlation, for tests/debugging. */ getPromptPolicyGcCorrelation(): PromptPolicyGcCorrelationReport; /** * First enforcement pilot (see context/context-prompt-enforcement.ts): opt-in, * default-disabled stub-in-place of stale artifact-backed tool_output results in the * provider-visible message array only. Runs on `messages` AFTER context-gc has already * produced its own result, so legacy context-gc's own packing/reporting is completely * unaffected by this pass -- it only ever acts on messages gc left untouched this turn. * Never throws into a live turn: any failure degrades to returning `messages` unchanged. */ runPromptEnforcement(messages: AgentMessage[], shadowReport: PromptPolicyShadowReport): { messages: AgentMessage[]; report: PromptEnforcementReport; }; /** * Enqueue relevance-scoring jobs for stale, artifact-backed tool outputs the enforcement * pilot could act on. Pure queueing — the verdicts only ever take effect through the * asymmetric advisory lever inside enforcePromptPolicy. Never throws into a turn. */ enqueueRelevanceCuration(messages: AgentMessage[], shadowReport: PromptPolicyShadowReport): void; /** * Drain gate: settings on, model configured+authed, and the model has PASSED the digest * fitness probe on THIS host (design: unfit or unprobed models are refused with a visible * reason, never silently degraded). Fire-and-forget; never throws into a turn. */ /** * Resolve the curation model IFF every gate passes: setting enabled, model configured, * resolvable+authed, and digest-fitness-proven on THIS host (canonical "provider/id" ref — * runModelFitness stores reports under it, while settings.model may be a bare id or pattern). * Sets _lastCurationSkipReason on refusal; never throws. */ private _effectiveContextWindowFromFitness; resolveCurationModelIfFit(): Model | undefined; maybeDrainBrainCuration(): void; /** * Compaction pre-digest gate (design surface 3): everything the drain gate requires PLUS a * RUNTIME reliability proof — the curator must have run >=5 jobs on this session with a parse * failure rate <=5% before it is trusted to pre-digest compaction input. Returns undefined * (verbatim compaction, byte-for-byte today's behavior) whenever any gate refuses. */ buildCompactionPreDigest(): ((text: string, signal?: AbortSignal) => Promise) | undefined; private _drainBrainCuration; /** Curation status for diagnostics/dashboard: settings, live telemetry, last refusal reason. */ getContextCurationStatus(): { enabled: boolean; model?: string; telemetry: CurationTelemetrySnapshot; lastSkipReason?: string; lastPreDigestSkipReason?: string; }; /** Read-only inspection of the latest prompt-enforcement report, for tests/debugging. */ getPromptEnforcementReport(): PromptEnforcementReport; applyContextGc(messages: AgentMessage[], writePayloads: boolean): { messages: AgentMessage[]; report: ContextGcReport; }; /** * Reference-release + cleanup lifecycle: once context-gc has packed an artifact-producing * tool result (grep/find/run_toolkit_script) out of the live prompt (the message is no longer current/active working * context -- see contracts-and-retention.md's "ephemeral"/"expired" retention * classes), release the pack-time reference `packToolOutput()` registered for it, and * opportunistically reclaim now-unreferenced artifacts. This is the other half of the * D2b-1 gate: artifacts were being registered but never released, so they accumulated * for the life of the session. * * `record.toolCallId` (from context-gc's packed record) is exactly the holder id * `packToolOutput()` used when it called `addReference()` -- both trace back to the * same tool call's id -- so no separate bookkeeping is needed to find it. */ private _releaseGcPackedArtifactReferences; getContextGcReport(messages?: AgentMessage[]): ContextGcReport; observeProviderUsage(messages: AgentMessage[], usageMessage: AssistantMessage): void; estimateCurrentContextTokens(messages: AgentMessage[]): number; } //# sourceMappingURL=context-pipeline.d.ts.map