blob: 686d26e930fb73ee3f5f3e5f1e840eef8355958d [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 type { AgentRunEvent, AgentRunEventType, AgentRunProjectionKey } from '@maka/core/agent-run';
import type { RuntimeEvent, ToolBoundaryProtocol } from '@maka/core/runtime-event';
import type { RuntimeContinuationAuthorityStore } from '@maka/core/runtime-event-store';
import type { ImmutableRuntimePrefixProofV1 } from '@maka/core/runtime-boundary';
import type { RuntimeTranscriptQueries } from './runtime-transcript-query.js';
import type {
RuntimeInvocationPageInput,
RuntimeInvocationPageResult,
RuntimeInvocationRecord,
RuntimeInvocationSearchResult,
} from '@maka/core/runtime-invocation';
import type { SessionHeader, SessionSummary, StoredMessage, TurnRecord } from '@maka/core/session';
import type { SessionListFilter } from '@maka/core/runtime-inputs';
import {
createSqliteAgentRunStore,
type AdmitRootTurnInput,
type AdmitRootTurnResult,
type CommitRootTurnStartRejectionInput,
type BoundedEvidenceReadResult,
type DurableAgentRunStore,
type DurableRuntimeEventStore,
type EvidenceReadBudget,
type RootTurnAdmission,
type RootTurnSourceMessageReceipt,
} from './agent-run-store.js';
import type { ConversationOperationalStateStore } from './conversation-operational-state.js';
import { createSessionStore } from './session-store.js';
import type { SessionAuthorityStore } from './session-store-contract.js';
import {
assertStorageRootLease,
assertStorageRootLeaseActive,
runWithStorageRootLease,
StorageRootAuthorityError,
type StorageRootKind,
type StorageRootLease,
} from './root-authority.js';
import {
closeSqliteInteractionStoreFacade,
openSqliteInteractiveInteractionStoreForRead,
openSqliteInteractiveInteractionStoreForWrite,
createSqliteInteractionStore,
type InteractiveInteractionStoreReaderFacade,
type InteractiveInteractionStoreWriterFacade,
} from './interaction-store.js';
import { openRuntimeEventReadPersistence } from './runtime-event-persistence.js';
import type {
CommitToolOutcomeInput,
CommitToolPreparedInput,
SessionRuntimeEventEntry,
ToolCommitResult,
ToolOperationRecord,
ImmutableRuntimePrefixProofReadBudget,
} from './runtime-event-store-contract.js';
import { localExecutionPersistenceProvider } from './local-execution-persistence.js';
import {
EXECUTION_GRAPH_METHODS,
type ExecutionGraphStore,
type ExecutionPersistenceProvider,
} from './execution-persistence-provider.js';
import {
openInteractiveGoalAuthorityForWrite,
createSqliteGoalAuthority,
type InteractiveGoalAuthorityWriter,
} from './goal-authority.js';
export type {
ExecutionPersistenceProvider,
ExecutionGraphStore,
} from './execution-persistence-provider.js';
const executionStoreProvidersByLease = new WeakMap<object, ExecutionPersistenceProvider>();
const failedExecutionLeases = new WeakSet<object>();
const executionStoresWriterBrand: unique symbol = Symbol('ExecutionStoresWriter');
const executionStoresReaderBrand: unique symbol = Symbol('ExecutionStoresReader');
const executionStoresWriterKinds = new WeakMap<object, StorageRootKind>();
const executionStoresReaderKinds = new WeakMap<object, StorageRootKind>();
const executionStoresWritersByLease = new WeakMap<object, object>();
const executionStoresWritersOpeningByLease = new WeakMap<object, Promise<void>>();
export {
normalizeRootTurnAdmissionPayload,
rootTurnAdmissionRecordFits,
rootTurnSourceMessagePayloadsEqual,
} from './agent-run-store.js';
export { isSessionNotFoundError } from './session-store-contract.js';
export {
SessionMetadataConflictError,
SessionMetadataVersionConflictError,
} from './session-store-contract.js';
export type {
AdmitRootTurnInput,
AdmitRootTurnResult,
CommitRootTurnStartRejectionInput,
CommitRootTurnStartRejectionResult,
BoundedEvidenceReadResult,
EvidenceReadBudget,
ImmutableSteeringMessageProof,
RootTurnAdmission,
RootTurnAdmissionAuthorization,
RootTurnAdmissionStore,
RootTurnStartRejectionStore,
RootTurnSourceMessage,
RootTurnSourceMessageReceipt,
RootTurnStartRejection,
RuntimeEventScanBudget,
RuntimeEventScanResult,
} from './agent-run-store.js';
export type {
MarkMessagesHandedOffInput,
MessageAdmissionStore,
PendingMessageAdmission,
ProvenSteeringMessageHandoff,
} from './message-admission-store.js';
export { submittedTurnIntentsEqual } from './submitted-turn-intent.js';
export type { SubmittedTurnIntent } from './submitted-turn-intent.js';
export type {
CreateStableSessionRequest,
ProbeSessionRemovalResult,
ExternalSessionImportLookupResult,
SessionCatalogPageCursor,
SessionCatalogPageResult,
SessionCatalogRecord,
SessionHeaderSnapshot,
SessionTranscriptMessageLookupRequest,
SessionTranscriptPageRequest,
CoordinationTranscriptReference,
SessionTranscriptRecordScanPage,
SessionTranscriptRecordScanRequest,
SessionTranscriptStoragePage,
SessionTranscriptStorageFragment,
SessionTurnContribution,
SessionTurnContributionPage,
} from './session-store-contract.js';
export type ExecutionSessionWriter = SessionAuthorityStore;
export type { RuntimeTranscriptRun, RuntimeTranscriptTurn } from './runtime-transcript-query.js';
export type ExecutionAgentRunWriter = DurableAgentRunStore;
export type ExecutionRuntimeEventWriter = DurableRuntimeEventStore &
RuntimeTranscriptQueries &
RuntimeContinuationAuthorityStore & {
readImmutableRuntimePrefixProof(
input: { sessionId: string; runId: string; upToEventSeq?: number },
budget: ImmutableRuntimePrefixProofReadBudget,
): Promise<ImmutableRuntimePrefixProofV1>;
readonly toolBoundaryProtocol: ToolBoundaryProtocol;
commitToolPrepared(input: CommitToolPreparedInput): Promise<ToolCommitResult>;
commitToolOutcome(input: CommitToolOutcomeInput): Promise<ToolCommitResult>;
listUnsettledToolOperations(sessionId: string): Promise<ToolOperationRecord[]>;
appendRuntimePartialBatch(
sessionId: string,
runId: string,
events: readonly RuntimeEvent[],
): Promise<void>;
readSessionRuntimeEventEntries(sessionId: string): Promise<SessionRuntimeEventEntry[]>;
/** Called once per Session after each write that committed RuntimeEvents to it. */
subscribeRuntimeEventCommits(listener: (sessionId: string) => void): () => void;
listSessionsWithRuntimeEventText(
sessionIds: readonly string[],
terms: readonly string[],
): Promise<string[]>;
countRuntimeEventMessages(sessionIds: readonly string[]): Promise<number>;
};
interface ExecutionStoresWriterBase<K extends StorageRootKind> {
readonly kind: K;
readonly [executionStoresWriterBrand]: K;
purgeConversationOperationalState(sessionId: string): Promise<void>;
readonly sessionStore: Readonly<ExecutionSessionWriter>;
readonly agentRunStore: Readonly<ExecutionAgentRunWriter>;
readonly runtimeEventStore: Readonly<ExecutionRuntimeEventWriter>;
}
export interface InteractiveExecutionStoresWriter extends ExecutionStoresWriterBase<'interactive'> {
readonly graphControlStore: ExecutionGraphStore;
readonly goalStore: InteractiveGoalAuthorityWriter;
readonly interactionStore: InteractiveInteractionStoreWriterFacade;
}
interface ExecutionStoresWriters {
readonly interactive: InteractiveExecutionStoresWriter;
}
export type ExecutionStoresWriter<K extends StorageRootKind> = ExecutionStoresWriters[K];
export interface ExecutionSessionReader {
list(filter?: SessionListFilter): Promise<SessionSummary[]>;
readHeader(sessionId: string): Promise<SessionHeader>;
readMessages(sessionId: string): Promise<StoredMessage[]>;
listTurns(sessionId: string): Promise<TurnRecord[]>;
close?(): Promise<void>;
}
export interface ExecutionAgentRunReader {
readEvents(sessionId: string, runId: string): Promise<AgentRunEvent[]>;
readEventsBounded(
sessionId: string,
runId: string,
budget: EvidenceReadBudget,
): Promise<BoundedEvidenceReadResult<AgentRunEvent>>;
readEventsByTypeBounded(
sessionId: string,
runId: string,
type: AgentRunEventType,
budget: EvidenceReadBudget,
): Promise<BoundedEvidenceReadResult<AgentRunEvent>>;
readEventProjection(
sessionId: string,
type: AgentRunProjectionKey,
): Promise<AgentRunEvent | null | undefined>;
readRootTurnAdmission(sessionId: string, turnId: string): Promise<RootTurnAdmission | undefined>;
readRootTurnContinuationAdmission(
sessionId: string,
sourceTurnId: string,
sourceRunId: string,
): Promise<RootTurnAdmission | undefined>;
readRootTurnSourceMessageReceipt(
sessionId: string,
sourceMessageId: string,
): Promise<RootTurnSourceMessageReceipt | undefined>;
}
export interface ExecutionRuntimeEventReader {
/**
* A Session's run inventory, read from its canonical events. This is the
* definition of the inventory, not a cache of it, so nothing writes or
* repairs it.
*/
listSessionInvocations(sessionId: string): Promise<RuntimeInvocationRecord[]>;
readRunInvocation(sessionId: string, runId: string): Promise<RuntimeInvocationRecord | undefined>;
listSessionInvocationsBounded(
sessionId: string,
limit: number,
): Promise<RuntimeInvocationSearchResult>;
listSessionInvocationsPage(
sessionId: string,
input: RuntimeInvocationPageInput,
): Promise<RuntimeInvocationPageResult>;
readInvocation(sessionId: string, invocationId: string): Promise<RuntimeInvocationRecord>;
readRuntimeEvents(sessionId: string, runId: string): Promise<RuntimeEvent[]>;
readRuntimeEventsBounded(
sessionId: string,
runId: string,
budget: EvidenceReadBudget,
): Promise<BoundedEvidenceReadResult<RuntimeEvent>>;
readImmutableRuntimeEvents(sessionId: string, runId: string): Promise<RuntimeEvent[]>;
readSessionRuntimeEvents(sessionId: string): Promise<RuntimeEvent[]>;
/** Session-wide events with the ordinal that fixes their transcript order. */
readSessionRuntimeEventEntries(
sessionId: string,
): Promise<ReadonlyArray<{ ordinal: number; event: RuntimeEvent }>>;
/** Recall's narrowing over the ledger; see `RuntimeEventStore`. */
listSessionsWithRuntimeEventText(
sessionIds: readonly string[],
terms: readonly string[],
): Promise<string[]>;
countRuntimeEventMessages(sessionIds: readonly string[]): Promise<number>;
}
interface ExecutionStoresReaderBase<K extends StorageRootKind> {
readonly kind: K;
readonly [executionStoresReaderBrand]: K;
readonly sessionStore: Readonly<ExecutionSessionReader>;
readonly agentRunStore: Readonly<ExecutionAgentRunReader>;
readonly runtimeEventStore: Readonly<ExecutionRuntimeEventReader>;
}
export interface InteractiveExecutionStoresReader extends ExecutionStoresReaderBase<'interactive'> {
readonly interactionStore: InteractiveInteractionStoreReaderFacade;
}
interface ExecutionStoresReaders {
readonly interactive: InteractiveExecutionStoresReader;
}
export type ExecutionStoresReader<K extends StorageRootKind> = ExecutionStoresReaders[K];
export function authenticateExecutionStoresWriter<K extends StorageRootKind>(
stores: ExecutionStoresWriter<K>,
expectedKind: K,
): ExecutionStoresWriter<K> {
if (executionStoresWriterKinds.get(stores) !== expectedKind) {
throw invalidExecutionStores(expectedKind, 'write');
}
return stores;
}
export function authenticateExecutionStoresReader<K extends StorageRootKind>(
stores: ExecutionStoresReader<K>,
expectedKind: K,
): ExecutionStoresReader<K> {
if (executionStoresReaderKinds.get(stores) !== expectedKind) {
throw invalidExecutionStores(expectedKind, 'read');
}
return stores;
}
export async function openInteractiveExecutionStoresForWrite(
lease: StorageRootLease<'interactive', 'write'>,
provider: ExecutionPersistenceProvider = localExecutionPersistenceProvider,
): Promise<ExecutionStoresWriter<'interactive'>> {
await assertStorageRootLease(lease, 'interactive', 'write');
if (failedExecutionLeases.has(lease)) {
throw new StorageRootAuthorityError(
'invalid_lease',
'Execution persistence requires a fresh owner after an uncertain open or close',
);
}
const selected = executionStoreProvidersByLease.get(lease);
if (selected && selected !== provider) {
throw new StorageRootAuthorityError(
'invalid_lease',
'A different execution provider already owns this lease',
);
}
const existing = executionStoresWritersByLease.get(lease);
if (existing) {
if (executionStoresWriterKinds.get(existing) !== 'interactive')
throw invalidExecutionStores('interactive', 'write');
return existing as InteractiveExecutionStoresWriter;
}
const opening = executionStoresWritersOpeningByLease.get(lease);
if (opening) {
await opening;
return openInteractiveExecutionStoresForWrite(lease, provider);
}
executionStoreProvidersByLease.set(lease, provider);
let releaseOpening!: () => void;
const openingGate = new Promise<void>((resolve) => {
releaseOpening = resolve;
});
executionStoresWritersOpeningByLease.set(lease, openingGate);
try {
return await createExecutionStoresForWrite(lease, provider);
} catch (error) {
if (!executionStoresWritersByLease.has(lease) && !failedExecutionLeases.has(lease))
executionStoreProvidersByLease.delete(lease);
throw error;
} finally {
executionStoresWritersOpeningByLease.delete(lease);
releaseOpening();
}
}
async function createExecutionStoresForWrite(
lease: StorageRootLease<'interactive', 'write'>,
provider: ExecutionPersistenceProvider,
): Promise<InteractiveExecutionStoresWriter> {
const kind = 'interactive' as const;
const persistence = await runWithStorageRootLease(lease, kind, 'write', () =>
provider.open({
rootId: lease.rootId,
canonicalPath: lease.canonicalPath,
}),
).catch((error: unknown) => {
// An unsuccessful factory must clean up its own partial handles. Until a
// fresh owner is acquired, do not assume an unknown factory failure did so.
failedExecutionLeases.add(lease);
throw error;
});
let closed = false;
let closeTask: Promise<void> | undefined;
const active = new Set<Promise<unknown>>();
const subscriptions = new Set<() => void>();
const run = <T>(operation: () => Promise<T>): Promise<T> => {
if (closed) return Promise.reject(invalidExecutionStores(kind, 'write'));
const pending = runWithStorageRootLease(lease, kind, 'write', () => {
if (closed) throw invalidExecutionStores(kind, 'write');
return operation();
});
active.add(pending);
void pending.finally(() => active.delete(pending)).catch(() => undefined);
return pending;
};
const sessionStore = persistence.sessionStore;
const agentRunStore = persistence.agentRunStore;
const runtimeEventStore = persistence.runtimeEventStore;
const runtimePersistence = { runtimeCommitStore: runtimeEventStore };
const conversationOperationalStateStore = {
purge: (sessionId: string) => persistence.purgeConversationOperationalState(sessionId),
};
let interactionStore: InteractiveInteractionStoreWriterFacade | undefined;
let goalStore: InteractiveGoalAuthorityWriter | undefined;
const releaseChildBindings: Array<() => void> = [];
const retainUntilGroupClose = (release: () => void) => releaseChildBindings.push(release);
try {
await assertStorageRootLease(lease, kind, 'write');
await sessionStore.ready();
await agentRunStore.ready?.();
interactionStore = await openSqliteInteractiveInteractionStoreForWrite(
lease,
() =>
new Proxy(persistence.interactionStore, {
get(target, property, receiver) {
if (property === 'close') return () => {};
const value = Reflect.get(target, property, receiver);
return typeof value === 'function'
? (...args: unknown[]) => run(async () => Reflect.apply(value, target, args))
: value;
},
}),
provider === localExecutionPersistenceProvider ? createSqliteInteractionStore : provider,
retainUntilGroupClose,
);
goalStore = await openInteractiveGoalAuthorityForWrite(
lease,
() => ({
list: () => run(async () => persistence.goalStore.list()),
read: (sessionId) => run(async () => persistence.goalStore.read(sessionId)),
commit: (input) => run(async () => persistence.goalStore.commit(input)),
// The group owns the backend handle; closing this facade only revokes it.
close: () => {},
}),
provider === localExecutionPersistenceProvider ? createSqliteGoalAuthority : provider,
retainUntilGroupClose,
);
} catch (error) {
closed = true;
const failures: unknown[] = [error];
try {
if (interactionStore) closeSqliteInteractionStoreFacade(interactionStore);
} catch (closeError) {
failures.push(closeError);
}
try {
await goalStore?.close();
} catch (closeError) {
failures.push(closeError);
}
try {
await persistence.close();
} catch (closeError) {
failures.push(closeError);
}
if (failures.length > 1) {
failedExecutionLeases.add(lease);
throw new AggregateError(failures, 'Unable to compose execution persistence');
}
for (const release of releaseChildBindings) release();
throw error;
}
const close = () =>
(closeTask ??= (async () => {
closed = true;
executionStoresWriterKinds.delete(stores);
const errors: unknown[] = [];
for (const unsubscribe of subscriptions) {
try {
unsubscribe();
} catch (error) {
errors.push(error);
}
}
subscriptions.clear();
await Promise.allSettled([...active]);
try {
await goalStore!.close();
} catch (error) {
errors.push(error);
}
try {
closeSqliteInteractionStoreFacade(interactionStore!);
} catch (error) {
errors.push(error);
}
try {
await persistence.close();
} catch (error) {
errors.push(error);
}
// Failed close retains the closed owner, so another backend cannot open over it.
if (errors.length) throw new AggregateError(errors, 'Unable to close execution persistence');
for (const release of releaseChildBindings) release();
if (executionStoresWritersByLease.get(lease) === stores) {
executionStoresWritersByLease.delete(lease);
executionStoreProvidersByLease.delete(lease);
}
})());
const graphMethods = Object.fromEntries(
EXECUTION_GRAPH_METHODS.map((name) => [
name,
(...args: unknown[]) =>
run(() =>
Reflect.apply(persistence.graphControlStore[name], persistence.graphControlStore, args),
),
]),
) as Omit<ExecutionGraphStore, 'close'>;
const graphControlStore: ExecutionGraphStore = Object.freeze({
...graphMethods,
close: () => {},
});
const stores: InteractiveExecutionStoresWriter = {
interactionStore,
graphControlStore,
goalStore,
kind,
[executionStoresWriterBrand]: kind,
purgeConversationOperationalState: (sessionId) =>
run(() => conversationOperationalStateStore.purge(sessionId)),
sessionStore: {
ready: () => run(() => sessionStore.ready()),
create: (input, initialBoundary) => run(() => sessionStore.create(input, initialBoundary)),
createImportedSession: (input, messages, externalOrigin, options) =>
run(() => sessionStore.createImportedSession(input, messages, externalOrigin, options)),
lookupExternalSessionImports: (adapterId, sourceSessionIds, recentSessionIdLimit) =>
run(() =>
sessionStore.lookupExternalSessionImports(
adapterId,
sourceSessionIds,
recentSessionIdLimit,
),
),
probeStableSessionCreate: (sessionId, requestFingerprint) =>
run(() => sessionStore.probeStableSessionCreate(sessionId, requestFingerprint)),
createStableSession: (request, initialBoundary) =>
run(() => sessionStore.createStableSession(request, initialBoundary)),
assignWorkHubMessage: (request) => run(() => sessionStore.assignWorkHubMessage(request)),
readWorkHubAssignment: (actionId) => run(() => sessionStore.readWorkHubAssignment(actionId)),
readActiveWorkHubAssignmentsByTarget: (targetSessionIds, maxAssignmentsPerTarget) =>
run(() =>
sessionStore.readActiveWorkHubAssignmentsByTarget(
targetSessionIds,
maxAssignmentsPerTarget,
),
),
readWorkHubReplacement: (delegationId) =>
run(() => sessionStore.readWorkHubReplacement(delegationId)),
readWorkHubReplacementAbort: (delegationId) =>
run(() => sessionStore.readWorkHubReplacementAbort(delegationId)),
readWorkHubSupersession: (delegationId) =>
run(() => sessionStore.readWorkHubSupersession(delegationId)),
readWorkHubStopRequest: (delegationId) =>
run(() => sessionStore.readWorkHubStopRequest(delegationId)),
readWorkHubStopResolution: (delegationId) =>
run(() => sessionStore.readWorkHubStopResolution(delegationId)),
claimWorkHubAction: (claim) => run(() => sessionStore.claimWorkHubAction(claim)),
readWorkHubActionClaim: (actionId) =>
run(() => sessionStore.readWorkHubActionClaim(actionId)),
discardStableConversationCopy: (sessionId, requestFingerprint) =>
run(() => sessionStore.discardStableConversationCopy(sessionId, requestFingerprint)),
createSubagent: (input, initialBoundary) =>
run(() => sessionStore.createSubagent(input, initialBoundary)),
createAgentGraphOperator: (input, request, expectedRevision, initialBoundary) =>
run(() =>
sessionStore.createAgentGraphOperator(input, request, expectedRevision, initialBoundary),
),
readExecutionBoundary: (sessionId) =>
run(() => sessionStore.readExecutionBoundary(sessionId)),
createSandboxBoundaryRequest: (input) =>
run(() => sessionStore.createSandboxBoundaryRequest(input)),
readSandboxBoundaryRequest: (sessionId, requestId) =>
run(() => sessionStore.readSandboxBoundaryRequest(sessionId, requestId)),
listPendingSandboxBoundaryRequests: (sessionId) =>
run(() => sessionStore.listPendingSandboxBoundaryRequests(sessionId)),
listSandboxBoundaryRestartClosures: (sessionId) =>
run(() => sessionStore.listSandboxBoundaryRestartClosures(sessionId)),
hasExplicitSandboxBoundaryDenial: (identities) =>
run(() => sessionStore.hasExplicitSandboxBoundaryDenial(identities)),
settleSandboxBoundaryRequest: (input) =>
run(() => sessionStore.settleSandboxBoundaryRequest(input)),
setExecutionBoundaryKind: (sessionId, boundaryKind, projection) =>
run(() => sessionStore.setExecutionBoundaryKind(sessionId, boundaryKind, projection)),
list: (filter) => run(() => sessionStore.list(filter)),
listCatalogPage: (filter, cursor, limit, expectedRevision) =>
run(() => sessionStore.listCatalogPage(filter, cursor, limit, expectedRevision)),
listHeaders: () => run(() => sessionStore.listHeaders()),
listForRecovery: () => run(() => sessionStore.listForRecovery()),
readHeaderSnapshot: (sessionId) => run(() => sessionStore.readHeaderSnapshot(sessionId)),
readHeaderRecordSnapshot: (sessionId) =>
run(() => sessionStore.readHeaderRecordSnapshot(sessionId)),
readCatalogRecord: (sessionId, roleScope) =>
run(() => sessionStore.readCatalogRecord(sessionId, roleScope)),
probeSessionRemoval: (sessionId) => run(() => sessionStore.probeSessionRemoval(sessionId)),
readMessagesSnapshot: (sessionId) => run(() => sessionStore.readMessagesSnapshot(sessionId)),
readTranscriptMessagesSnapshot: (sessionId, request) =>
run(() => sessionStore.readTranscriptMessagesSnapshot(sessionId, request)),
readCoordinationTranscriptIndexState: () =>
run(() => sessionStore.readCoordinationTranscriptIndexState()),
appendCoordinationTranscriptIndex: (records) =>
run(() => sessionStore.appendCoordinationTranscriptIndex(records)),
readCoordinationTranscriptIndex: (request) =>
run(() => sessionStore.readCoordinationTranscriptIndex(request)),
readTranscriptHighWaterSnapshot: (sessionId) =>
run(() => sessionStore.readTranscriptHighWaterSnapshot(sessionId)),
listTurnsSnapshot: (sessionId) => run(() => sessionStore.listTurnsSnapshot(sessionId)),
readHeader: (sessionId) => run(() => sessionStore.readHeader(sessionId)),
readMessages: (sessionId) => run(() => sessionStore.readMessages(sessionId)),
readMessagesAfter: (sessionId, request) =>
run(() => sessionStore.readMessagesAfter(sessionId, request)),
listTurns: (sessionId) => run(() => sessionStore.listTurns(sessionId)),
appendMessage: (sessionId, message) =>
run(() => sessionStore.appendMessage(sessionId, message)),
appendMessages: (sessionId, messages) =>
run(() => sessionStore.appendMessages(sessionId, messages)),
commitMessageCatalogProjection: (sessionId, message) =>
run(() => sessionStore.commitMessageCatalogProjection(sessionId, message)),
commitMessageAdmission: (admission) =>
run(() => sessionStore.commitMessageAdmission(admission)),
readMessageAdmission: (sessionId, messageId) =>
run(() => sessionStore.readMessageAdmission(sessionId, messageId)),
hasCancelledMessageAdmission: (sessionId, messageId) =>
run(() => sessionStore.hasCancelledMessageAdmission(sessionId, messageId)),
claimMessageAdmissionCancellation: (sessionId, messageId, claimId) =>
run(() => sessionStore.claimMessageAdmissionCancellation(sessionId, messageId, claimId)),
listMessageAdmissions: (sessionId) =>
run(() => sessionStore.listMessageAdmissions(sessionId)),
markMessagesHandedOff: (input) => run(() => sessionStore.markMessagesHandedOff(input)),
updateMessageAdmission: (admission) =>
run(() => sessionStore.updateMessageAdmission(admission)),
reorderMessageAdmissions: (sessionId, messageIds, disposition) =>
run(() => sessionStore.reorderMessageAdmissions(sessionId, messageIds, disposition)),
cancelMessageAdmissions: (sessionId, messageIds) =>
run(() => sessionStore.cancelMessageAdmissions(sessionId, messageIds)),
subscribeTranscriptChanges: (listener) => {
if (closed) throw invalidExecutionStores(kind, 'write');
assertStorageRootLeaseActive(lease, kind, 'write');
const unsubscribe = sessionStore.subscribeTranscriptChanges((sessionId) => {
if (!closed) listener(sessionId);
});
subscriptions.add(unsubscribe);
return () => {
subscriptions.delete(unsubscribe);
unsubscribe();
};
},
updateHeader: (sessionId, patch) => run(() => sessionStore.updateHeader(sessionId, patch)),
updateHeaderVersioned: (sessionId, patch, expectedRevision) =>
run(() => sessionStore.updateHeaderVersioned(sessionId, patch, expectedRevision)),
updateSessionConfiguration: (sessionId, input) =>
run(() => sessionStore.updateSessionConfiguration(sessionId, input)),
setFlagged: (sessionId, isFlagged) =>
run(() => sessionStore.setFlagged(sessionId, isFlagged)),
rename: (sessionId, name) => run(() => sessionStore.rename(sessionId, name)),
setGeneratedTitleIfAbsent: (sessionId, title) =>
run(() => sessionStore.setGeneratedTitleIfAbsent(sessionId, title)),
remove: (sessionId) => run(() => sessionStore.remove(sessionId)),
setSessionsArchivedVersioned: (sessions, isArchived) =>
run(() => sessionStore.setSessionsArchivedVersioned(sessions, isArchived)),
removeSessionsVersioned: (sessions, archiveSessions) =>
run(() => sessionStore.removeSessionsVersioned(sessions, archiveSessions)),
reconcileOrphanedAgentGraphRetirements: () =>
run(() => sessionStore.reconcileOrphanedAgentGraphRetirements()),
listPendingSessionRetirementCleanupIds: (sessionId) =>
run(() => sessionStore.listPendingSessionRetirementCleanupIds(sessionId)),
completeSessionRetirementCleanup: (sessionId) =>
run(() => sessionStore.completeSessionRetirementCleanup(sessionId)),
close,
},
agentRunStore: {
appendEvent: (sessionId, runId, event, options) =>
run(() => agentRunStore.appendEvent(sessionId, runId, event, options)),
readEvents: (sessionId, runId) => run(() => agentRunStore.readEvents(sessionId, runId)),
readEventsBounded: (sessionId, runId, budget) =>
run(() => agentRunStore.readEventsBounded(sessionId, runId, budget)),
readEventsByTypeBounded: (sessionId, runId, type, budget) =>
run(() => agentRunStore.readEventsByTypeBounded(sessionId, runId, type, budget)),
readEventsForRecovery: (sessionId, runId) =>
run(() => agentRunStore.readEventsForRecovery(sessionId, runId)),
readEventsForEvidence: (sessionId, runId) =>
run(() => agentRunStore.readEventsForEvidence(sessionId, runId)),
readEventProjection: (sessionId, type) =>
run(() => agentRunStore.readEventProjection(sessionId, type)),
readEventLedgerRevision: (sessionId) =>
run(() => agentRunStore.readEventLedgerRevision(sessionId)),
repairEventProjection: (sessionId, type, event, options) =>
run(() => agentRunStore.repairEventProjection(sessionId, type, event, options)),
admitRootTurn: (input: AdmitRootTurnInput): Promise<AdmitRootTurnResult> =>
run(() => agentRunStore.admitRootTurn(input)),
readRootTurnAdmission: (sessionId, turnId) =>
run(() => agentRunStore.readRootTurnAdmission(sessionId, turnId)),
readRootTurnContinuationAdmission: (sessionId, sourceTurnId, sourceRunId) =>
run(() =>
agentRunStore.readRootTurnContinuationAdmission(sessionId, sourceTurnId, sourceRunId),
),
readRootTurnStartRejection: (sessionId, turnId) =>
run(() => agentRunStore.readRootTurnStartRejection(sessionId, turnId)),
commitRootTurnStartRejection: (input: CommitRootTurnStartRejectionInput) =>
run(() => agentRunStore.commitRootTurnStartRejection(input)),
readRootTurnSourceMessageReceipt: (sessionId, sourceMessageId) =>
run(() => agentRunStore.readRootTurnSourceMessageReceipt(sessionId, sourceMessageId)),
listRootTurnAdmissionsForRecovery: (sessionId) =>
run(() => agentRunStore.listRootTurnAdmissionsForRecovery(sessionId)),
},
runtimeEventStore: {
durability: runtimeEventStore.durability,
continuationAuthorityCapability: runtimeEventStore.continuationAuthorityCapability,
toolBoundaryProtocol: runtimePersistence.runtimeCommitStore.toolBoundaryProtocol,
appendRuntimeEvent: (sessionId, runId, event, options) =>
run(() => runtimeEventStore.appendRuntimeEvent(sessionId, runId, event, options)),
appendRuntimePartialBatch: (sessionId, runId, events) =>
run(() => runtimeEventStore.appendRuntimePartialBatch(sessionId, runId, events)),
importConversationCopyRuntimeEvents: (sessionId, batches) =>
run(() => runtimeEventStore.importConversationCopyRuntimeEvents(sessionId, batches)),
ensureTerminalRuntimeEventDurable: (sessionId, runId, event) =>
run(() => runtimeEventStore.ensureTerminalRuntimeEventDurable(sessionId, runId, event)),
readRuntimeEvents: (sessionId, runId) =>
run(() => runtimeEventStore.readRuntimeEvents(sessionId, runId)),
scanRuntimeEvents: (sessionId, runId, budget, visit) =>
run(() => runtimeEventStore.scanRuntimeEvents(sessionId, runId, budget, visit)),
readRuntimeEventsBounded: (sessionId, runId, budget) =>
run(() => runtimeEventStore.readRuntimeEventsBounded(sessionId, runId, budget)),
readImmutableRuntimeEvents: (sessionId, runId) =>
run(() => runtimeEventStore.readImmutableRuntimeEvents(sessionId, runId)),
readImmutableRuntimePrefix: (input) =>
run(() => runtimeEventStore.readImmutableRuntimePrefix(input)),
readImmutableRuntimePrefixProof: (input, budget) =>
run(() => runtimeEventStore.readImmutableRuntimePrefixProof(input, budget)),
listSessionInvocations: (sessionId) =>
run(() => runtimeEventStore.listSessionInvocations(sessionId)),
readRunInvocation: (sessionId, runId) =>
run(() => runtimeEventStore.readRunInvocation(sessionId, runId)),
listSessionInvocationsBounded: (sessionId, limit) =>
run(() => runtimeEventStore.listSessionInvocationsBounded(sessionId, limit)),
listSessionInvocationsPage: (sessionId, input) =>
run(() => runtimeEventStore.listSessionInvocationsPage(sessionId, input)),
readInvocation: (sessionId, invocationId) =>
run(() => runtimeEventStore.readInvocation(sessionId, invocationId)),
readSessionRuntimeEvents: (sessionId) =>
run(() => runtimeEventStore.readSessionRuntimeEvents(sessionId)),
readSessionRuntimeEventEntries: (sessionId) =>
run(() => runtimeEventStore.readSessionRuntimeEventEntries(sessionId)),
listSessionsWithRuntimeEventText: (sessionIds, terms) =>
run(() => runtimeEventStore.listSessionsWithRuntimeEventText(sessionIds, terms)),
countRuntimeEventMessages: (sessionIds) =>
run(() => runtimeEventStore.countRuntimeEventMessages(sessionIds)),
resequenceSessionEventOrdinals: (sessionId) =>
run(() => runtimeEventStore.resequenceSessionEventOrdinals(sessionId)),
readTranscriptHighWater: (sessionId) =>
run(() => runtimeEventStore.readTranscriptHighWater(sessionId)),
readTranscriptRun: (sessionId, request, project) =>
run(() => runtimeEventStore.readTranscriptRun(sessionId, request, project)),
readTranscriptTurns: (sessionId, request) =>
run(() => runtimeEventStore.readTranscriptTurns(sessionId, request)),
readTranscriptTurnCrossing: (sessionId, ordinal) =>
run(() => runtimeEventStore.readTranscriptTurnCrossing(sessionId, ordinal)),
subscribeRuntimeEventCommits: (listener) => {
if (closed) throw invalidExecutionStores(kind, 'write');
assertStorageRootLeaseActive(lease, kind, 'write');
const unsubscribe = runtimeEventStore.subscribeRuntimeEventCommits((sessionId) => {
if (!closed) listener(sessionId);
});
subscriptions.add(unsubscribe);
return () => {
subscriptions.delete(unsubscribe);
unsubscribe();
};
},
claimContinuation: (input) => run(() => runtimeEventStore.claimContinuation(input)),
readContinuationClaimByBoundary: (boundaryDigest) =>
run(() => runtimeEventStore.readContinuationClaimByBoundary(boundaryDigest)),
readContinuationClaimStateByBoundary: (boundaryDigest) =>
run(() => runtimeEventStore.readContinuationClaimStateByBoundary(boundaryDigest)),
listContinuationClaimsForRecovery: (sessionId) =>
run(() => runtimeEventStore.listContinuationClaimsForRecovery(sessionId)),
commitContinuationStart: (input) =>
run(() => runtimeEventStore.commitContinuationStart(input)),
commitContinuationRepairStart: (input) =>
run(() => runtimeEventStore.commitContinuationRepairStart(input)),
readImmutableSteeringMessageProof: (sessionId, messageId) =>
run(() => runtimeEventStore.readImmutableSteeringMessageProof(sessionId, messageId)),
repairImmutableSteeringMessageProofsForRecovery: (sessionId) =>
run(() => runtimeEventStore.repairImmutableSteeringMessageProofsForRecovery(sessionId)),
commitToolPrepared: (input) =>
run(() => runtimePersistence.runtimeCommitStore.commitToolPrepared(input)),
commitToolOutcome: (input) =>
run(() => runtimePersistence.runtimeCommitStore.commitToolOutcome(input)),
listUnsettledToolOperations: (sessionId) =>
run(() => runtimePersistence.runtimeCommitStore.listUnsettledToolOperations(sessionId)),
},
};
freezeExecutionStoresFacade(stores);
executionStoresWriterKinds.set(stores, kind);
executionStoresWritersByLease.set(lease, stores);
return stores;
}
export async function openInteractiveExecutionStoresForRead(
lease: StorageRootLease<'interactive', 'read'>,
): Promise<ExecutionStoresReader<'interactive'>> {
const interactionStore = await openSqliteInteractiveInteractionStoreForRead(lease);
return openExecutionStoresForRead(lease, 'interactive', { interactionStore });
}
async function openExecutionStoresForRead<K extends StorageRootKind, E extends object>(
lease: StorageRootLease<K, 'read'>,
kind: K,
extension: E,
): Promise<ExecutionStoresReaderBase<K> & E> {
await assertStorageRootLease(lease, kind, 'read');
const sessionStore = createSessionStore(lease.canonicalPath);
const agentRunStore = createSqliteAgentRunStore(lease.canonicalPath);
const interactionStore =
'interactionStore' in extension
? (extension.interactionStore as InteractiveInteractionStoreReaderFacade)
: undefined;
await agentRunStore.ready?.().catch(async (error) => {
await sessionStore.close?.().catch(() => {});
agentRunStore.close?.();
if (interactionStore) closeSqliteInteractionStoreFacade(interactionStore);
throw error;
});
const runtimePersistence = await openRuntimeEventReadPersistence({
workspaceRoot: lease.canonicalPath,
}).catch(async (error) => {
await sessionStore.close?.().catch(() => {});
agentRunStore.close?.();
if (interactionStore) closeSqliteInteractionStoreFacade(interactionStore);
throw error;
});
const runtimeEventStore = runtimePersistence.runtimeEventStore;
const run = <T>(operation: () => Promise<T>) =>
runWithStorageRootLease(lease, kind, 'read', operation);
const stores: ExecutionStoresReaderBase<K> & E = {
...extension,
kind,
[executionStoresReaderBrand]: kind,
sessionStore: {
list: (filter) => run(() => sessionStore.list(filter)),
readHeader: (sessionId) => run(() => sessionStore.readHeaderSnapshot(sessionId)),
readMessages: (sessionId) => run(() => sessionStore.readMessagesSnapshot(sessionId)),
listTurns: (sessionId) => run(() => sessionStore.listTurnsSnapshot(sessionId)),
close: () =>
closeExecutionStorePersistence(sessionStore, runtimePersistence, {
agentRunStore,
interactionStore,
}),
},
agentRunStore: {
readEvents: (sessionId, runId) => run(() => agentRunStore.readEvents(sessionId, runId)),
readEventsBounded: (sessionId, runId, budget) =>
run(() => agentRunStore.readEventsBounded(sessionId, runId, budget)),
readEventsByTypeBounded: (sessionId, runId, type, budget) =>
run(() => agentRunStore.readEventsByTypeBounded(sessionId, runId, type, budget)),
readEventProjection: (sessionId, type) =>
run(() => agentRunStore.readEventProjection(sessionId, type)),
readRootTurnAdmission: (sessionId, turnId) =>
run(() => agentRunStore.readRootTurnAdmission(sessionId, turnId)),
readRootTurnContinuationAdmission: (sessionId, sourceTurnId, sourceRunId) =>
run(() =>
agentRunStore.readRootTurnContinuationAdmission(sessionId, sourceTurnId, sourceRunId),
),
readRootTurnSourceMessageReceipt: (sessionId, sourceMessageId) =>
run(() => agentRunStore.readRootTurnSourceMessageReceipt(sessionId, sourceMessageId)),
},
runtimeEventStore: {
readRuntimeEvents: (sessionId, runId) =>
run(() => runtimeEventStore.readRuntimeEvents(sessionId, runId)),
readRuntimeEventsBounded: (sessionId, runId, budget) =>
run(() => runtimeEventStore.readRuntimeEventsBounded(sessionId, runId, budget)),
readImmutableRuntimeEvents: (sessionId, runId) =>
run(() => runtimeEventStore.readImmutableRuntimeEvents(sessionId, runId)),
listSessionInvocations: (sessionId) =>
run(() => runtimeEventStore.listSessionInvocations(sessionId)),
readRunInvocation: (sessionId, runId) =>
run(() => runtimeEventStore.readRunInvocation(sessionId, runId)),
listSessionInvocationsBounded: (sessionId, limit) =>
run(() => runtimeEventStore.listSessionInvocationsBounded(sessionId, limit)),
listSessionInvocationsPage: (sessionId, input) =>
run(() => runtimeEventStore.listSessionInvocationsPage(sessionId, input)),
readInvocation: (sessionId, invocationId) =>
run(() => runtimeEventStore.readInvocation(sessionId, invocationId)),
readSessionRuntimeEvents: (sessionId) =>
run(() => runtimeEventStore.readSessionRuntimeEvents(sessionId)),
readSessionRuntimeEventEntries: (sessionId) =>
run(() => runtimeEventStore.readSessionRuntimeEventEntries(sessionId)),
listSessionsWithRuntimeEventText: (sessionIds, terms) =>
run(() => runtimeEventStore.listSessionsWithRuntimeEventText(sessionIds, terms)),
countRuntimeEventMessages: (sessionIds) =>
run(() => runtimeEventStore.countRuntimeEventMessages(sessionIds)),
},
};
freezeExecutionStoresFacade(stores);
executionStoresReaderKinds.set(stores, kind);
return stores;
}
function freezeExecutionStoresFacade(stores: {
readonly sessionStore: object;
readonly agentRunStore: object;
readonly runtimeEventStore: object;
}): void {
Object.freeze(stores.sessionStore);
Object.freeze(stores.agentRunStore);
Object.freeze(stores.runtimeEventStore);
Object.freeze(stores);
}
async function closeExecutionStorePersistence(
sessionStore: { close?(): Promise<void> },
runtimePersistence: { close(): void },
extras: {
agentRunStore?: Pick<DurableAgentRunStore, 'close'>;
conversationOperationalStateStore?: Pick<ConversationOperationalStateStore, 'close'>;
interactionStore?:
| InteractiveInteractionStoreReaderFacade
| InteractiveInteractionStoreWriterFacade;
} = {},
): Promise<void> {
const errors: unknown[] = [];
try {
runtimePersistence.close();
} catch (error) {
errors.push(error);
}
try {
await sessionStore.close?.();
} catch (error) {
errors.push(error);
}
try {
extras.agentRunStore?.close?.();
} catch (error) {
errors.push(error);
}
try {
extras.conversationOperationalStateStore?.close();
} catch (error) {
errors.push(error);
}
try {
if (extras.interactionStore) {
closeSqliteInteractionStoreFacade(extras.interactionStore);
}
} catch (error) {
errors.push(error);
}
if (errors.length > 0) {
throw new AggregateError(errors, 'Unable to close execution store persistence');
}
}
function invalidExecutionStores(
kind: StorageRootKind,
access: 'read' | 'write',
): StorageRootAuthorityError {
return new StorageRootAuthorityError(
'invalid_lease',
`Expected authentic ${kind} ${access} execution stores`,
);
}