blob: e2a9dc8c03b7f9439573dbb47485a516cd58b2b3 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { createHash } from 'node:crypto';
import type {
CommitMemoryExtractionRequest,
MemoryExtractionCursor,
MemoryExtractionFailureClass,
MemoryExtractionReceipt,
MemoryItemWrite,
PendingMemoryExtractionFailure,
SettleMemoryExtractionFailureRequest,
SettleMemoryExtractionFailureResult,
} from '@maka/core/long-term-memory';
import { redactSecrets } from '@maka/core/redaction';
import type { SessionHeader } from '@maka/core/session';
import { z } from 'zod';
import {
fitMemoryExtractionEvidence,
bindProviderVisibleEvidence,
isMemoryToolName,
planMemoryCoverage,
projectMemoryExtractionEvidence,
renderMemoryLocalizationContext,
searchSameSessionMemoryHistory,
type MemoryExtractionEventEntry,
type MemoryCoveragePlan,
} from './memory-extraction-evidence.js';
import {
admitMemoryProposalItemDetailed,
buildFirstMemoryProposalPrompt,
buildLocalizedMemoryProposalPrompt,
buildMemoryCanonicalizationPrompt,
deterministicMemoryPolicyRejection,
minuteTimestamp,
parseMemoryCanonicalization,
parseLocalizedMemoryProposal,
parseMemoryProposal,
type AdmittedProposalFields,
type MemoryProposalItem,
} from './memory-extraction-proposal.js';
import { isHistoryCompactContentEvent } from './history-compaction.js';
import {
isTextHistoryCompactCheckpoint,
matchHistoryCompactCheckpointPrefix,
renderHistoryCompactCheckpoint,
type HistoryCompactCheckpoint,
} from './history-compact-checkpoint.js';
import type { ModelMessage, ModelToolSet } from './model-protocol.js';
import type { MakaTool, MakaToolContext } from './tool-runtime.js';
export const MEMORY_REMEMBER_TOOL_NAME = 'memory_remember';
export const MEMORY_EXTRACT_TOOL_NAME = 'memory_extract';
export type MemoryExtractionTrigger = 'remember' | 'extract' | 'compaction';
export type MemoryExtractionGate =
| { readonly allowed: true }
| {
readonly allowed: false;
readonly reason: 'disabled' | 'incognito' | 'ineligible' | 'unavailable';
};
/** Frozen extraction request. Compaction may defer durable-prefix materialization to its lane. */
export interface MemoryExtractionSourceSnapshot {
readonly trigger: MemoryExtractionTrigger;
readonly sourceHeader: Pick<
SessionHeader,
'llmConnectionId' | 'llmConnectionSlug' | 'model' | 'thinkingLevel'
>;
readonly sourceSystemPrompt?: string;
readonly sourceMessages: readonly ModelMessage[];
/** Compaction-only recipe: rebuild messages from its durable checkpoint boundary. */
readonly rebuildSourceContextFromCompactionCheckpoint?: true;
/** RuntimeEvent-to-message positions in the frozen provider prefix. */
readonly sourceEventMessagePositions?: Readonly<Record<string, readonly number[]>>;
readonly sourceTools: ModelToolSet;
readonly sourceActiveTools: readonly string[];
readonly sourceProviderOptions?: Record<string, unknown>;
readonly sourceMaxOutputTokens?: number;
/** Frozen model input capacity used for deterministic auxiliary-request preflight. */
readonly sourceContextWindowTokens?: number;
readonly sessionId: string;
readonly runId: string;
readonly turnId: string;
readonly workspaceKey: string;
/** Present only for memory_remember; identifies the call excluded from evidence. */
readonly toolCallId?: string;
/** Present only for post-terminal memory_extract. */
readonly terminalEventId?: string;
/** Present only for automatic Compaction extraction. */
readonly compactionCheckpointId?: string;
/** Exact durable RuntimeEvent boundary captured by the Compaction checkpoint. */
readonly compactionBoundaryEventId?: string;
}
export interface MemoryCompactionSourceContext {
readonly messages: readonly ModelMessage[];
readonly eventMessagePositions?: Readonly<Record<string, readonly number[]>>;
}
interface RememberedMemoryItem {
readonly itemId: string;
readonly content: string;
}
export type MemoryRememberResult =
| { readonly status: 'remembered'; readonly requestedItems: readonly RememberedMemoryItem[] }
| {
readonly status: 'not_applicable';
readonly requestedItems: readonly [];
readonly reason?: 'sensitive_information';
}
| {
readonly status: 'unavailable';
readonly requestedItems: readonly [];
readonly reason?: 'provider_unsupported';
};
export interface MemoryExtractionSourceCapabilities {
readonly gate: () => Promise<MemoryExtractionGate>;
/** Synchronous policy snapshot frozen with the backend; never performs I/O. */
readonly automaticGate?: () => MemoryExtractionGate;
readonly remember: (snapshot: MemoryExtractionSourceSnapshot) => Promise<MemoryRememberResult>;
readonly extract: (snapshot: MemoryExtractionSourceSnapshot) => void;
}
export interface MemoryExtractionEnginePorts {
readonly readGate: (sessionId: string) => Promise<MemoryExtractionGate>;
readonly readSessionEvents: (sessionId: string) => Promise<readonly MemoryExtractionEventEntry[]>;
readonly readCursor: (sessionId: string) => Promise<MemoryExtractionCursor | undefined>;
readonly initializeCursor: (
sessionId: string,
processedOrdinal: number,
) => Promise<MemoryExtractionCursor>;
readonly readPendingFailure: (
sessionId: string,
) => Promise<PendingMemoryExtractionFailure | undefined>;
readonly recordCompactionPolicyDenial: (input: {
readonly sessionId: string;
readonly compactionCheckpointId: string;
readonly deniedAt: number;
}) => Promise<unknown>;
readonly readCompactionPolicyDenials: (
sessionId: string,
) => Promise<readonly { readonly compactionCheckpointId: string }[]>;
readonly readLatestCompactionCheckpoint: (
sessionId: string,
) => Promise<HistoryCompactCheckpoint | undefined>;
readonly readCompactionCheckpoints: (
sessionId: string,
) => Promise<readonly HistoryCompactCheckpoint[]>;
readonly readReceipt: (operationId: string) => Promise<MemoryExtractionReceipt | undefined>;
readonly generate: (input: {
readonly snapshot: MemoryExtractionSourceSnapshot;
readonly prompt: string;
readonly stage: 'proposal' | 'localized' | 'canonicalize';
readonly abortSignal: AbortSignal;
}) => Promise<
| { readonly ok: true; readonly text: string }
| {
readonly ok: false;
readonly errorClass:
| 'aborted'
| 'timeout'
| 'configuration'
| 'provider'
| 'persistence'
| 'unknown';
}
>;
readonly commit: (request: CommitMemoryExtractionRequest) => Promise<{
readonly receipt: MemoryExtractionReceipt;
}>;
readonly settleFailure: (
request: SettleMemoryExtractionFailureRequest,
) => Promise<SettleMemoryExtractionFailureResult>;
readonly now?: () => number;
}
/** Keep most of a small context window available for the bounded source slice. */
export function memoryExtractionMaxOutputTokens(
snapshot: Pick<
MemoryExtractionSourceSnapshot,
'sourceMaxOutputTokens' | 'sourceContextWindowTokens'
>,
): number {
const configured = Math.max(1, snapshot.sourceMaxOutputTokens ?? 2_048);
return snapshot.sourceContextWindowTokens === undefined
? configured
: Math.min(configured, Math.max(1, Math.floor(snapshot.sourceContextWindowTokens / 6)));
}
/**
* Build the attachment-free normalized history used by automatic extraction and crash recovery.
* RuntimeEvents remain the evidence authority; this projection is context only.
*/
export function buildMemoryCompactionSourceContext(
events: readonly MemoryExtractionEventEntry['event'][],
boundaryEventId: string,
options: {
readonly afterEventId?: string;
readonly previousCheckpoint?: HistoryCompactCheckpoint;
} = {},
): MemoryCompactionSourceContext | undefined {
const boundaryIndex = events.findIndex((event) => event.id === boundaryEventId);
if (boundaryIndex < 0) return undefined;
const afterIndex = options.afterEventId
? events.findIndex((event) => event.id === options.afterEventId)
: -1;
if (options.afterEventId && afterIndex < 0) return undefined;
if (afterIndex >= boundaryIndex) return undefined;
const messages: ModelMessage[] = [];
const positions: Record<string, number[]> = {};
const push = (eventId: string | undefined, message: ModelMessage): void => {
const index = messages.length;
messages.push(message);
if (eventId) (positions[eventId] ??= []).push(index);
};
// Provider checkpoints are opaque transport state, not Memory evidence.
// Their covered RuntimeEvents remain the durable interpretation source.
if (options.previousCheckpoint && isTextHistoryCompactCheckpoint(options.previousCheckpoint)) {
push(undefined, {
role: 'user',
content: [{ type: 'text', text: renderHistoryCompactCheckpoint(options.previousCheckpoint) }],
});
}
for (const event of events.slice(afterIndex + 1, boundaryIndex + 1)) {
if (event.partial || event.content?.kind !== 'text') continue;
if (event.role === 'user' && event.author === 'user') {
push(event.id, { role: 'user', content: [{ type: 'text', text: event.content.text }] });
continue;
}
if (event.role === 'model' && event.author === 'agent') {
push(event.id, { role: 'assistant', content: [{ type: 'text', text: event.content.text }] });
}
}
// Tool calls/results, Thinking, attachment metadata/bytes, quotes, and provider-native
// metadata are intentionally outside the portable Memory interpretation context.
return {
messages: structuredClone(messages),
...(Object.keys(positions).length > 0 ? { eventMessagePositions: positions } : {}),
};
}
const MAX_MEMORY_EXTRACTION_MODEL_CALLS = 3;
type CoverageProcessingResult =
| {
readonly kind: 'committed';
readonly receipt: MemoryExtractionReceipt;
readonly nextCursorOrdinal: number;
}
| {
readonly kind: 'counted_failure';
readonly failureClass: MemoryExtractionFailureClass;
readonly failedTrigger?: MemoryExtractionTrigger;
readonly failedOperationId?: string;
readonly failedExpectedCursorOrdinal?: number;
readonly failedThroughOrdinal?: number;
readonly coverageHash?: string;
}
| { readonly kind: 'blocked' };
interface MemoryModelCallBudget {
remaining: number;
}
export function buildMemoryExtractionTriggerTools(input: {
readonly capabilities: MemoryExtractionSourceCapabilities;
readonly snapshot: (
trigger: MemoryExtractionTrigger,
context: MakaToolContext,
) => MemoryExtractionSourceSnapshot | undefined;
readonly markExtractRequested: (context: MakaToolContext) => void;
readonly unsupportedReason?: 'provider_unsupported';
}): readonly MakaTool[] {
const noArguments = z.object({}).strict();
return [
{
name: MEMORY_REMEMBER_TOOL_NAME,
description:
'Use only when the user explicitly asks you to remember long-term information. It stores the requested memory and returns exactly what was saved.',
parameters: noArguments,
executionSemantics: 'exclusive_step',
recoveryMode: 'idempotent',
impl: async (_args: Record<string, never>, context: MakaToolContext) => {
const gate = await input.capabilities.gate();
if (!gate.allowed) return { status: 'unavailable', requestedItems: [] };
if (input.unsupportedReason) {
return {
status: 'unavailable',
reason: input.unsupportedReason,
requestedItems: [],
};
}
const snapshot = input.snapshot('remember', context);
if (!snapshot) return { status: 'unavailable', requestedItems: [] };
return input.capabilities.remember(snapshot);
},
},
{
name: MEMORY_EXTRACT_TOOL_NAME,
description:
'Use when the conversation contains durable long-term information worth preserving and the user did not explicitly ask to remember it. The extraction runs after this turn.',
parameters: noArguments,
recoveryMode: 'idempotent',
impl: async (_args: Record<string, never>, context: MakaToolContext) => {
const gate = await input.capabilities.gate();
if (!gate.allowed) return { status: 'unavailable' };
if (input.unsupportedReason) {
return { status: 'unavailable', reason: input.unsupportedReason };
}
input.markExtractRequested(context);
return { status: 'accepted' };
},
},
];
}
/** Runtime-owned bounded state machine. Host supplies authority and lifecycle ports only. */
export class MemoryExtractionEngine {
constructor(private readonly ports: MemoryExtractionEnginePorts) {}
async execute(snapshot: MemoryExtractionSourceSnapshot): Promise<MemoryRememberResult> {
if (!validSourceContextContract(snapshot)) return unavailableMemoryResult();
const operationId = memoryExtractionOperationId(snapshot);
if (!operationId) return unavailableMemoryResult();
if (!(await this.allowedOrSettleCompaction(snapshot, operationId))) {
return unavailableMemoryResult();
}
const existing = await this.ports.readReceipt(operationId);
if (existing) return rememberResultFromReceipt(snapshot.trigger, existing);
if (!(await this.allowedOrSettleCompaction(snapshot, operationId))) {
return unavailableMemoryResult();
}
const entries = await this.ports.readSessionEvents(snapshot.sessionId);
const compactionCheckpoints = await this.ports.readCompactionCheckpoints(snapshot.sessionId);
const policyDeniedCheckpointIds = new Set(
(await this.ports.readCompactionPolicyDenials(snapshot.sessionId)).map(
({ compactionCheckpointId }) => compactionCheckpointId,
),
);
const currentCompactionCheckpoint =
snapshot.trigger === 'compaction'
? compactionCheckpoints.find(
(checkpoint) => checkpoint.checkpointId === snapshot.compactionCheckpointId,
)
: undefined;
if (
snapshot.trigger === 'compaction' &&
(!currentCompactionCheckpoint ||
!compactionCheckpointMatchesSnapshot([currentCompactionCheckpoint], snapshot))
) {
return unavailableMemoryResult();
}
const boundary = currentCompactionCheckpoint
? compactionBoundaryEntry(entries, currentCompactionCheckpoint)
: findExtractionBoundary(entries, snapshot);
if (!boundary) return unavailableMemoryResult();
if (
currentCompactionCheckpoint &&
isPolicyDeniedCheckpoint(currentCompactionCheckpoint, policyDeniedCheckpointIds)
) {
await this.settlePolicyDeniedCheckpoint(snapshot, currentCompactionCheckpoint, entries);
return unavailableMemoryResult();
}
const currentSnapshot = currentCompactionCheckpoint
? rebuildCompactionSnapshot(snapshot, currentCompactionCheckpoint)
: snapshot;
if (!currentSnapshot) return unavailableMemoryResult();
if (!(await this.allowedOrSettleCompaction(snapshot, operationId))) {
return unavailableMemoryResult();
}
let cursor = await this.ports.readCursor(snapshot.sessionId);
if (!(await this.allowedOrSettleCompaction(snapshot, operationId))) {
return unavailableMemoryResult();
}
const pendingFailure = await this.ports.readPendingFailure(snapshot.sessionId);
if (!cursor && !pendingFailure) {
if (!(await this.allowedOrSettleCompaction(snapshot, operationId))) {
return unavailableMemoryResult();
}
const checkpoint = await this.ports.readLatestCompactionCheckpoint(snapshot.sessionId);
const hasRecoverableCompaction = compactionCheckpoints.some((candidate) => {
const candidateBoundary = compactionBoundaryEntry(entries, candidate);
return candidateBoundary !== undefined && candidateBoundary.ordinal <= boundary.ordinal;
});
const bootstrapOrdinal =
checkpoint && !hasRecoverableCompaction
? validCompactionBootstrapOrdinal(entries, checkpoint)
: undefined;
if (bootstrapOrdinal && bootstrapOrdinal <= boundary.ordinal) {
cursor = await this.ports.initializeCursor(snapshot.sessionId, bootstrapOrdinal);
}
}
let expectedCursorOrdinal = cursor?.processedOrdinal ?? 0;
if (pendingFailure?.firstOperationId === operationId) return unavailableMemoryResult();
if (
pendingFailure &&
(pendingFailure.fromOrdinal !== expectedCursorOrdinal + 1 ||
pendingFailure.throughOrdinal > boundary.ordinal)
) {
return unavailableMemoryResult();
}
if (pendingFailure) {
const pendingCheckpoint = pendingFailure.compactionCheckpointId
? compactionCheckpoints.find(
(checkpoint) => checkpoint.checkpointId === pendingFailure.compactionCheckpointId,
)
: undefined;
const pendingBoundary = pendingCheckpoint
? compactionBoundaryEntry(entries, pendingCheckpoint)
: entries.find(({ ordinal }) => ordinal === pendingFailure.throughOrdinal);
if (pendingCheckpoint && pendingBoundary?.ordinal !== pendingFailure.throughOrdinal) {
return unavailableMemoryResult();
}
const retrySnapshot = pendingCheckpoint
? rebuildCompactionSnapshot(currentSnapshot, pendingCheckpoint)
: pendingFailure.compactionCheckpointId
? undefined
: rebuildExplicitPendingSnapshot(
currentSnapshot,
pendingFailure,
pendingBoundary,
entries,
);
if (!retrySnapshot) return unavailableMemoryResult();
const retryOperationId = pendingRetryOperationId(operationId, pendingFailure);
const retry = await this.processRange({
snapshot: retrySnapshot,
trigger: pendingFailure.firstTrigger,
operationId: retryOperationId,
expectedCursorOrdinal,
targetBoundaryOrdinal: pendingFailure.throughOrdinal,
expectedCoverageHash: pendingFailure.coverageHash,
entries,
compactionCheckpoints,
policyDeniedCheckpointIds,
prioritizeCurrentTurn: pendingFailure.firstTrigger === 'remember',
});
if (retry.kind === 'blocked') {
await this.settleCompactionIfCurrentlyDenied(snapshot, operationId);
return unavailableMemoryResult();
}
if (retry.kind === 'committed') {
expectedCursorOrdinal = retry.nextCursorOrdinal;
} else {
const settled = await this.settleCountedFailure({
snapshot: retrySnapshot,
trigger: retry.failedTrigger ?? pendingFailure.firstTrigger,
operationId: retry.failedOperationId ?? retryOperationId,
expectedCursorOrdinal: retry.failedExpectedCursorOrdinal ?? expectedCursorOrdinal,
throughOrdinal: retry.failedThroughOrdinal ?? pendingFailure.throughOrdinal,
coverageHash: retry.coverageHash ?? pendingFailure.coverageHash,
failureClass: retry.failureClass,
});
if (!settled) {
await this.settleCompactionIfCurrentlyDenied(snapshot, operationId);
return unavailableMemoryResult();
}
if (settled.status !== 'discarded') return unavailableMemoryResult();
expectedCursorOrdinal = settled.cursor.processedOrdinal;
}
}
for (;;) {
const recoveredCheckpoint = earliestUnprocessedCompactionCheckpoint(
compactionCheckpoints,
entries,
expectedCursorOrdinal,
boundary.ordinal,
snapshot.compactionCheckpointId,
);
if (!recoveredCheckpoint) break;
if (isPolicyDeniedCheckpoint(recoveredCheckpoint, policyDeniedCheckpointIds)) {
const nextCursorOrdinal = await this.settlePolicyDeniedCheckpoint(
snapshot,
recoveredCheckpoint,
entries,
);
if (nextCursorOrdinal === undefined) return unavailableMemoryResult();
expectedCursorOrdinal = nextCursorOrdinal;
} else {
const recoveredSnapshot = rebuildCompactionSnapshot(currentSnapshot, recoveredCheckpoint);
const recoveredBoundary = compactionBoundaryEntry(entries, recoveredCheckpoint);
const recoveredOperationId = recoveredSnapshot
? memoryExtractionOperationId(recoveredSnapshot)
: undefined;
if (!recoveredSnapshot || !recoveredBoundary || !recoveredOperationId) {
return unavailableMemoryResult();
}
const recovered = await this.processRange({
snapshot: recoveredSnapshot,
trigger: 'compaction',
operationId: recoveredOperationId,
expectedCursorOrdinal,
targetBoundaryOrdinal: recoveredBoundary.ordinal,
entries,
compactionCheckpoints,
policyDeniedCheckpointIds,
prioritizeCurrentTurn: false,
});
if (recovered.kind === 'blocked') {
await this.settleCompactionIfCurrentlyDenied(snapshot, operationId);
return unavailableMemoryResult();
}
if (recovered.kind === 'counted_failure') {
const settled = await this.settleCountedFailure({
snapshot: recoveredSnapshot,
trigger: recovered.failedTrigger ?? 'compaction',
operationId: recovered.failedOperationId ?? recoveredOperationId,
expectedCursorOrdinal: recovered.failedExpectedCursorOrdinal ?? expectedCursorOrdinal,
throughOrdinal: recovered.failedThroughOrdinal ?? recoveredBoundary.ordinal,
coverageHash:
recovered.coverageHash ??
memoryCoverageHash(
entries.filter(
({ ordinal }) =>
ordinal > expectedCursorOrdinal && ordinal <= recoveredBoundary.ordinal,
),
),
failureClass: recovered.failureClass,
});
if (!settled) {
await this.settleCompactionIfCurrentlyDenied(snapshot, operationId);
}
return unavailableMemoryResult();
}
expectedCursorOrdinal = recovered.nextCursorOrdinal;
}
}
if (expectedCursorOrdinal >= boundary.ordinal) {
return snapshot.trigger === 'remember'
? { status: 'not_applicable', requestedItems: [] }
: unavailableMemoryResult();
}
const processed = await this.processRange({
snapshot: currentSnapshot,
trigger: snapshot.trigger,
operationId,
expectedCursorOrdinal,
targetBoundaryOrdinal: boundary.ordinal,
entries,
compactionCheckpoints,
policyDeniedCheckpointIds,
prioritizeCurrentTurn: snapshot.trigger === 'remember',
});
if (processed.kind === 'committed') {
return rememberResultFromReceipt(snapshot.trigger, processed.receipt);
}
if (processed.kind === 'counted_failure') {
const settled = await this.settleCountedFailure({
snapshot: currentSnapshot,
trigger: processed.failedTrigger ?? snapshot.trigger,
operationId: processed.failedOperationId ?? operationId,
expectedCursorOrdinal: processed.failedExpectedCursorOrdinal ?? expectedCursorOrdinal,
throughOrdinal: processed.failedThroughOrdinal ?? boundary.ordinal,
coverageHash:
processed.coverageHash ??
memoryCoverageHash(
entries.filter(
({ ordinal }) => ordinal > expectedCursorOrdinal && ordinal <= boundary.ordinal,
),
),
failureClass: processed.failureClass,
});
if (!settled) {
await this.settleCompactionIfCurrentlyDenied(snapshot, operationId);
}
} else if (processed.kind === 'blocked') {
await this.settleCompactionIfCurrentlyDenied(snapshot, operationId);
}
return unavailableMemoryResult();
}
private async processRange(input: {
readonly snapshot: MemoryExtractionSourceSnapshot;
readonly trigger: MemoryExtractionTrigger;
readonly operationId: string;
readonly expectedCursorOrdinal: number;
readonly targetBoundaryOrdinal: number;
readonly expectedCoverageHash?: string;
readonly entries: readonly MemoryExtractionEventEntry[];
readonly compactionCheckpoints: readonly HistoryCompactCheckpoint[];
readonly policyDeniedCheckpointIds: ReadonlySet<string>;
readonly prioritizeCurrentTurn: boolean;
readonly allowSplit?: boolean;
}): Promise<CoverageProcessingResult> {
const pendingEntries = input.entries.filter(
({ ordinal }) =>
ordinal > input.expectedCursorOrdinal && ordinal <= input.targetBoundaryOrdinal,
);
const coverageHash = memoryCoverageHash(pendingEntries);
if (input.expectedCoverageHash && input.expectedCoverageHash !== coverageHash) {
return { kind: 'blocked' };
}
const processSplit = async (): Promise<CoverageProcessingResult | undefined> => {
if (input.allowSplit === false) return undefined;
let maximumSplitIndex: number | undefined;
if (input.trigger === 'remember') {
const requestedTurnStart = pendingEntries.findIndex(
({ event }) =>
event.runId === input.snapshot.runId && event.turnId === input.snapshot.turnId,
);
if (requestedTurnStart <= 0) return undefined;
maximumSplitIndex = requestedTurnStart;
}
const split = findMemoryRangeSplit(pendingEntries, maximumSplitIndex, ({ first, second }) => {
const firstThroughOrdinal = first.at(-1)!.ordinal;
const firstTrigger: MemoryExtractionTrigger = 'extract';
const firstPrepared = this.prepareRange({
...input,
trigger: firstTrigger,
targetBoundaryOrdinal: firstThroughOrdinal,
prioritizeCurrentTurn: false,
pendingEntries: first,
coverageHash: memoryCoverageHash(first),
});
const secondPrepared = this.prepareRange({
...input,
expectedCursorOrdinal: firstThroughOrdinal,
pendingEntries: second,
coverageHash: memoryCoverageHash(second),
});
return (
preparedMemoryRangeFits(firstPrepared, firstTrigger) &&
preparedMemoryRangeFits(secondPrepared, input.trigger)
);
});
if (!split) {
return undefined;
}
const firstThroughOrdinal = split.first.at(-1)!.ordinal;
const first = await this.processRange({
...input,
// A split prefix is an independent incidental range. It cannot retain
// the full Compaction checkpoint identity because its boundary is
// earlier than that checkpoint, and it cannot retain remember semantics
// because the requested Turn is deliberately kept in the final segment.
trigger: 'extract',
operationId: memorySegmentOperationId(
input.operationId,
input.expectedCursorOrdinal,
firstThroughOrdinal,
input.snapshot.trigger === 'compaction',
),
targetBoundaryOrdinal: firstThroughOrdinal,
expectedCoverageHash: memoryCoverageHash(split.first),
prioritizeCurrentTurn: false,
allowSplit: false,
});
if (first.kind !== 'committed') return first;
return this.processRange({
...input,
expectedCursorOrdinal: first.nextCursorOrdinal,
expectedCoverageHash: memoryCoverageHash(split.second),
allowSplit: false,
});
};
const prepared = this.prepareRange({ ...input, pendingEntries, coverageHash });
if (!prepared) {
const split = await processSplit();
if (split) return split;
return {
kind: 'counted_failure',
failureClass: 'evidence',
failedTrigger: input.trigger,
failedOperationId: input.operationId,
failedExpectedCursorOrdinal: input.expectedCursorOrdinal,
failedThroughOrdinal: input.targetBoundaryOrdinal,
coverageHash,
};
}
if (
(prepared.coverage.evidence.length > 0 || input.trigger === 'remember') &&
!memoryRequestFits(prepared.snapshot, prepared.firstPrompt, 'proposal')
) {
const split = await processSplit();
if (split) return split;
return {
kind: 'counted_failure',
failureClass: 'provider',
failedTrigger: input.trigger,
failedOperationId: input.operationId,
failedExpectedCursorOrdinal: input.expectedCursorOrdinal,
failedThroughOrdinal: input.targetBoundaryOrdinal,
coverageHash,
};
}
const processed = await this.processCoverage({
snapshot: prepared.snapshot,
trigger: input.trigger,
operationId: input.operationId,
expectedCursorOrdinal: input.expectedCursorOrdinal,
coverage: prepared.coverage,
entries: input.entries,
boundaryOrdinal: input.targetBoundaryOrdinal,
coverageHash,
localizationAfterOrdinal: latestPolicyDeniedBoundaryOrdinal(
input.compactionCheckpoints,
input.entries,
input.policyDeniedCheckpointIds,
input.targetBoundaryOrdinal,
),
requestedEvidenceContainsSensitiveText: prepared.requestedEvidenceContainsSensitiveText,
});
return processed.kind === 'counted_failure'
? {
...processed,
failedTrigger: input.trigger,
failedOperationId: input.operationId,
failedExpectedCursorOrdinal: input.expectedCursorOrdinal,
failedThroughOrdinal: input.targetBoundaryOrdinal,
coverageHash,
}
: processed;
}
private prepareRange(input: {
readonly snapshot: MemoryExtractionSourceSnapshot;
readonly trigger: MemoryExtractionTrigger;
readonly expectedCursorOrdinal: number;
readonly targetBoundaryOrdinal: number;
readonly entries: readonly MemoryExtractionEventEntry[];
readonly compactionCheckpoints: readonly HistoryCompactCheckpoint[];
readonly policyDeniedCheckpointIds: ReadonlySet<string>;
readonly prioritizeCurrentTurn: boolean;
readonly pendingEntries: readonly MemoryExtractionEventEntry[];
readonly coverageHash: string;
}):
| {
readonly snapshot: MemoryExtractionSourceSnapshot;
readonly coverage: MemoryCoveragePlan;
readonly firstPrompt: string;
readonly requestedEvidenceContainsSensitiveText: boolean;
}
| undefined {
const rangeSnapshot = buildMemoryRangeSnapshot({
snapshot: input.snapshot,
entries: input.entries,
checkpoints: input.compactionCheckpoints,
policyDeniedCheckpointIds: input.policyDeniedCheckpointIds,
afterOrdinal: input.expectedCursorOrdinal,
throughOrdinal: input.targetBoundaryOrdinal,
});
if (!rangeSnapshot) return undefined;
const priorityEvidence = input.prioritizeCurrentTurn
? projectMemoryExtractionEvidence(
input.pendingEntries
.filter(
({ event }) =>
event.runId === rangeSnapshot.runId && event.turnId === rangeSnapshot.turnId,
)
.map(({ event }) => event),
)
: [];
const requestedEvidenceContainsSensitiveText =
input.trigger === 'remember' && memoryEvidenceContainsSensitiveText(priorityEvidence);
const coverage = planMemoryCoverage({
pendingEntries: input.pendingEntries,
...(input.trigger === 'remember' ? { priorityEvidence } : {}),
sourceEventMessagePositions: rangeSnapshot.sourceEventMessagePositions,
sourceMessages: rangeSnapshot.sourceMessages,
});
if (!coverage || coverage.entries.length === 0) return undefined;
const firstPrompt = buildFirstMemoryProposalPrompt({
trigger: input.trigger,
now: this.now(),
evidence: coverage.evidence,
sourceEventMessagePositions: rangeSnapshot.sourceEventMessagePositions,
});
return {
snapshot: rangeSnapshot,
coverage,
firstPrompt,
requestedEvidenceContainsSensitiveText,
};
}
private async processCoverage(input: {
readonly snapshot: MemoryExtractionSourceSnapshot;
readonly trigger: MemoryExtractionTrigger;
readonly operationId: string;
readonly expectedCursorOrdinal: number;
readonly coverage: MemoryCoveragePlan;
readonly entries: readonly MemoryExtractionEventEntry[];
readonly boundaryOrdinal: number;
readonly coverageHash: string;
readonly localizationAfterOrdinal: number;
readonly requestedEvidenceContainsSensitiveText: boolean;
}): Promise<CoverageProcessingResult> {
const { snapshot, coverage } = input;
if (input.requestedEvidenceContainsSensitiveText) {
return this.commitSensitiveNoOp(input);
}
const budget: MemoryModelCallBudget = { remaining: MAX_MEMORY_EXTRACTION_MODEL_CALLS };
let lastFailureClass: MemoryExtractionFailureClass = 'schema';
do {
const attempt = await this.processCoverageAttempt(input, budget);
if (attempt.kind === 'committed' || attempt.kind === 'blocked') return attempt;
lastFailureClass = attempt.failureClass;
} while (budget.remaining > 0);
return { kind: 'counted_failure', failureClass: lastFailureClass };
}
private async processCoverageAttempt(
input: {
readonly snapshot: MemoryExtractionSourceSnapshot;
readonly trigger: MemoryExtractionTrigger;
readonly operationId: string;
readonly expectedCursorOrdinal: number;
readonly coverage: MemoryCoveragePlan;
readonly entries: readonly MemoryExtractionEventEntry[];
readonly boundaryOrdinal: number;
readonly coverageHash: string;
readonly localizationAfterOrdinal: number;
readonly requestedEvidenceContainsSensitiveText: boolean;
},
budget: MemoryModelCallBudget,
): Promise<CoverageProcessingResult> {
const { snapshot, trigger, coverage } = input;
let requestedItems: readonly MemoryProposalItem[] = [];
let incidentalItems: readonly MemoryProposalItem[] = [];
let requestedStatus: 'resolved' | 'not_applicable' | 'unresolved' = 'not_applicable';
let requestedAdmissionEvidence = coverage.evidence;
let localizedInterpretationContext: string | undefined;
if (coverage.evidence.length > 0 || trigger === 'remember') {
const firstCall = await this.callModel(
snapshot,
buildFirstMemoryProposalPrompt({
trigger,
now: this.now(),
evidence: coverage.evidence,
sourceEventMessagePositions: snapshot.sourceEventMessagePositions,
}),
'proposal',
budget,
);
if (firstCall.kind !== 'ok') return firstCall;
const first = parseMemoryProposal(firstCall.raw);
if (!first) return { kind: 'counted_failure', failureClass: 'schema' };
if (
trigger !== 'remember' &&
(first.requestedItems.length > 0 ||
(first.status === 'complete'
? first.requestedStatus !== 'not_applicable'
: first.requestedStatus !== 'unresolved'))
) {
return { kind: 'counted_failure', failureClass: 'schema' };
}
requestedItems = first.requestedItems;
incidentalItems = first.incidentalItems;
requestedStatus = first.requestedStatus;
if (first.status === 'search_required') {
if (!(await this.allowed(snapshot.sessionId))) {
return { kind: 'blocked' };
}
const localizedEntries = searchSameSessionMemoryHistory(
input.entries,
input.boundaryOrdinal,
first.search,
input.localizationAfterOrdinal,
);
if (localizedEntries.length === 0) {
return { kind: 'counted_failure', failureClass: 'localization' };
}
localizedInterpretationContext = renderMemoryLocalizationContext(localizedEntries);
if (!localizedInterpretationContext) {
return { kind: 'counted_failure', failureClass: 'localization' };
}
const localizedVisibleEvidence = bindProviderVisibleEvidence(
projectMemoryExtractionEvidence(
localizedEntries.map(({ event }) => event),
{ snippetTerms: first.search.terms },
),
snapshot.sourceMessages,
snapshot.sourceEventMessagePositions,
);
const localizedEvidence = fitMemoryExtractionEvidence(
localizedVisibleEvidence,
undefined,
snapshot.sourceEventMessagePositions,
);
if (!localizedEvidence) {
return { kind: 'counted_failure', failureClass: 'evidence' };
}
if (trigger === 'remember' && memoryEvidenceContainsSensitiveText(localizedEvidence)) {
return this.commitSensitiveNoOp(input);
}
if (budget.remaining <= 0) {
return { kind: 'counted_failure', failureClass: 'localization' };
}
const localizedPrompt = fittingLocalizedMemoryPrompt({
snapshot,
trigger,
now: this.now(),
evidence: trigger === 'remember' ? localizedEvidence : coverage.evidence,
interpretationContext: localizedInterpretationContext,
});
if (!localizedPrompt) {
return { kind: 'counted_failure', failureClass: 'localization' };
}
localizedInterpretationContext = localizedPrompt.interpretationContext;
const localizedCall = await this.callModel(
snapshot,
localizedPrompt.prompt,
'localized',
budget,
);
if (localizedCall.kind !== 'ok') return localizedCall;
const localized = parseLocalizedMemoryProposal(localizedCall.raw);
if (!localized) return { kind: 'counted_failure', failureClass: 'schema' };
if (localized.status === 'cannot_resolve') {
return { kind: 'counted_failure', failureClass: 'localization' };
}
if ('coverageStatus' in localized) {
if (localized.status !== 'complete') {
return { kind: 'counted_failure', failureClass: 'schema' };
}
if (
trigger !== 'remember' &&
(localized.requestedStatus !== 'not_applicable' || localized.requestedItems.length > 0)
) {
return { kind: 'counted_failure', failureClass: 'schema' };
}
requestedItems = localized.requestedItems;
incidentalItems = [...incidentalItems, ...localized.incidentalItems];
requestedStatus = localized.requestedStatus;
} else {
// Backward-compatible parser branch for in-flight PR 2-A responses.
requestedItems = localized.requestedItems;
requestedStatus = localized.status;
}
if (trigger === 'remember') requestedAdmissionEvidence = localizedEvidence;
} else if (first.status === 'cannot_resolve') {
return { kind: 'counted_failure', failureClass: 'localization' };
}
}
if (requestedStatus === 'unresolved') {
return { kind: 'counted_failure', failureClass: 'localization' };
}
const requestedEvidenceByRef = new Map(
requestedAdmissionEvidence.map((entry) => [entry.sourceRef, entry]),
);
const coverageEvidenceByRef = new Map(
coverage.evidence.map((entry) => [entry.sourceRef, entry]),
);
const coverageEventIds = new Set(coverage.entries.map(({ event }) => event.id));
const candidates: Array<{
readonly candidateId: string;
readonly requested: boolean;
readonly proposal: MemoryProposalItem;
}> = [];
let noOpReason: 'sensitive_information' | undefined;
proposalLoop: for (const [requested, proposals] of [
[true, requestedItems],
[false, incidentalItems],
] as const) {
for (const proposal of proposals) {
if (deterministicMemoryPolicyRejection(proposal)) {
if (requested) {
noOpReason = 'sensitive_information';
break proposalLoop;
}
continue;
}
const admission = admitMemoryProposalItemDetailed(
proposal,
requested ? requestedEvidenceByRef : coverageEvidenceByRef,
);
if (!admission.admitted) {
if (requested) {
return {
kind: 'counted_failure',
failureClass: admission.reason === 'evidence' ? 'evidence' : 'requested_admission',
};
}
continue;
}
if (
!requested &&
admission.fields.citedEvents.some((event) => !coverageEventIds.has(event.id))
) {
continue;
}
candidates.push({
candidateId: `candidate_${candidates.length}`,
requested,
proposal,
});
}
}
const writes: MemoryItemWrite[] = [];
const requestedItemIndexes: number[] = [];
if (!noOpReason && candidates.length > 0) {
const canonicalPrompt = buildMemoryCanonicalizationPrompt({
now: this.now(),
candidates: candidates.map(({ candidateId, requested, proposal }) => ({
candidateId,
requested,
evidence: proposal.evidence.map(({ sourceRef, quote }) => {
const source = (requested ? requestedEvidenceByRef : coverageEvidenceByRef).get(
sourceRef,
)!;
return {
sourceRef,
quote,
observedAt: minuteTimestamp(Math.max(...source.events.map((event) => event.ts))),
};
}),
...(localizedInterpretationContext
? { interpretationContext: localizedInterpretationContext }
: {}),
})),
});
let byId:
| Map<
string,
NonNullable<ReturnType<typeof parseMemoryCanonicalization>>['results'][number]
>
| undefined;
let canonicalFailureClass: MemoryExtractionFailureClass = 'schema';
while (!byId && budget.remaining > 0) {
const canonicalCall = await this.callModel(
snapshot,
canonicalPrompt,
'canonicalize',
budget,
);
if (canonicalCall.kind === 'blocked') return canonicalCall;
if (canonicalCall.kind === 'counted_failure') {
canonicalFailureClass = canonicalCall.failureClass;
continue;
}
const results = parseMemoryCanonicalization(canonicalCall.raw)?.results;
if (!results || results.length !== candidates.length) {
canonicalFailureClass = 'schema';
continue;
}
const indexed = new Map(results.map((result) => [result.candidateId, result]));
if (
indexed.size !== candidates.length ||
candidates.some(({ candidateId }) => !indexed.has(candidateId))
) {
canonicalFailureClass = 'schema';
continue;
}
byId = indexed;
}
if (!byId) return { kind: 'counted_failure', failureClass: canonicalFailureClass };
for (const candidate of candidates) {
const result = byId.get(candidate.candidateId)!;
if (result.status === 'rejected') {
if (candidate.requested) {
return { kind: 'counted_failure', failureClass: 'requested_admission' };
}
continue;
}
const canonicalProposal: MemoryProposalItem = {
...result.item,
evidence: candidate.proposal.evidence,
};
if (deterministicMemoryPolicyRejection(canonicalProposal)) {
if (candidate.requested) {
noOpReason = 'sensitive_information';
break;
}
continue;
}
const admission = admitMemoryProposalItemDetailed(
canonicalProposal,
candidate.requested ? requestedEvidenceByRef : coverageEvidenceByRef,
);
if (!admission.admitted) {
if (candidate.requested) {
return {
kind: 'counted_failure',
failureClass: admission.reason === 'evidence' ? 'evidence' : 'requested_admission',
};
}
continue;
}
if (candidate.requested) requestedItemIndexes.push(writes.length);
writes.push(memoryItemWrite(snapshot, admission.fields, candidate.requested));
}
}
if (noOpReason) {
writes.length = 0;
requestedItemIndexes.length = 0;
}
if (!(await this.allowed(snapshot.sessionId))) return { kind: 'blocked' };
const committed = await this.ports.commit({
operationId: input.operationId,
sessionId: snapshot.sessionId,
expectedCursorOrdinal: input.expectedCursorOrdinal,
nextCursorOrdinal: coverage.entries.at(-1)!.ordinal,
coverageHash: input.coverageHash,
items: writes,
requestedItemIndexes,
...(noOpReason ? { noOpReason } : {}),
trigger,
...(trigger === 'compaction' && snapshot.compactionCheckpointId
? { compactionCheckpointId: snapshot.compactionCheckpointId }
: {}),
});
return {
kind: 'committed',
receipt: committed.receipt,
nextCursorOrdinal: coverage.entries.at(-1)!.ordinal,
};
}
private async commitSensitiveNoOp(input: {
readonly snapshot: MemoryExtractionSourceSnapshot;
readonly trigger: MemoryExtractionTrigger;
readonly operationId: string;
readonly expectedCursorOrdinal: number;
readonly coverage: MemoryCoveragePlan;
readonly coverageHash: string;
}): Promise<CoverageProcessingResult> {
if (!(await this.allowed(input.snapshot.sessionId))) return { kind: 'blocked' };
const nextCursorOrdinal = input.coverage.entries.at(-1)!.ordinal;
const committed = await this.ports.commit({
operationId: input.operationId,
sessionId: input.snapshot.sessionId,
expectedCursorOrdinal: input.expectedCursorOrdinal,
nextCursorOrdinal,
coverageHash: input.coverageHash,
items: [],
requestedItemIndexes: [],
noOpReason: 'sensitive_information',
trigger: input.trigger,
...(input.trigger === 'compaction' && input.snapshot.compactionCheckpointId
? { compactionCheckpointId: input.snapshot.compactionCheckpointId }
: {}),
});
return { kind: 'committed', receipt: committed.receipt, nextCursorOrdinal };
}
private async callModel(
snapshot: MemoryExtractionSourceSnapshot,
prompt: string,
stage: 'proposal' | 'localized' | 'canonicalize',
budget: MemoryModelCallBudget,
): Promise<
| { readonly kind: 'ok'; readonly raw: string }
| { readonly kind: 'counted_failure'; readonly failureClass: 'provider' }
| { readonly kind: 'blocked' }
> {
if (budget.remaining <= 0) {
return { kind: 'counted_failure', failureClass: 'provider' };
}
if (!memoryRequestFits(snapshot, prompt, stage)) {
budget.remaining = 0;
return { kind: 'counted_failure', failureClass: 'provider' };
}
if (!(await this.allowed(snapshot.sessionId))) return { kind: 'blocked' };
budget.remaining -= 1;
const result = await this.ports.generate({
snapshot,
prompt,
stage,
abortSignal: AbortSignal.timeout(60_000),
});
if (result.ok) return { kind: 'ok', raw: result.text };
if (!(await this.allowed(snapshot.sessionId))) return { kind: 'blocked' };
return result.errorClass === 'provider' || result.errorClass === 'timeout'
? { kind: 'counted_failure', failureClass: 'provider' }
: { kind: 'blocked' };
}
private async settleCountedFailure(input: {
readonly snapshot: MemoryExtractionSourceSnapshot;
readonly trigger: MemoryExtractionTrigger;
readonly operationId: string;
readonly expectedCursorOrdinal: number;
readonly throughOrdinal: number;
readonly coverageHash: string;
readonly failureClass: MemoryExtractionFailureClass;
}): Promise<SettleMemoryExtractionFailureResult | undefined> {
if (!(await this.allowed(input.snapshot.sessionId))) return undefined;
return this.ports.settleFailure({
operationId: input.operationId,
sessionId: input.snapshot.sessionId,
expectedCursorOrdinal: input.expectedCursorOrdinal,
failedThroughOrdinal: input.throughOrdinal,
coverageHash: input.coverageHash,
failureClass: input.failureClass,
trigger: input.trigger,
...(input.trigger === 'compaction' && input.snapshot.compactionCheckpointId
? { compactionCheckpointId: input.snapshot.compactionCheckpointId }
: {}),
});
}
private async allowedOrSettleCompaction(
snapshot: MemoryExtractionSourceSnapshot,
operationId: string,
): Promise<boolean> {
const gate = await this.ports.readGate(snapshot.sessionId);
if (gate.allowed) return true;
if (gate.reason !== 'unavailable') {
await this.settleDeniedCompaction(snapshot, operationId);
}
return false;
}
private async settleCompactionIfCurrentlyDenied(
snapshot: MemoryExtractionSourceSnapshot,
operationId: string,
): Promise<void> {
if (snapshot.trigger !== 'compaction') return;
const gate = await this.ports.readGate(snapshot.sessionId);
if (!gate.allowed && gate.reason !== 'unavailable') {
await this.settleDeniedCompaction(snapshot, operationId);
}
}
/**
* A policy rejection after the checkpoint is intentional, not a crash gap.
* Settle the whole frozen range atomically without model access or Item writes
* so a later enabled Compaction cannot recover history from the denied period.
*/
private async settleDeniedCompaction(
snapshot: MemoryExtractionSourceSnapshot,
_operationId: string,
): Promise<void> {
if (snapshot.trigger !== 'compaction') return;
const entries = await this.ports.readSessionEvents(snapshot.sessionId);
const checkpoints = await this.ports.readCompactionCheckpoints(snapshot.sessionId);
const checkpoint = checkpoints.find(
(candidate) => candidate.checkpointId === snapshot.compactionCheckpointId,
);
if (!checkpoint || !compactionCheckpointMatchesSnapshot([checkpoint], snapshot)) return;
await this.ports.recordCompactionPolicyDenial({
sessionId: snapshot.sessionId,
compactionCheckpointId: checkpoint.checkpointId,
deniedAt: this.now(),
});
await this.settlePolicyDeniedCheckpoint(snapshot, checkpoint, entries);
}
private async settlePolicyDeniedCheckpoint(
snapshot: MemoryExtractionSourceSnapshot,
checkpoint: HistoryCompactCheckpoint,
entries: readonly MemoryExtractionEventEntry[],
): Promise<number | undefined> {
const boundary = compactionBoundaryEntry(entries, checkpoint);
if (!boundary) return;
const cursor = await this.ports.readCursor(snapshot.sessionId);
const expectedCursorOrdinal = cursor?.processedOrdinal ?? 0;
if (expectedCursorOrdinal >= boundary.ordinal) return expectedCursorOrdinal;
const coverageEntries = entries.filter(
({ ordinal }) => ordinal > expectedCursorOrdinal && ordinal <= boundary.ordinal,
);
if (coverageEntries.length === 0) return;
await this.ports.commit({
operationId: policySkipOperationId(snapshot.sessionId, checkpoint.checkpointId),
sessionId: snapshot.sessionId,
expectedCursorOrdinal,
nextCursorOrdinal: boundary.ordinal,
coverageHash: memoryCoverageHash(coverageEntries),
items: [],
requestedItemIndexes: [],
skipReason: 'policy_denied',
trigger: 'compaction',
compactionCheckpointId: checkpoint.checkpointId,
});
return boundary.ordinal;
}
private async allowed(sessionId: string): Promise<boolean> {
return (await this.ports.readGate(sessionId)).allowed;
}
private now(): number {
return (this.ports.now ?? Date.now)();
}
}
function memoryExtractionOperationId(snapshot: MemoryExtractionSourceSnapshot): string | undefined {
const stableBoundary =
snapshot.trigger === 'remember'
? snapshot.toolCallId
: snapshot.trigger === 'extract'
? snapshot.terminalEventId
: snapshot.compactionCheckpointId;
if (!stableBoundary) return undefined;
return `memory_${createHash('sha256')
.update(
JSON.stringify({
sessionId: snapshot.sessionId,
trigger: snapshot.trigger,
stableBoundary,
...(snapshot.trigger === 'compaction'
? {}
: { runId: snapshot.runId, turnId: snapshot.turnId }),
}),
)
.digest('hex')}`;
}
function memoryCoverageHash(entries: readonly MemoryExtractionEventEntry[]): string {
return createHash('sha256')
.update(JSON.stringify(entries.map(({ ordinal, event }) => [ordinal, event.id])))
.digest('hex');
}
function memorySegmentOperationId(
operationId: string,
afterOrdinal: number,
throughOrdinal: number,
portableCompactionPrefix: boolean,
): string {
const prefix = portableCompactionPrefix ? 'memory_compaction_segment' : 'memory_segment';
return `${prefix}_${createHash('sha256')
.update(JSON.stringify({ operationId, afterOrdinal, throughOrdinal }))
.digest('hex')}`;
}
function findMemoryRangeSplit(
entries: readonly MemoryExtractionEventEntry[],
maximumSplitIndex = entries.length - 1,
accepts: (split: {
readonly first: readonly MemoryExtractionEventEntry[];
readonly second: readonly MemoryExtractionEventEntry[];
}) => boolean,
):
| {
readonly first: readonly MemoryExtractionEventEntry[];
readonly second: readonly MemoryExtractionEventEntry[];
}
| undefined {
if (entries.length < 2) return undefined;
const weights = entries.map(memoryRangeEventWeight);
const total = weights.reduce((sum, weight) => sum + weight, 0);
let prefix = 0;
const candidates = entries
.slice(1)
.map((entry, offset) => {
const index = offset + 1;
prefix += weights[offset]!;
return {
index,
turnBoundary: entries[index - 1]!.event.turnId !== entry.event.turnId,
distance: Math.abs(prefix - total / 2),
};
})
.filter(({ index }) => index <= maximumSplitIndex);
candidates.sort(
(left, right) =>
Number(right.turnBoundary) - Number(left.turnBoundary) ||
left.distance - right.distance ||
left.index - right.index,
);
// Keep only the candidate being checked; each pair copies the entire range.
for (const { index } of candidates) {
const split = { first: entries.slice(0, index), second: entries.slice(index) };
if (accepts(split)) return split;
}
return undefined;
}
function preparedMemoryRangeFits(
prepared:
| {
readonly snapshot: MemoryExtractionSourceSnapshot;
readonly coverage: MemoryCoveragePlan;
readonly firstPrompt: string;
}
| undefined,
trigger: MemoryExtractionTrigger,
): boolean {
if (!prepared) return false;
return (
(prepared.coverage.evidence.length === 0 && trigger !== 'remember') ||
memoryRequestFits(prepared.snapshot, prepared.firstPrompt, 'proposal')
);
}
function memoryRequestFits(
snapshot: MemoryExtractionSourceSnapshot,
prompt: string,
stage: 'proposal' | 'localized' | 'canonicalize',
): boolean {
const contextWindow = snapshot.sourceContextWindowTokens;
if (contextWindow === undefined) return true;
const outputReserve = memoryExtractionMaxOutputTokens(snapshot);
const inputBudget = Math.max(1, contextWindow - outputReserve);
const payloadChars =
prompt.length +
(stage === 'canonicalize'
? 0
: (snapshot.sourceSystemPrompt?.length ?? 0) +
safeJsonLength(snapshot.sourceMessages) +
safeJsonLength(snapshot.sourceTools) +
safeJsonLength(snapshot.sourceActiveTools) +
safeJsonLength(snapshot.sourceProviderOptions));
return Math.ceil(payloadChars / 4) <= inputBudget;
}
function fittingLocalizedMemoryPrompt(input: {
readonly snapshot: MemoryExtractionSourceSnapshot;
readonly trigger: MemoryExtractionTrigger;
readonly now: number;
readonly evidence: readonly MemoryCoveragePlan['evidence'][number][];
readonly interpretationContext: string;
}): { readonly prompt: string; readonly interpretationContext: string } | undefined {
const build = (interpretationContext: string): string =>
buildLocalizedMemoryProposalPrompt({
trigger: input.trigger,
now: input.now,
evidence: input.evidence,
interpretationContext,
sourceEventMessagePositions: input.snapshot.sourceEventMessagePositions,
});
const full = build(input.interpretationContext);
if (memoryRequestFits(input.snapshot, full, 'localized')) {
return { prompt: full, interpretationContext: input.interpretationContext };
}
const codePoints = Array.from(input.interpretationContext);
let low = 1;
let high = codePoints.length;
let best: { readonly prompt: string; readonly interpretationContext: string } | undefined;
while (low <= high) {
const size = Math.floor((low + high) / 2);
const context = centeredContextSlice(codePoints, size);
const prompt = build(context);
if (memoryRequestFits(input.snapshot, prompt, 'localized')) {
best = { prompt, interpretationContext: context };
low = size + 1;
} else {
high = size - 1;
}
}
return best;
}
function centeredContextSlice(codePoints: readonly string[], maximum: number): string {
if (codePoints.length <= maximum) return codePoints.join('');
const head = Math.ceil(maximum / 2);
const tail = Math.floor(maximum / 2);
return `${codePoints.slice(0, head).join('')}\n[localized context truncated]\n${
tail > 0 ? codePoints.slice(-tail).join('') : ''
}`;
}
function safeJsonLength(value: unknown): number {
try {
return JSON.stringify(value)?.length ?? 0;
} catch {
return Number.MAX_SAFE_INTEGER;
}
}
function memoryRangeEventWeight({ event }: MemoryExtractionEventEntry): number {
if (
!event.partial &&
event.content?.kind === 'text' &&
((event.role === 'user' && event.author === 'user') ||
(event.role === 'model' && event.author === 'agent'))
) {
// Text appears once in the interpretation messages and, for user text, once
// more in the bounded evidence index when it cannot be referenced by position.
return Math.max(1, event.content.text.length * (event.role === 'user' ? 2 : 1));
}
return 1;
}
function memoryEvidenceContainsSensitiveText(
evidence: readonly { readonly events: readonly MemoryExtractionEventEntry['event'][] }[],
): boolean {
return evidence.some(({ events }) =>
events.some(
({ content }) => content?.kind === 'text' && redactSecrets(content.text) !== content.text,
),
);
}
function pendingRetryOperationId(
triggerOperationId: string,
pending: PendingMemoryExtractionFailure,
): string {
return `memory_retry_${createHash('sha256')
.update(
JSON.stringify({
triggerOperationId,
firstOperationId: pending.firstOperationId,
coverageHash: pending.coverageHash,
}),
)
.digest('hex')}`;
}
function policySkipOperationId(sessionId: string, checkpointId: string): string {
return `memory_policy_skip_${createHash('sha256')
.update(JSON.stringify({ sessionId, checkpointId }))
.digest('hex')}`;
}
function validSourceContextContract(snapshot: MemoryExtractionSourceSnapshot): boolean {
if (snapshot.trigger === 'compaction') {
return (
snapshot.rebuildSourceContextFromCompactionCheckpoint === true &&
snapshot.sourceMessages.length === 0 &&
snapshot.sourceEventMessagePositions === undefined
);
}
return snapshot.rebuildSourceContextFromCompactionCheckpoint === undefined;
}
function compactionBoundaryEntry(
entries: readonly MemoryExtractionEventEntry[],
checkpoint: HistoryCompactCheckpoint,
): MemoryExtractionEventEntry | undefined {
const boundary = checkpoint.memoryExtractionBoundary;
if (!boundary) return undefined;
return entries.find(
({ event }) =>
event.id === boundary.runtimeEventId &&
event.runId === boundary.runId &&
event.turnId === boundary.turnId,
);
}
function compactionCheckpointMatchesSnapshot(
checkpoints: readonly HistoryCompactCheckpoint[],
snapshot: MemoryExtractionSourceSnapshot,
): boolean {
if (!snapshot.compactionCheckpointId || !snapshot.compactionBoundaryEventId) return false;
const checkpoint = checkpoints.find(
(candidate) => candidate.checkpointId === snapshot.compactionCheckpointId,
);
return (
checkpoint?.memoryExtractionBoundary?.runtimeEventId === snapshot.compactionBoundaryEventId
);
}
function earliestUnprocessedCompactionCheckpoint(
checkpoints: readonly HistoryCompactCheckpoint[],
entries: readonly MemoryExtractionEventEntry[],
cursorOrdinal: number,
currentBoundaryOrdinal: number,
currentCheckpointId?: string,
): HistoryCompactCheckpoint | undefined {
return checkpoints
.map((checkpoint) => ({ checkpoint, boundary: compactionBoundaryEntry(entries, checkpoint) }))
.filter(
(
candidate,
): candidate is {
checkpoint: HistoryCompactCheckpoint;
boundary: MemoryExtractionEventEntry;
} =>
candidate.boundary !== undefined &&
candidate.boundary.ordinal > cursorOrdinal &&
candidate.boundary.ordinal <= currentBoundaryOrdinal &&
candidate.checkpoint.checkpointId !== currentCheckpointId,
)
.sort((left, right) => left.boundary.ordinal - right.boundary.ordinal)[0]?.checkpoint;
}
function rebuildCompactionSnapshot(
current: MemoryExtractionSourceSnapshot,
checkpoint: HistoryCompactCheckpoint | undefined,
): MemoryExtractionSourceSnapshot | undefined {
const boundary = checkpoint?.memoryExtractionBoundary;
if (!checkpoint || !boundary || boundary.disposition === 'policy_denied') return undefined;
return {
trigger: 'compaction',
sourceHeader: current.sourceHeader,
...(current.sourceSystemPrompt ? { sourceSystemPrompt: current.sourceSystemPrompt } : {}),
sourceMessages: [],
rebuildSourceContextFromCompactionCheckpoint: true,
sourceTools: current.sourceTools,
sourceActiveTools: current.sourceActiveTools,
...(current.sourceProviderOptions
? { sourceProviderOptions: current.sourceProviderOptions }
: {}),
...(current.sourceMaxOutputTokens !== undefined
? { sourceMaxOutputTokens: current.sourceMaxOutputTokens }
: {}),
...(current.sourceContextWindowTokens !== undefined
? { sourceContextWindowTokens: current.sourceContextWindowTokens }
: {}),
sessionId: current.sessionId,
runId: current.runId,
turnId: current.turnId,
workspaceKey: current.workspaceKey,
compactionCheckpointId: checkpoint.checkpointId,
compactionBoundaryEventId: boundary.runtimeEventId,
};
}
function rebuildExplicitPendingSnapshot(
current: MemoryExtractionSourceSnapshot,
pending: PendingMemoryExtractionFailure,
boundary: MemoryExtractionEventEntry | undefined,
entries: readonly MemoryExtractionEventEntry[],
): MemoryExtractionSourceSnapshot | undefined {
if (!boundary || pending.firstTrigger === 'compaction') return undefined;
const portableCompactionSegment = pending.firstOperationId.startsWith(
'memory_compaction_segment_',
);
const shared = {
sourceHeader: current.sourceHeader,
...(!portableCompactionSegment && current.sourceSystemPrompt
? { sourceSystemPrompt: current.sourceSystemPrompt }
: {}),
sourceMessages: [],
sourceTools: portableCompactionSegment ? {} : current.sourceTools,
sourceActiveTools: portableCompactionSegment ? [] : current.sourceActiveTools,
...(!portableCompactionSegment && current.sourceProviderOptions
? { sourceProviderOptions: current.sourceProviderOptions }
: {}),
...(current.sourceMaxOutputTokens !== undefined
? { sourceMaxOutputTokens: current.sourceMaxOutputTokens }
: {}),
...(current.sourceContextWindowTokens !== undefined
? { sourceContextWindowTokens: current.sourceContextWindowTokens }
: {}),
sessionId: current.sessionId,
runId: boundary.event.runId,
turnId: boundary.event.turnId,
workspaceKey: current.workspaceKey,
};
if (pending.firstTrigger === 'remember') {
const call = entries.find(
({ ordinal, event }) =>
ordinal > boundary.ordinal &&
event.runId === boundary.event.runId &&
event.turnId === boundary.event.turnId &&
event.content?.kind === 'function_call' &&
event.content.name === MEMORY_REMEMBER_TOOL_NAME,
);
return call?.event.content?.kind === 'function_call'
? { ...shared, trigger: 'remember', toolCallId: call.event.content.id }
: undefined;
}
return { ...shared, trigger: 'extract', terminalEventId: boundary.event.id };
}
function buildMemoryRangeSnapshot(input: {
readonly snapshot: MemoryExtractionSourceSnapshot;
readonly entries: readonly MemoryExtractionEventEntry[];
readonly checkpoints: readonly HistoryCompactCheckpoint[];
readonly policyDeniedCheckpointIds: ReadonlySet<string>;
readonly afterOrdinal: number;
readonly throughOrdinal: number;
}): MemoryExtractionSourceSnapshot | undefined {
const afterEvent = input.entries.find(({ ordinal }) => ordinal === input.afterOrdinal)?.event;
const throughEvent = input.entries.find(({ ordinal }) => ordinal === input.throughOrdinal)?.event;
if (!throughEvent) return undefined;
const sourceCheckpoint = input.snapshot.compactionCheckpointId
? input.checkpoints.find(
(checkpoint) => checkpoint.checkpointId === input.snapshot.compactionCheckpointId,
)
: latestCheckpointAtOrBefore(input.checkpoints, input.entries, input.throughOrdinal);
const previousCheckpointCandidate =
input.snapshot.trigger === 'compaction'
? sourceCheckpoint?.previousCheckpointId
? input.checkpoints.find(
(checkpoint) => checkpoint.checkpointId === sourceCheckpoint.previousCheckpointId,
)
: undefined
: sourceCheckpoint;
if (
input.snapshot.trigger === 'compaction' &&
sourceCheckpoint?.previousCheckpointId &&
!previousCheckpointCandidate
) {
return undefined;
}
const previousCheckpoint =
previousCheckpointCandidate &&
checkpointSummaryIsPolicySafe(
previousCheckpointCandidate,
input.checkpoints,
input.policyDeniedCheckpointIds,
)
? previousCheckpointCandidate
: undefined;
const sourceContext = buildMemoryCompactionSourceContext(
input.entries.map(({ event }) => event),
throughEvent.id,
{
...(afterEvent ? { afterEventId: afterEvent.id } : {}),
...(previousCheckpoint ? { previousCheckpoint } : {}),
},
);
if (!sourceContext) return undefined;
return {
...input.snapshot,
...(input.snapshot.trigger === 'compaction'
? { sourceSystemPrompt: undefined, sourceProviderOptions: undefined }
: {}),
sourceMessages: sourceContext.messages,
// Automatic extraction is an isolated portable stage. Carrying the Agent's
// System Prompt or tool catalog adds no interpretation value and can consume
// the auxiliary context window before any conversation text is considered.
sourceTools: input.snapshot.trigger === 'compaction' ? {} : input.snapshot.sourceTools,
sourceActiveTools:
input.snapshot.trigger === 'compaction'
? []
: input.snapshot.sourceActiveTools.filter(
(name) => input.snapshot.sourceTools[name]?.kind !== 'provider',
),
...(sourceContext.eventMessagePositions
? { sourceEventMessagePositions: sourceContext.eventMessagePositions }
: { sourceEventMessagePositions: undefined }),
...(input.snapshot.trigger === 'compaction'
? { rebuildSourceContextFromCompactionCheckpoint: true as const }
: {}),
};
}
function isPolicyDeniedCheckpoint(
checkpoint: HistoryCompactCheckpoint,
policyDeniedCheckpointIds: ReadonlySet<string>,
): boolean {
return (
checkpoint.memoryExtractionBoundary?.disposition === 'policy_denied' ||
policyDeniedCheckpointIds.has(checkpoint.checkpointId)
);
}
function latestPolicyDeniedBoundaryOrdinal(
checkpoints: readonly HistoryCompactCheckpoint[],
entries: readonly MemoryExtractionEventEntry[],
policyDeniedCheckpointIds: ReadonlySet<string>,
throughOrdinal: number,
): number {
const ordinals = new Map(entries.map(({ ordinal, event }) => [event.id, ordinal]));
return checkpoints.reduce((latest, checkpoint) => {
if (!isPolicyDeniedCheckpoint(checkpoint, policyDeniedCheckpointIds)) return latest;
const boundaryId = checkpoint.memoryExtractionBoundary?.runtimeEventId;
const ordinal = boundaryId ? ordinals.get(boundaryId) : undefined;
return ordinal !== undefined && ordinal <= throughOrdinal ? Math.max(latest, ordinal) : latest;
}, 0);
}
function checkpointSummaryIsPolicySafe(
checkpoint: HistoryCompactCheckpoint,
checkpoints: readonly HistoryCompactCheckpoint[],
policyDeniedCheckpointIds: ReadonlySet<string>,
): boolean {
const byId = new Map(checkpoints.map((candidate) => [candidate.checkpointId, candidate]));
const seen = new Set<string>();
let current: HistoryCompactCheckpoint | undefined = checkpoint;
while (current) {
if (seen.has(current.checkpointId)) return false;
seen.add(current.checkpointId);
if (isPolicyDeniedCheckpoint(current, policyDeniedCheckpointIds)) return false;
if (!current.previousCheckpointId) return true;
current = byId.get(current.previousCheckpointId);
if (!current) return false;
}
return true;
}
function latestCheckpointAtOrBefore(
checkpoints: readonly HistoryCompactCheckpoint[],
entries: readonly MemoryExtractionEventEntry[],
throughOrdinal: number,
): HistoryCompactCheckpoint | undefined {
const ordinals = new Map(entries.map(({ ordinal, event }) => [event.id, ordinal]));
return checkpoints
.flatMap((checkpoint) => {
const ordinal = ordinals.get(checkpoint.coverage.through.runtimeEventId);
return ordinal !== undefined && ordinal <= throughOrdinal ? [{ checkpoint, ordinal }] : [];
})
.sort(
(left, right) =>
right.ordinal - left.ordinal || right.checkpoint.createdAt - left.checkpoint.createdAt,
)[0]?.checkpoint;
}
function validCompactionBootstrapOrdinal(
entries: readonly MemoryExtractionEventEntry[],
checkpoint: HistoryCompactCheckpoint,
): number | undefined {
const compactable = entries.filter(({ event }) => isHistoryCompactContentEvent(event));
const matched = matchHistoryCompactCheckpointPrefix(
checkpoint,
compactable.map(({ event }) => event),
);
if (matched.reason) return undefined;
if (checkpoint.phase === 'mid_turn' && checkpoint.headAnchor) {
const anchorIndex = entries.findIndex(
({ event }) => event.id === checkpoint.headAnchor!.runtimeEventId,
);
return anchorIndex > 0 ? entries[anchorIndex - 1]!.ordinal : undefined;
}
const throughId = matched.coveredRuntimeEvents.at(-1)?.id;
if (!throughId) return undefined;
return entries.find(({ event }) => event.id === throughId)?.ordinal;
}
function findExtractionBoundary(
entries: readonly MemoryExtractionEventEntry[],
snapshot: MemoryExtractionSourceSnapshot,
): MemoryExtractionEventEntry | undefined {
if (snapshot.trigger === 'extract') {
return entries.find(
({ event }) =>
event.id === snapshot.terminalEventId &&
event.runId === snapshot.runId &&
event.turnId === snapshot.turnId,
);
}
if (snapshot.trigger === 'compaction') {
if (!snapshot.compactionCheckpointId || !snapshot.compactionBoundaryEventId) return undefined;
return entries.find(
({ event }) =>
event.id === snapshot.compactionBoundaryEventId && event.sessionId === snapshot.sessionId,
);
}
if (!snapshot.toolCallId) return undefined;
const callIndex = entries.findIndex(
({ event }) =>
event.runId === snapshot.runId &&
event.turnId === snapshot.turnId &&
event.content?.kind === 'function_call' &&
event.content.id === snapshot.toolCallId &&
event.content.name === MEMORY_REMEMBER_TOOL_NAME,
);
if (callIndex < 1) return undefined;
for (let index = callIndex - 1; index >= 0; index -= 1) {
const entry = entries[index]!;
if (!entry.event.partial && !isMemoryEvent(entry.event)) return entry;
}
return undefined;
}
function isMemoryEvent(event: MemoryExtractionEventEntry['event']): boolean {
return (
(event.content?.kind === 'function_call' || event.content?.kind === 'function_response') &&
isMemoryToolName(event.content.name)
);
}
function memoryItemWrite(
snapshot: MemoryExtractionSourceSnapshot,
fields: AdmittedProposalFields,
requested: boolean,
): MemoryItemWrite {
const sources = new Map<string, MemoryItemWrite['sources'][number]>();
for (const event of fields.citedEvents) {
sources.set(event.id, {
sessionId: event.sessionId,
runId: event.runId,
turnId: event.turnId,
eventId: event.id,
});
}
return {
content: fields.content,
kind: fields.kind,
statementType: fields.statementType,
temporalType: fields.temporalType,
scopeType: fields.scopeType,
scopeKey: fields.scopeType === 'workspace' ? snapshot.workspaceKey : null,
eventStartedAt: fields.eventStartedAt,
eventEndedAt: fields.eventEndedAt,
observedAt: minuteTimestamp(Math.max(...fields.citedEvents.map((event) => event.ts))),
origin: requested ? 'user_requested' : 'agent_extracted',
keys: fields.keys.map(({ key, keyType }) => ({
key,
keyType,
keyOrigin: requested ? 'user' : 'llm',
})),
sources: [...sources.values()],
};
}
function rememberResultFromReceipt(
trigger: MemoryExtractionTrigger,
receipt: MemoryExtractionReceipt,
): MemoryRememberResult {
if (trigger !== 'remember') return unavailableMemoryResult();
if (receipt.status === 'discarded') return unavailableMemoryResult();
if (receipt.status === 'remembered' && receipt.requestedItems.length > 0) {
return { status: 'remembered', requestedItems: receipt.requestedItems };
}
return {
status: 'not_applicable',
requestedItems: [],
...(receipt.noOpReason ? { reason: receipt.noOpReason } : {}),
};
}
function unavailableMemoryResult(): MemoryRememberResult {
return { status: 'unavailable', requestedItems: [] };
}