blob: 4a77365ca0d3d6658cf0670d45266009e00e7811 [file]
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,
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-compact.js';
import {
matchHistoryCompactCheckpointPrefix,
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';
export type MemoryExtractionGate =
| { readonly allowed: true }
| { readonly allowed: false; readonly reason: 'disabled' | 'incognito' | 'unavailable' };
/** Exact provider-visible prefix frozen from the source request at the Tool/terminal boundary. */
export interface MemoryExtractionSourceSnapshot {
readonly trigger: MemoryExtractionTrigger;
readonly sourceHeader: Pick<SessionHeader, 'llmConnectionSlug' | 'model' | 'thinkingLevel'>;
readonly sourceSystemPrompt?: string;
readonly sourceMessages: readonly ModelMessage[];
/** 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;
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;
}
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>;
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 readLatestCompactionCheckpoint: (
sessionId: string,
) => Promise<HistoryCompactCheckpoint | undefined>;
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;
}
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 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> {
const operationId = memoryExtractionOperationId(snapshot);
if (!operationId || !(await this.allowed(snapshot.sessionId))) return unavailableMemoryResult();
const existing = await this.ports.readReceipt(operationId);
if (existing) return rememberResultFromReceipt(snapshot.trigger, existing);
if (!(await this.allowed(snapshot.sessionId))) return unavailableMemoryResult();
const entries = await this.ports.readSessionEvents(snapshot.sessionId);
const boundary = findExtractionBoundary(entries, snapshot);
if (!boundary || !(await this.allowed(snapshot.sessionId))) return unavailableMemoryResult();
if (!(await this.allowed(snapshot.sessionId))) return unavailableMemoryResult();
let cursor = await this.ports.readCursor(snapshot.sessionId);
if (!(await this.allowed(snapshot.sessionId))) return unavailableMemoryResult();
const pendingFailure = await this.ports.readPendingFailure(snapshot.sessionId);
if (!cursor && !pendingFailure) {
if (!(await this.allowed(snapshot.sessionId))) return unavailableMemoryResult();
const checkpoint = await this.ports.readLatestCompactionCheckpoint(snapshot.sessionId);
const bootstrapOrdinal = checkpoint
? 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 retryOperationId = pendingRetryOperationId(operationId, pendingFailure);
const retry = await this.processRange({
snapshot,
trigger: pendingFailure.firstTrigger,
operationId: retryOperationId,
expectedCursorOrdinal,
targetBoundaryOrdinal: pendingFailure.throughOrdinal,
expectedCoverageHash: pendingFailure.coverageHash,
entries,
prioritizeCurrentTurn: false,
});
if (retry.kind === 'blocked') return unavailableMemoryResult();
if (retry.kind === 'committed') {
expectedCursorOrdinal = retry.nextCursorOrdinal;
} else {
const settled = await this.settleCountedFailure({
snapshot,
trigger: pendingFailure.firstTrigger,
operationId: retryOperationId,
expectedCursorOrdinal,
throughOrdinal: pendingFailure.throughOrdinal,
coverageHash: pendingFailure.coverageHash,
failureClass: retry.failureClass,
});
if (!settled || settled.status !== 'discarded') return unavailableMemoryResult();
expectedCursorOrdinal = settled.cursor.processedOrdinal;
}
}
if (expectedCursorOrdinal >= boundary.ordinal) {
return snapshot.trigger === 'remember'
? { status: 'not_applicable', requestedItems: [] }
: unavailableMemoryResult();
}
const processed = await this.processRange({
snapshot,
trigger: snapshot.trigger,
operationId,
expectedCursorOrdinal,
targetBoundaryOrdinal: boundary.ordinal,
entries,
prioritizeCurrentTurn: snapshot.trigger === 'remember',
});
if (processed.kind === 'committed') {
return rememberResultFromReceipt(snapshot.trigger, processed.receipt);
}
if (processed.kind === 'counted_failure') {
await this.settleCountedFailure({
snapshot,
trigger: snapshot.trigger,
operationId,
expectedCursorOrdinal,
throughOrdinal: boundary.ordinal,
coverageHash: memoryCoverageHash(
entries.filter(
({ ordinal }) => ordinal > expectedCursorOrdinal && ordinal <= boundary.ordinal,
),
),
failureClass: processed.failureClass,
});
}
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 prioritizeCurrentTurn: 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 priorityEvidence = input.prioritizeCurrentTurn
? projectMemoryExtractionEvidence(
pendingEntries
.filter(
({ event }) =>
event.runId === input.snapshot.runId && event.turnId === input.snapshot.turnId,
)
.map(({ event }) => event),
)
: [];
const requestedEvidenceContainsSensitiveText =
input.trigger === 'remember' && memoryEvidenceContainsSensitiveText(priorityEvidence);
const coverage = planMemoryCoverage({
pendingEntries,
...(input.trigger === 'remember' ? { priorityEvidence } : {}),
sourceEventMessagePositions: input.snapshot.sourceEventMessagePositions,
sourceMessages: input.snapshot.sourceMessages,
});
if (!coverage || coverage.entries.length === 0) {
return { kind: 'counted_failure', failureClass: 'evidence' };
}
return this.processCoverage({
snapshot: input.snapshot,
trigger: input.trigger,
operationId: input.operationId,
expectedCursorOrdinal: input.expectedCursorOrdinal,
coverage,
entries: input.entries,
boundaryOrdinal: input.targetBoundaryOrdinal,
coverageHash,
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 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 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;
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 === 'extract' &&
(first.status !== 'complete' ||
first.requestedStatus !== 'not_applicable' ||
first.requestedItems.length > 0)
) {
return { kind: 'counted_failure', failureClass: 'schema' };
}
requestedItems = first.requestedItems;
incidentalItems = first.incidentalItems;
requestedStatus = first.requestedStatus;
if (first.status === 'search_required') {
if (trigger !== 'remember' || !(await this.allowed(snapshot.sessionId))) {
return { kind: 'blocked' };
}
const localizedEntries = searchSameSessionMemoryHistory(
input.entries,
input.boundaryOrdinal,
first.search,
);
if (localizedEntries.length === 0) {
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 (memoryEvidenceContainsSensitiveText(localizedEvidence)) {
return this.commitSensitiveNoOp(input);
}
if (budget.remaining <= 0) {
return { kind: 'counted_failure', failureClass: 'localization' };
}
const localizedCall = await this.callModel(
snapshot,
buildLocalizedMemoryProposalPrompt({
now: this.now(),
evidence: localizedEvidence,
sourceEventMessagePositions: snapshot.sourceEventMessagePositions,
}),
'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' };
}
requestedItems = localized.requestedItems;
requestedStatus = localized.status;
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))),
};
}),
})),
});
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,
});
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,
});
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 (!(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,
});
}
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.terminalEventId;
if (!stableBoundary) return undefined;
return `memory_${createHash('sha256')
.update(
JSON.stringify({
sessionId: snapshot.sessionId,
runId: snapshot.runId,
turnId: snapshot.turnId,
trigger: snapshot.trigger,
stableBoundary,
}),
)
.digest('hex')}`;
}
function memoryCoverageHash(entries: readonly MemoryExtractionEventEntry[]): string {
return createHash('sha256')
.update(JSON.stringify(entries.map(({ ordinal, event }) => [ordinal, event.id])))
.digest('hex');
}
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 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.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 === 'extract') 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: [] };
}