blob: 962d7f22aa8aa45ca6c9861b18274ef72f66e341 [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 { nextId } from '@maka/core/test-only/async-primitives';
import { sectionedSummary } from './history-compact-test-fixtures.js';
import { runtimeInvocationFailureClass } from '../runtime-event-read-model.js';
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createSessionStore } from '@maka/storage/session-store';
import {
openInteractivePlanStoreForWrite,
type InteractivePlanStoreWriter,
} from '@maka/storage/plan-authority';
import { resolveStorageRoot, tryAcquireInteractiveRootOwner } from '@maka/storage/root-authority';
import { DEFAULT_TOOL_MODE } from '@maka/core/tool-mode';
import {
buildInvocationOpenedEvent,
isSessionInlineInvocation,
runtimeInvocationOutcome,
runtimeInvocationsFromSessionEvents,
type RootExecutionDescriptor,
type RuntimeInvocationRecord,
} from '@maka/core/runtime-invocation';
import type {
RuntimeEventInvocationOpenedContent,
RuntimeInvocationLineage,
RuntimeInvocationRootAuthority,
} from '@maka/core/runtime-event';
import type { PermissionMode } from '@maka/core/permission';
import type { PersistedBackendKind } from '@maka/core/session';
import type { ToolMode } from '@maka/core/tool-mode';
import { setTimeout as timerDelay } from 'node:timers/promises';
import { createHash } from 'node:crypto';
import {
applySandboxBoundaryExpansion,
createGenesisExecutionBoundary,
isSandboxBoundaryRestartClosure,
} from '@maka/core/sandbox-boundary';
import {
canReadPath,
createReadOnlyPermissionProfile,
createWorkspaceWritePermissionProfile,
isReadOnlyPermissionProfile,
} from '@maka/core/permission-profile';
import { DEEP_RESEARCH_SESSION_LABEL } from '@maka/core/deep-research';
import { RUNTIME_CONTINUATION_AUTHORITY_V1 } from '@maka/core/runtime-event-store';
import { deriveTurnRecords } from '@maka/core/session';
import { isTerminalRuntimeEvent } from '@maka/core/runtime-event';
import { canonicalToolArgsHash } from '@maka/core/tool-args-identity';
import { buildImmutableRuntimePrefix, decodeContinuationClaim } from '@maka/core/runtime-boundary';
import type {
CreateSandboxBoundaryRequest,
SandboxBoundaryRequest,
SandboxBoundaryResponse,
SandboxBoundarySettlement,
SettleSandboxBoundaryRequest,
} from '@maka/core/sandbox-boundary';
import type {
CreateSessionInput,
SessionListFilter,
UserMessageInput,
} from '@maka/core/runtime-inputs';
import type { ExecutionBoundary } from '@maka/core/sandbox-boundary';
import type { SessionEvent } from '@maka/core/events';
import type {
AgentGraphIntentClaim,
AgentGraphIntentClaimStore,
} from '@maka/core/agent-graph-control';
import type {
AgentGraphOperatorProvisionRequest,
AgentGraphOperatorProvisionResult,
} from '@maka/core/agent-graph-topology';
import type { AgentRunEvent, AgentRunStore, EmittedAgentRunEvent } from '@maka/core/agent-run';
import type { ArtifactRecord } from '@maka/core/artifacts';
import type { ContinuationClaimV1, RuntimeBoundaryDigest } from '@maka/core/runtime-boundary';
import type {
RuntimeContinuationAuthorityStore,
RuntimeEventStore,
} from '@maka/core/runtime-event-store';
import type { RuntimeEvent } from '@maka/core/runtime-event';
import type { SessionHeader, SessionSummary, StoredMessage, TurnRecord } from '@maka/core/session';
import type {
BackendCompactHistoryInput,
BackendSendInput,
BackendStopMode,
} from '@maka/core/backend-types';
import {
PlanConflictError,
emptyPlanSessionState,
type PlanEvent,
type PlanSessionState,
type PlanStepStatus,
type PlanStore,
type UpdatePlanExecutionInput,
} from '@maka/core/plan';
import { MockLanguageModelV4, simulateReadableStream } from 'ai/test';
import { createTestAiSdkBackend } from './execution-boundary-test-helpers.js';
import { assertDoubleRunNotSealed } from './runtime-event-store-seal.js';
import type { LanguageModelV4StreamPart } from '@ai-sdk/provider';
import { z } from 'zod';
import { AiSdkBackend } from '../ai-sdk-backend.js';
import { renderPlanModePrompt, selectCollaborationTools } from '../plan-mode.js';
import {
BackendRegistry,
SessionConfigurationRevisionConflictError,
SessionConfigurationTransitionError,
SessionManager,
headerToSummary,
type BackendFactoryContext,
type SessionConfigurationTransitionRequest,
type SessionConfigurationStoreUpdate,
type SessionStore,
type VersionedSessionHeader,
} from '../session-manager.js';
import {
RuntimeContextCompactError,
RuntimeKernel,
type RuntimeKernelLike,
} from '../runtime-kernel.js';
import { FAKE_ASK_USER_QUESTION_PROMPT, FakeBackend } from '../test-only/fake-backend.js';
import { RuntimeReadModel, RuntimeReadModelError } from '../runtime-read-model.js';
import type { AgentBackend } from '@maka/core/backend-types';
import type { MakaTool } from '../tool-runtime.js';
import type { ShellRunProcessManager } from '../shell-run-manager.js';
import type { RuntimeCommitSink } from '../runtime-commit-sink.js';
import {
buildHistoryCompactCheckpoint,
type HistoryCompactCheckpoint,
} from '../history-compact-checkpoint.js';
import { buildLlmHistorySummarizer } from '../history-compact-summarizer.js';
import { decodeModelCallAttempt, type ModelCallAttempt } from '@maka/core/model-call-attempt';
import {
AGENT_WORKSPACE_WORKTREE,
IMPLEMENTATION_AGENT_DEFINITION,
IMPLEMENTATION_AGENT_ID,
LOCAL_READ_AGENT_DEFINITION,
LOCAL_READ_AGENT_ID,
LOCAL_READ_AGENT_PROFILE,
WEB_RESEARCH_AGENT_ID,
} from '../agent-catalog.js';
import {
RuntimeMessageAuthorityInvariantError,
type RuntimeHostedRootAuthority,
type RuntimeMessageRunIdentity,
} from '../message-authority.js';
import {
RuntimeInteractionInvariantError,
type CanonicalPermissionOutcomeReader,
type CanonicalPermissionOutcomeRecord,
type RuntimeInteractionAuthority,
type RuntimeInteractionRunIdentity,
type RuntimeUserQuestionContinuation,
} from '../interaction-authority.js';
import {
claimAgentGraphRunnableIntent,
fingerprintAgentGraphRunnableIntent,
} from '../stream-graph-admission.js';
import type { AgentGraphRunnableIntent } from '../stream-graph-readiness.js';
test('sendMessage rejects removed Automation as a live trigger', async () => {
const runStore = new MemoryAgentRunStore();
const manager = new SessionManager({
store: new MemorySessionStore(),
runStore,
runtimeEventStore: runStore,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(1),
});
const session = await manager.createSession(makeInput());
await assert.rejects(
() =>
drain(
manager.sendMessage(session.id, {
turnId: 'legacy-turn',
text: 'automated prompt',
origin: { kind: 'legacy_automation', automationId: 'automation-1' },
} as UserMessageInput),
),
/removed Automation authority/,
);
});
test('sendMessage rejects removed child AgentRun lineage as a live trigger', async () => {
for (const lineage of [
{ parentRunId: 'parent-run' },
{ resumedFromRunId: 'resumed-run' },
{ retriedFromRunId: 'retried-run' },
]) {
const runStore = new MemoryAgentRunStore();
const manager = new SessionManager({
store: new MemorySessionStore(),
runStore,
runtimeEventStore: runStore,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(1),
});
const session = await manager.createSession(makeInput());
await assert.rejects(
() =>
drain(
manager.sendMessage(session.id, {
turnId: 'legacy-child-turn',
text: 'removed child AgentRun input',
...lineage,
} as UserMessageInput),
),
/removed child AgentRun lineage/,
);
assert.deepStrictEqual(await runStore.listSessionInvocations(session.id), []);
}
});
/**
* "A turn is running" is a fact about the live process, so the read model takes
* it from the run rather than from the persisted status. The status cannot
* serve: it is written only at the END of `AgentRun.begin`, it reads the same
* before a turn starts and after it ends, and a crash between a turn's end and
* its status write leaves `running` in storage forever.
*/
test('listSessions preserves known-empty live run state', async () => {
const store = new MemorySessionStore();
let runningTurnIds: string[] = [];
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(1),
runtimeKernel: {
runningTurnIds: () => [...runningTurnIds],
} as unknown as RuntimeKernelLike,
});
const session = await manager.createSession(makeInput());
assert.deepEqual((await manager.listSessions())[0]?.runningTurnIds, []);
runningTurnIds = ['turn-live'];
assert.deepEqual((await manager.listSessions())[0]?.runningTurnIds, ['turn-live']);
assert.equal((await store.readHeader(session.id)).status, 'active');
});
test('listChildSessions preserves known-empty live run state', async () => {
const store = new MemorySessionStore();
const runningTurnIdsBySession = new Map<string, string[]>();
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(1),
runtimeKernel: {
runningTurnIds: (sessionId: string) => [...(runningTurnIdsBySession.get(sessionId) ?? [])],
} as unknown as RuntimeKernelLike,
});
const parent = await manager.createSession(makeInput({ name: 'Parent' }));
const child = await manager.createSession(
makeInput({
name: 'Child',
subagentParent: {
kind: 'subagent',
parentSessionId: parent.id,
spawnedBy: {
parentRunId: 'parent-run',
parentTurnId: 'parent-turn',
toolCallId: 'tool-call',
},
lifecycle: 'foreground',
},
}),
);
assert.deepEqual((await manager.listChildSessions(parent.id))[0]?.runningTurnIds, []);
runningTurnIdsBySession.set(child.id, ['turn-live']);
assert.deepEqual((await manager.listChildSessions(parent.id))[0]?.runningTurnIds, ['turn-live']);
});
describe('SessionManager Plan control boundaries', () => {
test('an exact approval retry completes Session side effects after a partial failure', async () => {
const store = new MemorySessionStore();
const runtimeKernel = new DelegatingRuntimeKernel();
const sessionId = 'session-1';
let receipt: object | undefined;
let committedApprovals = 0;
const planStore = {
readOperationReceipt: async () => receipt,
approveProposal: async () => {
if (!receipt) {
committedApprovals += 1;
receipt = { id: 'approve-operation', type: 'plan_approved' };
}
return {
event: receipt,
state: { ...emptyPlanSessionState(sessionId), storeVersion: 1 },
};
},
} as unknown as PlanStore;
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(1),
planStore,
runtimeKernel,
});
await manager.createSession(makeInput({ collaborationMode: 'plan' }));
const input = {
sessionId,
proposalId: 'proposal-1',
expectedRevision: 1,
expectedStoreVersion: 1,
operationId: 'approve-operation',
};
store.failUpdateHeaderFor.add(sessionId);
await assert.rejects(manager.approvePlan(input), /Cannot update header/);
store.failUpdateHeaderFor.delete(sessionId);
runtimeKernel.activeRuns = true;
await manager.approvePlan(input);
assert.strictEqual(committedApprovals, 1);
assert.strictEqual((await store.readHeader(sessionId)).collaborationMode, 'agent');
assert.deepStrictEqual(runtimeKernel.disposed, [sessionId]);
});
test('does not commit a same-mode Plan revision before backend disposal succeeds', async () => {
const store = new MemorySessionStore();
const runtimeKernel = new DelegatingRuntimeKernel();
const sessionId = 'session-1';
let receipt: object | undefined;
let committedRevisions = 0;
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(1),
runtimeKernel,
planStore: {
readOperationReceipt: async () => receipt,
requestRevision: async () => {
committedRevisions += 1;
receipt = { id: 'revision-operation', type: 'plan_revision_requested' };
return {
event: receipt,
state: { ...emptyPlanSessionState(sessionId), storeVersion: 1 },
};
},
} as unknown as PlanStore,
});
await manager.createSession(makeInput({ collaborationMode: 'plan' }));
runtimeKernel.failNextDispose = true;
await assert.rejects(
manager.requestPlanRevision(sessionId, 'proposal-1', 'revision-operation'),
/backend disposal failed/,
);
assert.strictEqual(committedRevisions, 0);
await manager.requestPlanRevision(sessionId, 'proposal-1', 'revision-operation');
assert.strictEqual(committedRevisions, 1);
assert.deepStrictEqual(runtimeKernel.disposed, [sessionId]);
});
test('does not commit Plan cancellation before backend disposal succeeds', async () => {
const store = new MemorySessionStore();
const runtimeKernel = new DelegatingRuntimeKernel();
const sessionId = 'session-1';
const planState = {
...emptyPlanSessionState(sessionId),
storeVersion: 2,
executions: [
{
executionId: 'execution-1',
planId: 'plan-1',
proposalId: 'proposal-1',
sessionId,
status: 'interrupted' as const,
steps: [],
startedAt: 1,
updatedAt: 2,
interruptedAt: 2,
interruptionReason: 'Host restart',
},
],
};
let receipt: object | undefined;
let committedCancellations = 0;
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(1),
runtimeKernel,
planStore: {
readState: async () => structuredClone(planState),
readOperationReceipt: async () => receipt,
cancelExecution: async () => {
committedCancellations += 1;
receipt = { id: 'cancel-operation', type: 'plan_execution_cancelled' };
return { event: receipt, state: { ...planState, storeVersion: 3 } };
},
} as unknown as PlanStore,
});
await manager.createSession(makeInput());
runtimeKernel.failNextDispose = true;
await assert.rejects(
manager.cancelPlanExecution(sessionId, 'execution-1', 'cancel-operation'),
/backend disposal failed/,
);
assert.strictEqual(committedCancellations, 0);
await manager.cancelPlanExecution(sessionId, 'execution-1', 'cancel-operation');
assert.strictEqual(committedCancellations, 1);
assert.deepStrictEqual(runtimeKernel.disposed, [sessionId]);
});
test('classifies an ineligible execution cancellation as a domain conflict', async () => {
const store = new MemorySessionStore();
const sessionId = 'session-1';
const planState = {
...emptyPlanSessionState(sessionId),
storeVersion: 2,
activeExecutionId: 'execution-1',
executions: [
{
executionId: 'execution-1',
planId: 'plan-1',
proposalId: 'proposal-1',
sessionId,
status: 'active' as const,
steps: [],
startedAt: 1,
updatedAt: 1,
},
],
};
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(1),
planStore: {
readState: async () => structuredClone(planState),
readOperationReceipt: async () => undefined,
} as unknown as PlanStore,
});
await manager.createSession(makeInput());
await assert.rejects(
manager.cancelPlanExecution(sessionId, 'execution-1', 'cancel-operation'),
(error: unknown) => {
assert.ok(error instanceof PlanConflictError);
assert.match(error.message, /interrupted/);
return true;
},
);
});
test('rejects Plan mode for a linked child Session', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(1),
planStore: {
readState: async (sessionId: string) => emptyPlanSessionState(sessionId),
} as unknown as PlanStore,
});
const child = await manager.createSession(
makeInput({
subagentParent: {
kind: 'subagent',
parentSessionId: 'parent-session',
spawnedBy: {
parentRunId: 'parent-run',
parentTurnId: 'parent-turn',
toolCallId: 'tool-call',
},
lifecycle: 'foreground',
},
}),
);
await assert.rejects(
manager.transitionSessionConfiguration(child.id, {
expectedRevision: 1,
clearConnectionBlock: false,
permissionModeOnly: false,
configuration: {
backend: child.backend,
llmConnectionId: 'test-connection-id',
llmConnectionSlug: child.llmConnectionSlug,
connectionLocked: true,
model: child.model,
thinkingLevel: child.thinkingLevel,
permissionMode: child.permissionMode,
collaborationMode: 'plan',
orchestrationMode: child.orchestrationMode ?? 'default',
},
}),
(error: unknown) => {
assert.ok(error instanceof SessionConfigurationTransitionError);
assert.equal(error.code, 'operation_unavailable');
return true;
},
);
assert.equal((await store.readHeader(child.id)).collaborationMode, 'agent');
});
});
describe('SessionManager Plan terminal settlement', () => {
test('a completed root Turn commits the terminal steps the model already reported', async () => {
const harness = await mountPlanSettlement('session-1', [
['step-1', 'completed'],
['step-2', 'skipped'],
]);
const result = await harness.manager.settleActivePlanExecutionAfterRootTurn(
harness.sessionId,
'completed',
'plan_root_terminal_run-1',
);
assert.equal(result?.event.type, 'plan_execution_completed');
assert.deepEqual(harness.updates, [
{
operationId: 'plan_root_terminal_run-1',
sessionId: harness.sessionId,
executionId: 'execution-1',
steps: [
{ id: 'step-1', status: 'completed' },
{ id: 'step-2', status: 'skipped' },
],
},
]);
// Every step is already terminal, so there is nothing to interrupt: the
// settlement keeps the running backend instead of disposing it.
assert.deepEqual(harness.runtimeKernel.disposed, []);
});
test('a completed root Turn interrupts an execution that never reached a terminal step', async () => {
const harness = await mountPlanSettlement('session-1', [
['step-1', 'in_progress'],
['step-2', 'pending'],
]);
const result = await harness.manager.settleActivePlanExecutionAfterRootTurn(
harness.sessionId,
'completed',
'plan_root_terminal_run-1',
);
assert.equal(result?.event.type, 'plan_execution_interrupted');
assert.deepEqual(harness.updates, []);
assert.deepEqual(harness.runtimeKernel.disposed, [harness.sessionId]);
assert.match(harness.interrupts[0]?.reason ?? '', /completed before all Plan steps/);
});
test('a failed or cancelled root Turn interrupts the active execution', async () => {
for (const [status, expected] of [
['failed', /root Turn failed/],
['cancelled', /root Turn was cancelled/],
] as const) {
const harness = await mountPlanSettlement('session-1', [['step-1', 'in_progress']]);
const result = await harness.manager.settleActivePlanExecutionAfterRootTurn(
harness.sessionId,
status,
`plan_root_terminal_${status}`,
);
assert.equal(result?.event.type, 'plan_execution_interrupted');
assert.deepEqual(harness.updates, []);
assert.deepEqual(harness.runtimeKernel.disposed, [harness.sessionId]);
assert.match(harness.interrupts[0]?.reason ?? '', expected);
}
});
// The retry guarantee is the Plan store's operation receipt, so it is asserted
// on the production store rather than a stub: a stub that hands back a receipt
// it was told to hand back states the stub, not the store.
test('a repeated settlement replays the recorded receipt without a second write', async (t) => {
const harness = await mountRealPlanSettlement();
t.after(harness.close);
const settle = () =>
harness.manager.settleActivePlanExecutionAfterRootTurn(
harness.sessionId,
'failed',
'plan_root_terminal_run-1',
);
const first = await settle();
assert.equal(first?.event.type, 'plan_execution_interrupted');
assert.deepEqual(harness.runtimeKernel.disposed, [harness.sessionId]);
const replay = await settle();
assert.deepEqual(replay?.event, first?.event, 'a replay must return the recorded event');
assert.equal(replay?.state.storeVersion, first?.state.storeVersion, 'a replay must not write');
assert.equal(replay?.state.activeExecutionId, undefined);
assert.equal(
(await harness.store.readState(harness.sessionId)).storeVersion,
first?.state.storeVersion,
);
assert.deepEqual(
harness.runtimeKernel.disposed,
[harness.sessionId],
'a replay must not dispose the Plan backend a second time',
);
});
test('settling an execution that already finished writes nothing', async (t) => {
const harness = await mountRealPlanSettlement();
t.after(harness.close);
// The Turn that ran the plan already reported every step through the store.
await harness.store.updateExecution({
sessionId: harness.sessionId,
executionId: harness.executionId,
steps: [
{ id: 'step-1', status: 'completed' },
{ id: 'step-2', status: 'completed' },
],
});
const completed = await harness.store.readState(harness.sessionId);
assert.equal(completed.activeExecutionId, undefined);
assert.equal(
await harness.manager.settleActivePlanExecutionAfterRootTurn(
harness.sessionId,
'completed',
'plan_root_terminal_run-2',
),
null,
);
assert.equal(
(await harness.store.readState(harness.sessionId)).storeVersion,
completed.storeVersion,
);
});
test('a Session without Plan authority settles nothing', async () => {
const manager = new SessionManager({
store: new MemorySessionStore(),
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(1),
});
assert.equal(
await manager.settleActivePlanExecutionAfterRootTurn(
'session-without-plan-authority',
'completed',
'plan_root_terminal_run-1',
),
null,
);
});
});
describe('SessionManager graph operator provisioning', () => {
test('provisions a graph operator before its active supervisor turn returns', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const parentGate = makeGate();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx, parentGate));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
subagentCatalog: {
list: async () => [],
resolve: async (id) => ({
connectionId: '22222222-2222-4222-8222-222222222222',
id,
name: 'Fast graph reader',
description: 'Cheap graph scans',
profile: 'local_read',
connectionSlug: 'worker-connection',
model: 'worker-model',
thinkingLevel: 'low',
enabled: true,
}),
},
newId: nextId(),
now: nextNow(10),
});
const parent = await manager.createSession(makeInput({ permissionMode: 'ask' }));
const parentTurn = manager
.sendMessage(parent.id, { turnId: 'supervisor-turn', text: 'schedule graph work' })
[Symbol.asyncIterator]();
await parentTurn.next();
const sourceRun = (await runStore.listSessionInvocations(parent.id))[0];
if (!sourceRun) throw new Error('Supervisor Run was not recorded');
let provisionSettled = false;
const provision = manager
.provisionAgentGraphOperator({
graphId: 'graph-active-supervisor',
workId: `graph_work_${'8'.repeat(32)}`,
subagentId: 'fast-reader',
operatorId: `graph_operator_${'9'.repeat(32)}`,
source: {
sessionId: parent.id,
runId: sourceRun.runId,
turnId: sourceRun.turnId,
toolCallId: 'schedule-tool',
},
edges: [],
expectedScheduleRevision: 1,
})
.finally(() => {
provisionSettled = true;
});
await new Promise<void>((resolve) => setImmediate(resolve));
assert.strictEqual(provisionSettled, true);
const provisioned = await provision;
assert.strictEqual(provisioned.created, true);
assert.strictEqual(provisioned.header.name, 'Fast graph reader');
assert.strictEqual(provisioned.header.llmConnectionSlug, 'worker-connection');
assert.strictEqual(provisioned.header.model, 'worker-model');
assert.strictEqual(provisioned.header.thinkingLevel, 'low');
assert.strictEqual(provisioned.header.subagentRuntime?.presetId, 'fast-reader');
assert.strictEqual(provisioned.provision.agentId, LOCAL_READ_AGENT_ID);
parentGate.release();
while (!(await parentTurn.next()).done) {}
});
test('serializes graph operator provisioning with parent permission narrowing', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const runStore = new MemoryAgentRunStore();
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends: new BackendRegistry(),
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(20),
shellRuns: {
async terminateSession() {
return undefined;
},
async commitSessionClose() {},
rollbackSessionClose() {},
resumeSession() {},
} as never,
});
const parent = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
await seedInvocationFromHeader(
runStore,
makeRunHeader({
sessionId: parent.id,
runId: 'supervisor-run',
turnId: 'supervisor-turn',
}),
);
const provisionStarted = makeGate();
const releaseProvision = makeGate();
store.nextGraphOperatorProvisionGate = {
started: provisionStarted,
release: releaseProvision,
};
const provision = manager.provisionAgentGraphOperator({
graphId: 'graph-config-fence',
workId: `graph_work_${'a'.repeat(32)}`,
agentId: LOCAL_READ_AGENT_ID,
operatorId: `graph_operator_${'b'.repeat(32)}`,
source: {
sessionId: parent.id,
runId: 'supervisor-run',
turnId: 'supervisor-turn',
toolCallId: 'schedule-tool',
},
edges: [],
expectedScheduleRevision: 1,
});
await provisionStarted.promise;
let transitionSettled = false;
const transition = manager
.transitionSessionConfiguration(parent.id, {
expectedRevision: 1,
clearConnectionBlock: false,
permissionModeOnly: false,
configuration: {
backend: parent.backend,
llmConnectionId: 'test-connection-id',
llmConnectionSlug: parent.llmConnectionSlug,
connectionLocked: true,
model: parent.model,
thinkingLevel: parent.thinkingLevel,
permissionMode: 'ask',
collaborationMode: parent.collaborationMode ?? 'agent',
orchestrationMode: parent.orchestrationMode ?? 'default',
},
})
.then(
(value) => ({ ok: true as const, value }),
(error: unknown) => ({ ok: false as const, error }),
)
.finally(() => {
transitionSettled = true;
});
await new Promise<void>((resolve) => setImmediate(resolve));
assert.strictEqual(transitionSettled, false);
releaseProvision.release();
const provisioned = await provision;
const transitionResult = await transition;
assert.strictEqual(transitionResult.ok, false);
if (transitionResult.ok) throw new Error('Configuration transition unexpectedly committed');
assert.ok(transitionResult.error instanceof SessionConfigurationTransitionError);
assert.strictEqual(transitionResult.error.code, 'operation_conflict');
assert.strictEqual((await store.readHeader(parent.id)).permissionMode, 'bypass');
assert.strictEqual(provisioned.header.permissionMode, 'bypass');
});
test('snapshots a catalog agent into a metadata-only child with reserved activation ids', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends: new BackendRegistry(),
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(30),
});
const parent = await manager.createSession(
makeInput({
cwd: '/tmp/graph-project',
llmConnectionSlug: 'graph-connection',
model: 'graph-model',
thinkingLevel: 'medium',
permissionMode: 'ask',
}),
);
await seedInvocationFromHeader(
runStore,
makeRunHeader({
sessionId: parent.id,
runId: 'supervisor-run',
turnId: 'supervisor-turn',
cwd: '/tmp/graph-project',
}),
);
const result = await manager.provisionAgentGraphOperator({
graphId: 'graph-1',
workId: `graph_work_${'1'.repeat(32)}`,
agentId: LOCAL_READ_AGENT_ID,
operatorId: `graph_operator_${'2'.repeat(32)}`,
source: {
sessionId: parent.id,
runId: 'supervisor-run',
turnId: 'supervisor-turn',
toolCallId: 'schedule-tool',
},
edges: [
{
edgeId: `graph_edge_${'3'.repeat(32)}`,
fromOperatorId: 'writer',
toOperatorId: `graph_operator_${'2'.repeat(32)}`,
},
],
expectedScheduleRevision: 1,
});
assert.strictEqual(result.created, true);
assert.deepStrictEqual(result.header.subagentParent?.graph, {
graphId: 'graph-1',
workId: `graph_work_${'1'.repeat(32)}`,
operatorId: `graph_operator_${'2'.repeat(32)}`,
});
assert.strictEqual(result.header.subagentRuntime?.agentId, LOCAL_READ_AGENT_ID);
assert.strictEqual(result.header.permissionMode, 'explore');
assert.strictEqual(result.provision.initialTurnId, result.header.subagentSpawn?.initialTurnId);
assert.strictEqual(result.provision.initialRunId, result.header.subagentSpawn?.initialRunId);
assert.deepStrictEqual(await runStore.listSessionInvocations(result.header.id), []);
});
test('provisions a graph child on an explicit plugin executor without Maka tools', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const checkedExecutors: string[] = [];
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends: new BackendRegistry(),
childTools: [],
assertChildExecutorAvailable: (parentSessionId, executorId) => {
checkedExecutors.push(`${parentSessionId}:${executorId}`);
},
newId: nextId(),
now: nextNow(40),
});
const parent = await manager.createSession(makeInput());
await seedInvocationFromHeader(
runStore,
makeRunHeader({
sessionId: parent.id,
runId: 'supervisor-run',
turnId: 'supervisor-turn',
}),
);
const result = await manager.provisionAgentGraphOperator({
graphId: 'graph-external',
workId: `graph_work_${'4'.repeat(32)}`,
agentId: LOCAL_READ_AGENT_ID,
executorId: 'codex',
operatorId: `graph_operator_${'5'.repeat(32)}`,
source: {
sessionId: parent.id,
runId: 'supervisor-run',
turnId: 'supervisor-turn',
toolCallId: 'schedule-tool',
},
edges: [],
expectedScheduleRevision: 1,
});
assert.strictEqual(result.header.backend, 'plugin-executor');
assert.strictEqual(result.header.executorId, 'codex');
assert.strictEqual(result.header.llmConnectionId, undefined);
assert.strictEqual(result.header.llmConnectionSlug, 'executor:codex');
assert.deepStrictEqual(result.header.subagentRuntime?.toolNames, []);
assert.deepStrictEqual(checkedExecutors, [`${parent.id}:codex`]);
});
test('keeps four large graph branches and a replacement off the supervisor data plane', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends: new BackendRegistry(),
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(90),
});
const parent = await manager.createSession(makeInput({ permissionMode: 'ask' }));
await seedInvocationFromHeader(
runStore,
makeRunHeader({
sessionId: parent.id,
runId: 'large-supervisor-run',
turnId: 'large-supervisor-turn',
}),
);
const rawPayloadBytes = 500 * 1024;
const oversizedPayload = 'x'.repeat(rawPayloadBytes);
const outputs = [];
for (let index = 0; index < 5; index += 1) {
const identity = String(index + 1).repeat(32);
const provisioned = await manager.provisionAgentGraphOperator({
graphId: 'graph-large-output',
workId: `graph_work_${identity}`,
agentId: LOCAL_READ_AGENT_ID,
operatorId: `graph_operator_${identity}`,
source: {
sessionId: parent.id,
runId: 'large-supervisor-run',
turnId: 'large-supervisor-turn',
toolCallId: `schedule-large-${index}`,
},
edges: [],
expectedScheduleRevision: index + 1,
});
const failed = index === 2;
const run = makeRunHeader({
sessionId: provisioned.header.id,
runId: provisioned.provision.initialRunId,
turnId: provisioned.provision.initialTurnId,
status: failed ? 'failed' : 'completed',
createdAt: 100 + index * 10,
updatedAt: 109 + index * 10,
completedAt: 109 + index * 10,
...(failed ? { failureClass: 'branch_failed' } : {}),
});
await seedRuntimeRun(runStore, run, [
runtimeEvent({
id: `large-tool-result-${index}`,
sessionId: run.sessionId,
runId: run.runId,
turnId: run.turnId,
ts: 105 + index * 10,
role: 'tool',
author: 'tool',
content: {
kind: 'function_response',
id: `large-tool-${index}`,
name: 'Read',
result: oversizedPayload,
isError: false,
},
}),
runtimeEvent({
id: `large-final-${index}`,
sessionId: run.sessionId,
runId: run.runId,
turnId: run.turnId,
ts: 108 + index * 10,
role: 'model',
author: 'agent',
content: {
kind: 'text',
text: failed
? 'Branch failed after collecting evidence.'
: index === 4
? 'Replacement branch completed with a verified answer.'
: `Branch ${index + 1} completed with a verified answer.`,
},
}),
runtimeEvent({
id: `large-terminal-${index}`,
sessionId: run.sessionId,
runId: run.runId,
turnId: run.turnId,
ts: 109 + index * 10,
role: 'system',
author: 'system',
status: failed ? 'failed' : 'completed',
actions: {
endInvocation: true,
...(failed ? { stateDelta: { failureClass: 'branch_failed' } } : {}),
},
}),
]);
outputs.push(
await manager.readChildAgentOutput(parent.id, {
execution: {
kind: 'child_session',
sessionId: run.sessionId,
currentRunId: run.runId,
},
view: 'result',
maxBytes: 32 * 1024,
}),
);
}
const serializedOutputs = JSON.stringify(outputs);
assert.strictEqual(
Buffer.byteLength(oversizedPayload, 'utf8') * outputs.length >= 2_500 * 1024,
true,
);
assert.strictEqual(Buffer.byteLength(serializedOutputs, 'utf8') < 64 * 1024, true);
assert.strictEqual(serializedOutputs.includes(oversizedPayload.slice(0, 1024)), false);
assert.strictEqual(
outputs.every((output) => output.runtimeEvents.length === 0),
true,
);
assert.strictEqual(
outputs.every((output) => output.budget.projectedBytes <= 32 * 1024),
true,
);
assert.strictEqual(outputs[2]?.result?.status, 'failed');
assert.strictEqual(outputs[2]?.result?.failureClass, 'branch_failed');
assert.strictEqual(
outputs[4]?.result?.text,
'Replacement branch completed with a verified answer.',
);
});
test('does not advertise implementation when the current project cannot use worktrees', async () => {
const store = new MemorySessionStore();
const checkedSources: unknown[] = [];
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
childTools: IMPLEMENTATION_AGENT_DEFINITION.tools.map(testTool),
worktreeChildExecutor: {
isAvailable: async (input) => {
checkedSources.push(input);
return false;
},
provision: async () => {
throw new Error('unavailable executor must not provision');
},
ensure: async () => {},
capturePatch: async () => new Uint8Array(),
recover: async () => {},
retire: async () => {},
},
newId: nextId(),
now: nextNow(40),
});
const parent = await manager.createSession(
makeInput({ cwd: '/tmp/plain-folder', projectId: 'plain-project' }),
);
const implementation = (await manager.listChildAgents(parent.id)).definitions.find(
(definition) => definition.id === IMPLEMENTATION_AGENT_ID,
);
assert.deepStrictEqual(checkedSources, [
{ sourceCwd: '/tmp/plain-folder', sourceProjectId: 'plain-project' },
]);
assert.deepStrictEqual(implementation?.availability, {
status: 'unavailable',
reason: 'workspace_isolation_unavailable',
workspace: AGENT_WORKSPACE_WORKTREE,
requiredRuntime: 'worktree_child_executor',
});
});
test('binds implementation operators to a durable project worktree', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const provisioned: unknown[] = [];
const binding = {
schemaVersion: 1 as const,
kind: 'git_worktree' as const,
leaseId: `subagent_worktree_${'a'.repeat(32)}`,
gitCommonDir: '/tmp/project/.git',
worktreePath: '/tmp/worktrees/implementation-a',
branch: `maka/subagent/${'a'.repeat(32)}`,
baseCommit: 'b'.repeat(40),
};
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends: new BackendRegistry(),
childTools: IMPLEMENTATION_AGENT_DEFINITION.tools
.filter((name) => name !== 'Write' && name !== 'Edit')
.map(testTool),
worktreeChildExecutor: {
isAvailable: async () => true,
provision: async (input) => {
provisioned.push(input);
return {
...binding,
leaseId: input.leaseId,
worktreePath: `/tmp/worktrees/${input.leaseId}`,
branch: `maka/subagent/${input.leaseId.slice('subagent_worktree_'.length)}`,
};
},
ensure: async () => {},
capturePatch: async () => new Uint8Array(),
recover: async () => {},
retire: async () => {},
},
listArtifactsForTurn: async () => [],
publishChildWorkspacePatch: async () => {
throw new Error('Patch publication is not expected during provisioning');
},
assertChildWorkspaceQuiescent: async () => {},
newId: nextId(),
now: nextNow(40),
});
const parent = await manager.createSession(
makeInput({
cwd: '/tmp/project',
projectId: 'project-1',
permissionMode: 'ask',
}),
);
await seedInvocationFromHeader(
runStore,
makeRunHeader({
sessionId: parent.id,
runId: 'supervisor-run',
turnId: 'supervisor-turn',
cwd: '/tmp/project',
}),
);
const result = await manager.provisionAgentGraphOperator({
graphId: 'graph-worktree',
workId: `graph_work_${'4'.repeat(32)}`,
agentId: IMPLEMENTATION_AGENT_ID,
operatorId: `graph_operator_${'5'.repeat(32)}`,
source: {
sessionId: parent.id,
runId: 'supervisor-run',
turnId: 'supervisor-turn',
toolCallId: 'schedule-tool',
},
edges: [],
expectedScheduleRevision: 1,
});
assert.strictEqual(provisioned.length, 1);
assert.strictEqual(result.header.projectId, 'project-1');
assert.strictEqual(result.header.permissionMode, 'ask');
assert.strictEqual(
result.header.subagentRuntime
? 'permissionCeiling' in result.header.subagentRuntime
: undefined,
false,
);
assert.strictEqual(result.header.cwd, result.header.subagentWorkspace?.worktreePath);
assert.strictEqual(result.header.subagentWorkspace?.kind, 'git_worktree');
assert.match(String(result.header.subagentWorkspace?.branch), /^maka\/subagent\//);
assert.deepStrictEqual(result.header.subagentRuntime?.toolNames, [
'Read',
'Glob',
'Grep',
'apply_patch',
'Bash',
'WriteStdin',
'StopBackgroundTask',
]);
assert.deepStrictEqual(
headerToSummary(result.header).subagentWorkspace,
result.header.subagentWorkspace,
);
});
test('recovers only the latest unpublished implementation patch idempotently', async () => {
const store = new MemorySessionStore();
const runStore = new ReverseOrderedAgentRunStore();
const binding = {
schemaVersion: 1 as const,
kind: 'git_worktree' as const,
leaseId: `subagent_worktree_${'d'.repeat(32)}`,
gitCommonDir: '/tmp/project/.git',
worktreePath: '/tmp/worktrees/implementation-recovery',
branch: `maka/subagent/${'d'.repeat(32)}`,
baseCommit: 'e'.repeat(40),
};
const parent = await store.create(makeInput({ cwd: '/tmp/project' }));
const { header: child } = await store.createSubagent(
makeInput({
cwd: binding.worktreePath,
permissionMode: 'ask',
subagentParent: {
kind: 'subagent',
parentSessionId: parent.id,
spawnedBy: {
parentRunId: 'parent-run',
parentTurnId: 'parent-turn',
toolCallId: 'implementation-spawn',
},
lifecycle: 'foreground',
},
subagentRuntime: {
schemaVersion: 1,
definitionVersion: IMPLEMENTATION_AGENT_DEFINITION.definitionVersion,
agentId: IMPLEMENTATION_AGENT_ID,
agentName: IMPLEMENTATION_AGENT_DEFINITION.name,
profile: 'implementation',
systemPrompt: IMPLEMENTATION_AGENT_DEFINITION.systemPrompt,
toolNames: [...IMPLEMENTATION_AGENT_DEFINITION.tools],
categoryPolicy: {},
},
subagentSpawn: {
schemaVersion: 1,
requestFingerprint: 'f'.repeat(64),
initialTurnId: 'child-turn',
initialRunId: 'child-run',
},
subagentWorkspace: binding,
}),
);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: child.id,
runId: 'child-run',
turnId: 'child-turn',
status: 'completed',
completedAt: 20,
updatedAt: 20,
cwd: binding.worktreePath,
permissionMode: 'ask',
agentId: IMPLEMENTATION_AGENT_ID,
agentName: IMPLEMENTATION_AGENT_DEFINITION.name,
}),
[
runtimeEvent({
id: 'child-complete',
sessionId: child.id,
runId: 'child-run',
turnId: 'child-turn',
ts: 20,
status: 'completed',
actions: { endInvocation: true },
}),
],
);
const artifacts = new Map<string, ArtifactRecord[]>();
let captures = 0;
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends: new BackendRegistry(),
worktreeChildExecutor: {
isAvailable: async () => true,
provision: async () => binding,
ensure: async () => {},
capturePatch: async () => {
captures += 1;
return new TextEncoder().encode('terminal patch');
},
recover: async () => {},
retire: async () => {},
},
listArtifactsForTurn: async (sessionId, turnId) =>
artifacts.get(`${sessionId}:${turnId}`) ?? [],
publishChildWorkspacePatch: async ({ sessionId, turnId, patch }) => {
const record: ArtifactRecord = {
id: 'recovered-writeback',
sessionId,
turnId,
createdAt: 30,
name: 'workspace.patch',
kind: 'diff',
relativePath: `${sessionId}/recovered-writeback-workspace.patch`,
sizeBytes: patch.byteLength,
mimeType: 'text/x-diff; charset=utf-8',
source: 'subagent_writeback',
};
artifacts.set(`${sessionId}:${turnId}`, [record]);
return record;
},
assertChildWorkspaceQuiescent: async () => {},
newId: nextId(),
now: nextNow(40),
});
await manager.recoverChildWorkspacePatches([parent.id, child.id]);
await manager.recoverChildWorkspacePatches([child.id]);
assert.strictEqual(captures, 1);
assert.strictEqual(artifacts.get(`${child.id}:child-turn`)?.[0]?.source, 'subagent_writeback');
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: child.id,
runId: 'newer-child-run',
turnId: 'newer-child-turn',
status: 'completed',
createdAt: 50,
completedAt: 60,
updatedAt: 60,
cwd: binding.worktreePath,
permissionMode: 'ask',
agentId: IMPLEMENTATION_AGENT_ID,
agentName: IMPLEMENTATION_AGENT_DEFINITION.name,
resumedFromRunId: 'child-run',
}),
[
runtimeEvent({
id: 'newer-child-complete',
sessionId: child.id,
runId: 'newer-child-run',
turnId: 'newer-child-turn',
ts: 60,
status: 'completed',
actions: { endInvocation: true },
}),
],
);
const oldArtifact = artifacts.get(`${child.id}:child-turn`)?.[0];
if (!oldArtifact) throw new Error('Recovered patch Artifact is missing');
artifacts.set(`${child.id}:newer-child-turn`, [
{
...oldArtifact,
id: 'newer-writeback',
turnId: 'newer-child-turn',
relativePath: `${child.id}/newer-writeback-workspace.patch`,
},
]);
artifacts.delete(`${child.id}:child-turn`);
await expectRejects(
manager.readChildAgentOutput(parent.id, {
execution: {
kind: 'child_session',
sessionId: child.id,
currentRunId: 'child-run',
},
}),
/cannot reconstruct the historical workspace patch/,
);
assert.strictEqual(captures, 1);
});
});
describe('SessionManager claimed graph intent execution', () => {
test('fails closed without a trusted hosted graph execution capability', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backendBuilds = 0;
backends.register('ai-sdk', (ctx) => {
backendBuilds += 1;
return new TestBackend(ctx);
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
messageAuthority: hostedRootAuthority(),
newId: nextId(),
now: nextNow(20),
});
const parent = await manager.createSession(makeInput({ name: 'Supervisor' }));
const child = await createGraphOperatorSession(store, parent.id);
const claim = graphIntentClaim({ targetSessionId: child.id }, 'must not start');
await expectRejects(
manager.runClaimedAgentGraphIntent(graphExecutionInput(claim, 'must not start')),
/requires its trusted graph execution capability/,
);
assert.deepStrictEqual(await runStore.listSessionInvocations(child.id), []);
assert.deepStrictEqual(await store.readMessages(child.id), []);
assert.strictEqual(backendBuilds, 0);
});
test('hosted execution reads the trusted claim and delegates the exact root descriptor', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const executions: Parameters<RuntimeHostedRootAuthority['executeRoot']>[0][] = [];
const authority = hostedRootAuthority();
authority.executeRoot = async (input) => {
executions.push(input);
for await (const event of input.start({
runId: input.runId,
userMessageId: input.userMessageId,
onRunStarted: () => input.onReady?.(),
})) {
input.onEvent?.(event);
}
};
const parent = await store.create(makeInput({ name: 'Supervisor' }));
const child = await createGraphOperatorSession(store, parent.id);
const claim = graphIntentClaim(
{
targetSessionId: child.id,
targetTurnId: 'hosted-graph-turn',
targetRunId: 'hosted-graph-run',
},
'canonical hosted graph prompt',
);
const prompt = 'canonical hosted graph prompt';
const trustedExecution = graphExecutionInput(claim, prompt);
let trustedReads = 0;
const trustedClaimStore = trustedExecution.claimStore;
const callerClaimStore: AgentGraphIntentClaimStore = {
async claimAgentGraphIntent() {
throw new Error('caller store must not be used');
},
async readAgentGraphIntentClaim() {
throw new Error('caller store must not be used');
},
async listAgentGraphIntentClaims() {
throw new Error('caller store must not be used');
},
};
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
messageAuthority: authority,
hostedAgentGraphExecution: {
async readAgentGraphIntentClaim(graphId, intentId) {
trustedReads += 1;
return trustedClaimStore.readAgentGraphIntentClaim(graphId, intentId);
},
async readRootTurnAdmissionIdentity(sessionId, turnId) {
const admission = await runStore.readRootTurnAdmission(sessionId, turnId);
return admission
? { runId: admission.runId, userMessageId: admission.userMessageId }
: undefined;
},
},
newId: nextId(),
now: nextNow(30),
});
const result = await manager.runClaimedAgentGraphIntent({
...trustedExecution,
claimStore: callerClaimStore,
});
assert.strictEqual(result.status, 'completed');
assert.strictEqual(trustedReads, 1);
assert.strictEqual(executions.length, 1);
assert.partialDeepStrictEqual(executions[0], {
sessionId: child.id,
turnId: claim.targetTurnId,
runId: claim.targetRunId,
userMessageId: 'id-1',
});
assert.deepStrictEqual((executions[0] as { execution?: unknown }).execution, {
kind: 'claimed_agent_graph_intent',
claim,
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
});
assert.deepStrictEqual((executions[0] as { content?: unknown }).content, { text: prompt });
assert.partialDeepStrictEqual(
(await manager.getMessages(child.id)).find(
(message) => message.type === 'user' && message.turnId === claim.targetTurnId,
),
{ id: 'id-1', text: prompt },
);
});
test('hosted execution rejects prompt drift from the durable graph claim before admission', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backendBuilds = 0;
backends.register('ai-sdk', (ctx) => {
backendBuilds += 1;
return new TestBackend(ctx);
});
const parent = await store.create(makeInput({ name: 'Supervisor' }));
const child = await createGraphOperatorSession(store, parent.id);
const proposedClaim = graphIntentClaim({
targetSessionId: child.id,
targetTurnId: 'prompt-bound-turn',
targetRunId: 'prompt-bound-run',
});
const intent = graphRunnableIntentForClaim(proposedClaim);
let persistedClaim: AgentGraphIntentClaim | undefined;
const durableClaims: AgentGraphIntentClaimStore = {
async claimAgentGraphIntent(request) {
if (persistedClaim) return { claim: persistedClaim, created: false };
persistedClaim = { ...request, claimedAt: 31 };
return { claim: persistedClaim, created: true };
},
async readAgentGraphIntentClaim(graphId, intentId) {
return persistedClaim?.graphId === graphId && persistedClaim.intentId === intentId
? persistedClaim
: undefined;
},
async listAgentGraphIntentClaims(graphId) {
return persistedClaim && (!graphId || persistedClaim.graphId === graphId)
? [persistedClaim]
: [];
},
};
const admitted = await claimAgentGraphRunnableIntent({
intent,
store: durableClaims,
newId: nextId(),
targetTurnId: proposedClaim.targetTurnId,
targetRunId: proposedClaim.targetRunId,
executionInput: { prompt: 'durably claimed prompt A' },
});
let hostedExecutions = 0;
const authority = hostedRootAuthority();
authority.executeRoot = async () => {
hostedExecutions += 1;
};
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
messageAuthority: authority,
hostedAgentGraphExecution: hostedGraphExecutionCapability(durableClaims, runStore),
newId: nextId(),
now: nextNow(31),
});
await expectRejects(
manager.runClaimedAgentGraphIntent({
claimStore: durableClaims,
intent,
graphId: admitted.claim.graphId,
intentId: admitted.claim.intentId,
prompt: 'drifted prompt B',
}),
/does not match its durable claim/,
);
assert.strictEqual(hostedExecutions, 0);
assert.strictEqual(backendBuilds, 0);
assert.deepStrictEqual(await runStore.listSessionInvocations(child.id), []);
assert.deepStrictEqual(await store.readMessages(child.id), []);
assert.strictEqual(
await runStore.readRootTurnAdmission(child.id, proposedClaim.targetTurnId),
undefined,
);
});
test('hosted retry reuses an admitted user message identity before a Run exists', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const parent = await store.create(makeInput({ name: 'Supervisor' }));
const child = await createGraphOperatorSession(store, parent.id);
const claim = graphIntentClaim(
{
targetSessionId: child.id,
targetTurnId: 'admitted-graph-turn',
targetRunId: 'admitted-graph-run',
},
'resume admitted graph root',
);
const descriptor: RootExecutionDescriptor = {
kind: 'claimed_agent_graph_intent',
claim,
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
};
runStore.seedRootTurnAdmission(child.id, claim.targetTurnId, {
runId: claim.targetRunId,
userMessageId: 'durable-graph-user-message',
execution: descriptor,
});
let newIdCalls = 0;
let newIdCallsAtExecution = -1;
const authority = hostedRootAuthority();
authority.executeRoot = async (input) => {
newIdCallsAtExecution = newIdCalls;
assert.strictEqual(input.userMessageId, 'durable-graph-user-message');
for await (const event of input.start({
runId: input.runId,
userMessageId: input.userMessageId,
onRunStarted: () => input.onReady?.(),
})) {
input.onEvent?.(event);
}
};
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
messageAuthority: authority,
hostedAgentGraphExecution: hostedGraphExecutionCapability(
graphExecutionInput(claim, '').claimStore,
runStore,
),
newId: () => `retry-id-${++newIdCalls}`,
now: nextNow(33),
});
const result = await manager.runClaimedAgentGraphIntent(
graphExecutionInput(claim, 'resume admitted graph root'),
);
assert.strictEqual(result.status, 'completed');
assert.strictEqual(newIdCallsAtExecution, 0);
assert.partialDeepStrictEqual(
(await manager.getMessages(child.id)).find(
(message) => message.type === 'user' && message.turnId === claim.targetTurnId,
),
{
id: 'durable-graph-user-message',
text: 'resume admitted graph root',
},
);
});
test('hosted retry rejects an existing Run without its durable RootTurn admission', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backendBuilds = 0;
backends.register('ai-sdk', (ctx) => {
backendBuilds += 1;
return new TestBackend(ctx);
});
const parent = await store.create(makeInput({ name: 'Supervisor' }));
const child = await createGraphOperatorSession(store, parent.id);
const claim = graphIntentClaim(
{
targetSessionId: child.id,
targetTurnId: 'orphaned-hosted-turn',
targetRunId: 'orphaned-hosted-run',
},
'must not be backfilled',
);
await seedInvocationFromHeader(
runStore,
makeRunHeader({
sessionId: child.id,
runId: claim.targetRunId,
turnId: claim.targetTurnId,
status: 'completed',
completedAt: 34,
permissionMode: 'explore',
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
}),
);
let hostedExecutions = 0;
const authority = hostedRootAuthority();
authority.executeRoot = async () => {
hostedExecutions += 1;
};
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
messageAuthority: authority,
hostedAgentGraphExecution: hostedGraphExecutionCapability(
graphExecutionInput(claim, '').claimStore,
runStore,
),
newId: nextId(),
now: nextNow(34),
});
await expectRejects(
manager.runClaimedAgentGraphIntent(graphExecutionInput(claim, 'must not be backfilled')),
/missing its durable RootTurn admission/,
);
assert.strictEqual(hostedExecutions, 0);
assert.strictEqual(backendBuilds, 0);
assert.deepStrictEqual(await store.readMessages(child.id), []);
assert.strictEqual(
runtimeInvocationOutcome(await readInvocation(runStore, child.id, claim.targetRunId)),
'completed',
);
});
test('hosted explicit abort stops only the exact claimed root identity', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const backendGate = makeGate();
const ready = makeGate();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx, backendGate));
const stoppedRoots: RuntimeMessageRunIdentity[] = [];
let stoppedSessions = 0;
const authority = hostedRootAuthority();
authority.stopRoot = async (identity) => {
stoppedRoots.push(identity);
backendGate.release();
};
authority.stopSession = async () => {
stoppedSessions += 1;
};
const parent = await store.create(makeInput({ name: 'Supervisor' }));
const child = await createGraphOperatorSession(store, parent.id);
const claim = graphIntentClaim(
{
targetSessionId: child.id,
targetTurnId: 'aborted-hosted-turn',
targetRunId: 'aborted-hosted-run',
},
'abort this exact root',
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
messageAuthority: authority,
hostedAgentGraphExecution: hostedGraphExecutionCapability(
graphExecutionInput(claim, '').claimStore,
runStore,
),
newId: nextId(),
now: nextNow(35),
});
const abort = new AbortController();
const execution = manager.runClaimedAgentGraphIntent({
...graphExecutionInput(claim, 'abort this exact root'),
abortSignal: abort.signal,
onReady: () => ready.release(),
});
await ready.promise;
abort.abort();
await execution;
assert.deepStrictEqual(stoppedRoots, [
{
sessionId: child.id,
turnId: claim.targetTurnId,
runId: claim.targetRunId,
},
]);
assert.strictEqual(stoppedSessions, 0);
});
test('recovers a pending hosted graph admission without source lineage', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(38),
});
const parent = await store.create(makeInput({ name: 'Supervisor' }));
const child = await createGraphOperatorSession(store, parent.id);
const claim = graphIntentClaim({
targetSessionId: child.id,
targetTurnId: 'recovered-graph-turn',
targetRunId: 'recovered-graph-run',
});
await manager.closePendingHostedAdmission({
sessionId: child.id,
turnId: claim.targetTurnId,
runId: claim.targetRunId,
admittedAt: 37,
execution: {
kind: 'claimed_agent_graph_intent',
claim,
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
},
});
const run = await readInvocation(runStore, child.id, claim.targetRunId);
assert.strictEqual(runtimeInvocationOutcome(run), 'failed');
assert.strictEqual(runtimeInvocationFailureClass(run), 'app_restarted');
assert.partialDeepStrictEqual(run.opening.lineage, {
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
});
assert.strictEqual(run.opening.configuration.workspaceIdentity, undefined);
assert.strictEqual(run.opening.lineage?.resumedFromRunId, undefined);
assert.strictEqual(run.opening.lineage?.retriedFromRunId, undefined);
const terminalEvents = (await runStore.readRuntimeEvents(child.id, claim.targetRunId)).filter(
(event) => event.status === 'failed',
);
assert.strictEqual(terminalEvents.length, 1);
assert.partialDeepStrictEqual(terminalEvents[0]?.actions?.stateDelta, {
recovered: true,
recoveryReason: 'child_internal_admission_without_run',
executionKind: 'claimed_agent_graph_intent',
failureClass: 'app_restarted',
});
assert.strictEqual(terminalEvents[0]?.actions?.stateDelta?.sourceRunId, undefined);
});
test('runs the exact claimed session-inline activation and durably deduplicates retries', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const backendsBySession = new Map<string, TestBackend>();
backends.register('ai-sdk', (ctx) => {
const backend = new TestBackend(ctx);
backendsBySession.set(ctx.sessionId, backend);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
listArtifactsForTurn: async (sessionId, turnId) => [
{
id: 'child-output',
sessionId,
turnId,
createdAt: 98,
name: 'answer.txt',
kind: 'file',
relativePath: `${sessionId}/child-output-answer.txt`,
sizeBytes: 6,
source: 'tool_result',
},
{
id: 'child-internal-archive',
sessionId,
turnId,
createdAt: 99,
name: 'tool-result.json',
kind: 'file',
relativePath: `${sessionId}/child-internal-archive-tool-result.json`,
sizeBytes: 12,
source: 'tool_result_archive',
},
],
newId: nextId(),
now: nextNow(40),
});
const parent = await manager.createSession(makeInput({ name: 'Supervisor' }));
const child = await createGraphOperatorSession(store, parent.id);
const claim = graphIntentClaim(
{
targetSessionId: child.id,
targetTurnId: 'graph-turn',
targetRunId: 'graph-run',
},
'summarize the routed records',
);
const ready: unknown[] = [];
const result = await manager.runClaimedAgentGraphIntent({
...graphExecutionInput(claim, 'summarize the routed records'),
onReady: (input) => {
ready.push(input);
},
});
assert.partialDeepStrictEqual(result, {
claimId: claim.claimId,
graphId: claim.graphId,
intentId: claim.intentId,
operatorId: claim.targetOperatorId,
childSessionId: child.id,
turnId: 'graph-turn',
runId: 'graph-run',
agentId: LOCAL_READ_AGENT_ID,
status: 'completed',
summary: 'ok',
});
assert.deepStrictEqual(result.artifactIds, ['child-output']);
assert.deepStrictEqual(ready, [
{
claimId: claim.claimId,
graphId: claim.graphId,
intentId: claim.intentId,
operatorId: claim.targetOperatorId,
childSessionId: child.id,
turnId: 'graph-turn',
runId: 'graph-run',
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
},
]);
const run = await readInvocation(runStore, child.id, 'graph-run');
assert.strictEqual(isSessionInlineInvocation(run.opening), true);
assert.strictEqual(run.opening.lineage?.parentRunId, undefined);
assert.strictEqual(run.turnId, 'graph-turn');
assert.partialDeepStrictEqual(
(await manager.getMessages(child.id)).find(
(message) => message.type === 'user' && message.turnId === 'graph-turn',
),
{ text: 'summarize the routed records' },
);
await store.updateHeader(child.id, { isArchived: true });
const retry = await manager.runClaimedAgentGraphIntent({
...graphExecutionInput(claim, 'summarize the routed records'),
});
assert.partialDeepStrictEqual(retry, {
claimId: result.claimId,
childSessionId: result.childSessionId,
turnId: result.turnId,
runId: result.runId,
status: 'completed',
summary: 'ok',
});
assert.strictEqual((await runStore.listSessionInvocations(child.id)).length, 1);
assert.strictEqual(backendsBySession.get(child.id)?.sendInputs.length, 1);
await expectRejects(
manager.runClaimedAgentGraphIntent({
...graphExecutionInput(claim, 'perform different work'),
}),
/does not match its durable claim/,
);
});
test('joins concurrent execution of one claim and rejects in-flight input drift', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const childGate = makeGate();
const started = makeGate();
let childBackend: TestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
childBackend = new TestBackend(ctx, childGate);
return childBackend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(60),
});
const parent = await manager.createSession(makeInput());
const child = await createGraphOperatorSession(store, parent.id);
const claim = graphIntentClaim({ targetSessionId: child.id }, 'one activation');
const first = manager.runClaimedAgentGraphIntent({
...graphExecutionInput(claim, 'one activation'),
onReady: () => started.release(),
});
await started.promise;
const joined = manager.runClaimedAgentGraphIntent({
...graphExecutionInput(claim, 'one activation'),
});
await expectRejects(
manager.runClaimedAgentGraphIntent({
...graphExecutionInput(claim, 'drifted activation'),
}),
/does not match its durable claim/,
);
childGate.release();
const [firstResult, joinedResult] = await Promise.all([first, joined]);
assert.deepStrictEqual(joinedResult, firstResult);
assert.strictEqual(childBackend?.sendInputs.length, 1);
assert.strictEqual((await runStore.listSessionInvocations(child.id)).length, 1);
});
test('serializes different claims per child session without letting a queued abort stop active work', async () => {
const { store, runStore, manager, child, backend, activeGate, first, claims } =
await createQueuedGraphScenario();
const [firstClaim, secondClaim, thirdClaim] = claims;
const queuedAbort = new AbortController();
let secondReadyCount = 0;
const second = manager.runClaimedAgentGraphIntent({
...graphExecutionInput(secondClaim, 'queued activation'),
abortSignal: queuedAbort.signal,
onReady: () => {
secondReadyCount += 1;
},
});
await new Promise<void>((resolve) => setImmediate(resolve));
assert.strictEqual(secondReadyCount, 0);
assert.strictEqual(backend?.sendInputs.length, 1);
queuedAbort.abort();
await new Promise<void>((resolve) => setImmediate(resolve));
assert.strictEqual(backend?.stopCalls, 0);
assert.strictEqual((await runStore.listSessionInvocations(child.id)).length, 1);
activeGate.release();
assert.strictEqual((await first).status, 'completed');
await expectRejects(second, /cancelled before runtime admission/);
assert.strictEqual(secondReadyCount, 0);
assert.strictEqual(backend?.stopCalls, 0);
assert.strictEqual(backend?.sendInputs.length, 1);
const third = await manager.runClaimedAgentGraphIntent(
graphExecutionInput(thirdClaim, 'third activation'),
);
assert.strictEqual(third.status, 'completed');
assert.strictEqual(backend?.sendInputs.length, 2);
assert.strictEqual((await runStore.listSessionInvocations(child.id)).length, 2);
});
test('evaluates execution admission only after a claimed child-session slot is available', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const firstGate = makeGate();
const firstReady = makeGate();
let backend: TestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new TestBackend(ctx, firstGate);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(75),
});
const parent = await manager.createSession(makeInput());
const child = await createGraphOperatorSession(store, parent.id);
const firstClaim = graphIntentClaim({ targetSessionId: child.id }, 'first activation');
const queuedClaim = graphIntentClaim(
{
claimId: `graph_claim_${'7'.repeat(32)}`,
intentId: `graph_intent_${'8'.repeat(32)}`,
targetSessionId: child.id,
targetTurnId: 'queued-turn',
targetRunId: 'queued-run',
},
'cancelled queued activation',
);
const first = manager.runClaimedAgentGraphIntent({
...graphExecutionInput(firstClaim, 'first activation'),
onReady: () => firstReady.release(),
});
await firstReady.promise;
let admissionChecks = 0;
const queued = manager.runClaimedAgentGraphIntent({
...graphExecutionInput(queuedClaim, 'cancelled queued activation'),
async admitExecution() {
admissionChecks += 1;
return 'cancelled';
},
});
await new Promise<void>((resolve) => setImmediate(resolve));
assert.strictEqual(admissionChecks, 0);
firstGate.release();
assert.strictEqual((await first).status, 'completed');
await expectRejects(queued, /cancelled before runtime admission/);
assert.strictEqual(admissionChecks, 1);
assert.strictEqual(backend?.sendInputs.length, 1);
assert.strictEqual((await runStore.listSessionInvocations(child.id)).length, 1);
});
test('keeps a stop pending across graph admission with an idle cached backend', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const admissionStarted = makeGate();
const releaseAdmission = makeGate();
let backend: TestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new TestBackend(ctx);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(77),
});
const parent = await manager.createSession(makeInput());
const child = await createGraphOperatorSession(store, parent.id);
await drain(
manager.sendMessage(child.id, {
turnId: 'completed-before-admission',
text: 'warm the cached operator backend',
}),
);
assert.strictEqual(backend?.sendInputs?.length, 1);
const claim = graphIntentClaim(
{ targetSessionId: child.id },
'activation stopped during admission',
);
const execution = manager.runClaimedAgentGraphIntent({
...graphExecutionInput(claim, 'activation stopped during admission'),
async admitExecution() {
admissionStarted.release();
await releaseAdmission.promise;
return 'executing';
},
});
await admissionStarted.promise;
let stopSettled = false;
const stop = manager.stopSession(child.id, { source: 'graph_supervisor' }).finally(() => {
stopSettled = true;
});
await Promise.resolve();
assert.strictEqual(stopSettled, false);
assert.strictEqual(backend?.sendInputs?.length, 1);
releaseAdmission.release();
await stop;
const result = await execution;
assert.strictEqual(result.status, 'cancelled');
assert.strictEqual(backend?.stopCalls, 1);
assert.strictEqual(backend?.sendInputs?.length, 1);
assert.strictEqual(
runtimeInvocationOutcome(await readInvocation(runStore, child.id, claim.targetRunId)),
'cancelled',
);
});
test('runtime stop settles queued graph claims without letting their slots pass the active claim', async () => {
const firstAbort = new AbortController();
const { store, runStore, manager, child, backend, stopStarted, first, claims } =
await createQueuedGraphScenario(firstAbort.signal);
const [firstClaim, secondClaim, thirdClaim] = claims;
let queuedReady = 0;
const second = manager.runClaimedAgentGraphIntent({
...graphExecutionInput(secondClaim, 'queued activation'),
onReady: () => {
queuedReady += 1;
},
});
const third = manager.runClaimedAgentGraphIntent({
...graphExecutionInput(thirdClaim, 'third activation'),
onReady: () => {
queuedReady += 1;
},
});
await new Promise<void>((resolve) => setImmediate(resolve));
firstAbort.abort();
await stopStarted.promise;
const bound = new AbortController();
let results: Awaited<ReturnType<typeof Promise.allSettled>>;
try {
results = await Promise.race([
Promise.allSettled([first, second, third]),
timerDelay(2_000, undefined, { signal: bound.signal }).then(() => {
throw new Error('graph stop composition did not settle within the bound');
}),
]);
} finally {
bound.abort();
}
assert.strictEqual(results[0]?.status, 'fulfilled');
assert.strictEqual(results[1]?.status, 'rejected');
assert.strictEqual(results[2]?.status, 'rejected');
assert.strictEqual(queuedReady, 0);
assert.deepStrictEqual(
backend?.sendInputs.map((input) => input.turnId),
[firstClaim.targetTurnId],
);
assert.deepStrictEqual(
(await runStore.listSessionInvocations(child.id)).map((run) => run.turnId),
[firstClaim.targetTurnId],
);
assert.deepStrictEqual(
(await manager.getMessages(child.id)).filter(
(message) =>
'turnId' in message &&
(message.turnId === secondClaim.targetTurnId ||
message.turnId === thirdClaim.targetTurnId),
),
[],
);
});
test('recovers an existing nonterminal claimed run without invoking the backend again', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backendBuilds = 0;
backends.register('ai-sdk', (ctx) => {
backendBuilds += 1;
return new TestBackend(ctx);
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(80),
});
const parent = await manager.createSession(makeInput());
const child = await createGraphOperatorSession(store, parent.id);
const claim = graphIntentClaim({ targetSessionId: child.id }, 'interrupted turn');
await seedRunningTurn(store, child.id, claim.targetTurnId);
await seedRun(
runStore,
makeRunHeader({
sessionId: child.id,
runId: claim.targetRunId,
turnId: claim.targetTurnId,
status: 'running',
permissionMode: 'explore',
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
}),
[
makeRunEvent({
sessionId: child.id,
runId: claim.targetRunId,
turnId: claim.targetTurnId,
type: 'turn_started',
ts: 81,
}),
],
);
const recovered = await manager.runClaimedAgentGraphIntent({
...graphExecutionInput(claim, 'interrupted turn'),
});
assert.strictEqual(recovered.status, 'failed');
assert.strictEqual(recovered.failureClass, 'app_restarted');
assert.strictEqual(backendBuilds, 0);
assert.strictEqual((await runStore.listSessionInvocations(child.id)).length, 1);
});
test('target Session stop owns a claimed graph execution before its first runtime preflight', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backend: TestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new TestBackend(ctx);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(90),
});
const parent = await manager.createSession(makeInput());
const child = await createGraphOperatorSession(store, parent.id);
const claim = graphIntentClaim(
{
targetSessionId: child.id,
targetTurnId: 'stopped-graph-turn',
targetRunId: 'stopped-graph-run',
},
'must stop before provider dispatch',
);
const readStarted = makeGate();
const releaseRead = makeGate();
store.nextReadHeaderGate = { started: readStarted, release: releaseRead };
const executing = manager.runClaimedAgentGraphIntent(
graphExecutionInput(claim, 'must stop before provider dispatch'),
);
await readStarted.promise;
let stopSettled = false;
const stopping = manager.stopSession(child.id, { source: 'stop_button' }).finally(() => {
stopSettled = true;
});
await Promise.resolve();
assert.strictEqual(stopSettled, false);
releaseRead.release();
const [result] = await Promise.all([executing, stopping]);
assert.strictEqual(result.status, 'cancelled');
assert.deepStrictEqual(backend?.sendInputs, []);
assert.strictEqual(
runtimeInvocationOutcome(await readInvocation(runStore, child.id, claim.targetRunId)),
'cancelled',
);
assert.strictEqual((await store.readHeader(child.id)).status === 'blocked', false);
});
test('fails closed before runtime when claim routing collides or targets a main session', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backendBuilds = 0;
backends.register('ai-sdk', (ctx) => {
backendBuilds += 1;
return new TestBackend(ctx);
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(100),
});
const parent = await manager.createSession(makeInput());
const unclaimedIntent = graphRunnableIntentForClaim(
graphIntentClaim({
graphId: 'graph-unclaimed',
intentId: `graph_intent_${'f'.repeat(32)}`,
}),
);
await expectRejects(
manager.runClaimedAgentGraphIntent({
claimStore: {
async claimAgentGraphIntent() {
throw new Error('read-only');
},
async readAgentGraphIntentClaim() {
return undefined;
},
async listAgentGraphIntentClaims() {
return [];
},
},
intent: unclaimedIntent,
graphId: 'graph-unclaimed',
intentId: `graph_intent_${'f'.repeat(32)}`,
prompt: 'must not run',
}),
/has not been claimed/,
);
const mainSessionClaim = graphIntentClaim({ targetSessionId: parent.id }, 'must not run');
await expectRejects(
manager.runClaimedAgentGraphIntent(graphExecutionInput(mainSessionClaim, 'must not run')),
/target must be a linked child session/,
);
const child = await createGraphOperatorSession(store, parent.id);
const claim = graphIntentClaim({ targetSessionId: child.id }, 'must not run');
await seedInvocationFromHeader(
runStore,
makeRunHeader({
sessionId: child.id,
runId: 'different-run',
turnId: claim.targetTurnId,
status: 'completed',
permissionMode: 'explore',
completedAt: 101,
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
}),
);
await expectRejects(
manager.runClaimedAgentGraphIntent(graphExecutionInput(claim, 'must not run')),
/already owned by run different-run/,
);
const archivedChild = await createGraphOperatorSession(store, parent.id);
await store.updateHeader(archivedChild.id, { isArchived: true });
const archivedClaim = graphIntentClaim(
{
claimId: `graph_claim_${'3'.repeat(32)}`,
intentId: `graph_intent_${'4'.repeat(32)}`,
targetSessionId: archivedChild.id,
targetTurnId: 'archived-turn',
targetRunId: 'archived-run',
},
'must not revive archived work',
);
await expectRejects(
manager.runClaimedAgentGraphIntent(
graphExecutionInput(archivedClaim, 'must not revive archived work'),
),
/target child session is terminated/,
);
const abortedChild = await createGraphOperatorSession(store, parent.id);
await store.updateHeader(abortedChild.id, { status: 'aborted' });
const abortedClaim = graphIntentClaim(
{
claimId: `graph_claim_${'5'.repeat(32)}`,
intentId: `graph_intent_${'6'.repeat(32)}`,
targetSessionId: abortedChild.id,
targetTurnId: 'aborted-turn',
targetRunId: 'aborted-run',
},
'must not revive aborted work',
);
await expectRejects(
manager.runClaimedAgentGraphIntent(
graphExecutionInput(abortedClaim, 'must not revive aborted work'),
),
/target child session is terminated/,
);
assert.strictEqual(backendBuilds, 0);
});
});
describe('SessionManager child-session runtime primitive', () => {
test('runs an explicitly delegated child through a plugin executor with no Maka tools', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const parentGate = makeGate();
let childContext: BackendFactoryContext | undefined;
const checkedExecutors: string[] = [];
backends.register('ai-sdk', (ctx) => new TestBackend(ctx, parentGate));
backends.register('plugin-executor', (ctx) => {
childContext = ctx;
return {
kind: 'plugin-executor' as const,
sessionId: ctx.sessionId,
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
yield {
type: 'text_complete',
id: 'external-complete-text',
turnId: input.turnId,
ts: 1,
messageId: 'external-message',
text: 'external result',
};
yield {
type: 'complete',
id: 'external-complete',
turnId: input.turnId,
ts: 2,
stopReason: 'end_turn',
};
},
async stop() {},
async respondToSandboxBoundary() {},
async dispose() {},
};
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [],
assertChildExecutorAvailable: (parentSessionId, executorId) => {
checkedExecutors.push(`${parentSessionId}:${executorId}`);
},
newId: nextId(),
now: nextNow(80),
});
const parent = await manager.createSession(makeInput());
const parentTurn = manager
.sendMessage(parent.id, { turnId: 'parent-turn', text: 'delegate externally' })
[Symbol.asyncIterator]();
await parentTurn.next();
const [parentRun] = await runStore.listSessionInvocations(parent.id);
if (!parentRun) throw new Error('parent run was not recorded');
const result = await manager.spawnChildSession(parent.id, {
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'tool-call-external',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
executorId: 'codex',
prompt: 'inspect through codex',
});
const child = await store.readHeader(result.childSessionId);
assert.strictEqual(result.status, 'completed');
assert.strictEqual(result.summary, 'external result');
assert.strictEqual(child.backend, 'plugin-executor');
assert.strictEqual(child.executorId, 'codex');
assert.strictEqual(child.llmConnectionId, undefined);
assert.deepStrictEqual(child.subagentRuntime?.toolNames, []);
assert.deepStrictEqual(childContext?.tools, []);
assert.strictEqual(childContext?.systemPrompt, LOCAL_READ_AGENT_DEFINITION.systemPrompt);
assert.deepStrictEqual(checkedExecutors, [`${parent.id}:codex`]);
parentGate.release();
while (!(await parentTurn.next()).done) {}
});
test('creates a fresh read-only child with a session-inline first run and no parent history', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const parentGate = makeGate();
const contexts: BackendFactoryContext[] = [];
const backendActivationSessions: string[] = [];
const backendsBySession = new Map<string, TestBackend>();
backends.register('ai-sdk', (ctx) => {
contexts.push(ctx);
const backend = new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate);
backendsBySession.set(ctx.sessionId, backend);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(100),
runBackendActivation: async (operation) => {
const contextIndex = contexts.length;
const result = await operation();
const activatedContext = contexts[contextIndex];
if (!activatedContext) throw new Error('Backend activation did not build a backend');
backendActivationSessions.push(activatedContext.sessionId);
return result;
},
});
const parent = await manager.createSession(
makeInput({
cwd: '/tmp/project',
projectId: 'project-1',
llmConnectionSlug: 'connection-1',
model: 'model-1',
thinkingLevel: 'medium',
permissionMode: 'ask',
}),
);
const parentTurn = manager
.sendMessage(parent.id, { turnId: 'parent-turn', text: 'private parent history' })
[Symbol.asyncIterator]();
await parentTurn.next();
const [parentRun] = await runStore.listSessionInvocations(parent.id);
if (!parentRun) throw new Error('parent run was not recorded');
const result = await manager.spawnChildSession(parent.id, {
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'tool-call-1',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
prompt: 'inspect the storage boundary',
});
const childHeader = await store.readHeader(result.childSessionId);
assert.deepStrictEqual(
await store.readExecutionBoundary(result.childSessionId),
await store.readExecutionBoundary(parent.id),
);
assert.strictEqual(childHeader.cwd, '/tmp/project');
assert.strictEqual(childHeader.projectId, 'project-1');
assert.strictEqual(
childHeader.workspaceRoot,
(await store.readHeader(parent.id)).workspaceRoot,
);
assert.strictEqual(childHeader.llmConnectionSlug, 'connection-1');
assert.strictEqual(childHeader.model, 'model-1');
assert.strictEqual(childHeader.thinkingLevel, 'medium');
assert.strictEqual(childHeader.permissionMode, 'explore');
assert.strictEqual(childHeader.connectionLocked, true);
assert.deepStrictEqual(childHeader.subagentParent, {
kind: 'subagent',
parentSessionId: parent.id,
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'tool-call-1',
},
lifecycle: 'foreground',
});
assert.deepStrictEqual(childHeader.subagentRuntime, {
schemaVersion: 1,
definitionVersion: 1,
agentId: LOCAL_READ_AGENT_ID,
agentName: 'Local Read',
profile: LOCAL_READ_AGENT_PROFILE,
systemPrompt: LOCAL_READ_AGENT_DEFINITION.systemPrompt,
toolNames: ['Read', 'Glob', 'Grep'],
categoryPolicy: {},
});
assert.strictEqual(childHeader.subagentSpawn?.schemaVersion, 1);
assert.match(String(childHeader.subagentSpawn?.requestFingerprint), /^[a-f0-9]{64}$/);
assert.strictEqual(childHeader.subagentSpawn?.initialTurnId, result.turnId);
assert.strictEqual(childHeader.subagentSpawn?.initialRunId, result.runId);
const [childRun] = await runStore.listSessionInvocations(result.childSessionId);
if (!childRun) throw new Error('child run was not recorded');
assert.strictEqual(childRun.runId, result.runId);
assert.strictEqual(childRun.opening.lineage?.parentRunId, undefined);
assert.strictEqual(childRun.opening.lineage?.agentId, LOCAL_READ_AGENT_ID);
assert.strictEqual(isSessionInlineInvocation(childRun.opening), true);
assert.strictEqual(result.status, 'completed');
assert.deepStrictEqual(backendActivationSessions, [parent.id, result.childSessionId]);
assert.strictEqual(
(await runStore.readRuntimeEvents(result.childSessionId, childRun.runId)).every(
(event) => event.sessionId === result.childSessionId,
),
true,
);
const childContext = contexts.find((ctx) => ctx.sessionId === result.childSessionId);
assert.strictEqual(childContext?.systemPrompt, LOCAL_READ_AGENT_DEFINITION.systemPrompt);
assert.deepStrictEqual(
childContext?.tools?.map((tool) => tool.name),
['Read', 'Glob', 'Grep'],
);
assert.strictEqual(
backendsBySession.get(result.childSessionId)?.sendInputs[0]?.context,
undefined,
);
assert.strictEqual(
backendsBySession
.get(result.childSessionId)
?.sendInputs[0]?.runtimeContext?.some(
(event) =>
event.content?.kind === 'text' && event.content.text === 'private parent history',
) ?? false,
false,
);
const parentMessages = await manager.getMessages(parent.id);
const childMessages = await manager.getMessages(result.childSessionId);
assert.strictEqual(
parentMessages.some(
(message) => message.type === 'user' && message.text === 'inspect the storage boundary',
),
false,
);
assert.strictEqual(
childMessages.some(
(message) => message.type === 'user' && message.text === 'inspect the storage boundary',
),
true,
);
const projection = await manager.listChildAgents(parent.id);
assert.deepStrictEqual(projection.runs, []);
assert.strictEqual(projection.executions.length, 1);
assert.deepStrictEqual(projection.executions[0]?.execution, {
kind: 'child_session',
sessionId: result.childSessionId,
currentRunId: result.runId,
});
assert.strictEqual(projection.executions[0]?.status, 'completed');
const output = await manager.readChildAgentOutput(parent.id, {
execution: {
kind: 'child_session',
sessionId: result.childSessionId,
},
});
assert.deepStrictEqual(output.execution, {
kind: 'child_session',
sessionId: result.childSessionId,
currentRunId: result.runId,
});
assert.strictEqual(output.invocation.sessionId, result.childSessionId);
assert.strictEqual(output.invocation.runId, result.runId);
const unrelatedParent = await manager.createSession(makeInput({ name: 'Unrelated parent' }));
await expectRejects(
manager.readChildAgentOutput(unrelatedParent.id, {
execution: {
kind: 'child_session',
sessionId: result.childSessionId,
},
}),
/could not find the requested child session/,
);
parentGate.release();
while (!(await parentTurn.next()).done) {}
});
test('freezes a configured subagent model target independently from the parent', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const parentGate = makeGate();
backends.register(
'ai-sdk',
(ctx) => new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
subagentCatalog: {
list: async () => [
{
id: 'fast-reader',
name: 'Fast reader',
description: 'Cheap scans',
profile: 'local_read',
connectionSlug: 'worker-connection',
model: 'worker-model',
thinkingLevel: 'low',
enabled: true,
availability: { status: 'available' },
},
],
resolve: async (id) => {
if (id !== 'fast-reader') throw new Error('unknown preset');
return {
connectionId: '33333333-3333-4333-8333-333333333333',
id,
name: 'Fast reader',
description: 'Cheap scans',
profile: 'local_read',
connectionSlug: 'worker-connection',
model: 'worker-model',
thinkingLevel: 'low',
enabled: true,
};
},
},
newId: nextId(),
now: nextNow(150),
});
const parent = await manager.createSession(
makeInput({
llmConnectionSlug: 'parent-connection',
model: 'parent-model',
thinkingLevel: 'high',
}),
);
const parentTurn = manager
.sendMessage(parent.id, { turnId: 'parent-turn-preset', text: 'delegate' })
[Symbol.asyncIterator]();
await parentTurn.next();
const [parentRun] = await runStore.listSessionInvocations(parent.id);
if (!parentRun) throw new Error('parent run was not recorded');
const result = await manager.spawnChildSession(parent.id, {
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'tool-call-preset',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
subagentId: 'fast-reader',
prompt: 'inspect cheaply',
});
const child = await store.readHeader(result.childSessionId);
assert.strictEqual(child.llmConnectionSlug, 'worker-connection');
assert.strictEqual(child.llmConnectionId, '33333333-3333-4333-8333-333333333333');
assert.strictEqual(child.model, 'worker-model');
assert.strictEqual(child.thinkingLevel, 'low');
assert.strictEqual(child.subagentRuntime?.presetId, 'fast-reader');
assert.strictEqual(child.subagentRuntime?.agentName, 'Fast reader');
assert.strictEqual(child.connectionLocked, true);
assert.strictEqual((await manager.listChildAgents(parent.id)).presets[0]?.id, 'fast-reader');
parentGate.release();
while (!(await parentTurn.next()).done) {}
});
test('child sessions preserve an explicit no-project association', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const parentGate = makeGate();
backends.register(
'ai-sdk',
(ctx) => new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(200),
});
const parent = await manager.createSession(makeInput({ projectId: null }));
const parentTurn = manager
.sendMessage(parent.id, { turnId: 'parent-turn', text: 'keep the parent active' })
[Symbol.asyncIterator]();
await parentTurn.next();
const [parentRun] = await runStore.listSessionInvocations(parent.id);
if (!parentRun) throw new Error('parent run was not recorded');
const child = await manager.spawnChildSession(parent.id, {
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'tool-call-no-project',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
prompt: 'inspect without a project',
});
assert.strictEqual((await store.readHeader(child.childSessionId)).projectId, null);
parentGate.release();
while (!(await parentTurn.next()).done) {}
});
test('hosted child spawn joins the exact in-flight identity and rejects request drift', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const parentGate = makeGate();
const childGate = makeGate();
const backendsBySession = new Map<string, TestBackend>();
backends.register('ai-sdk', (ctx) => {
const backend = new TestBackend(ctx, ctx.header.subagentRuntime ? childGate : parentGate);
backendsBySession.set(ctx.sessionId, backend);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(150),
messageAuthority: hostedRootAuthority(),
});
const parent = await manager.createSession(makeInput());
const parentTurn = manager
.sendMessage(parent.id, { turnId: 'parent-turn', text: 'keep parent active' })
[Symbol.asyncIterator]();
await parentTurn.next();
const [parentRun] = await runStore.listSessionInvocations(parent.id);
if (!parentRun) throw new Error('parent run was not recorded');
const spawnInput = {
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'same-tool-call',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
prompt: 'one durable task',
} as const;
const ready = makeGate();
const first = manager.spawnChildSession(parent.id, {
...spawnInput,
onReady: () => ready.release(),
});
await ready.promise;
const joined = manager.spawnChildSession(parent.id, spawnInput);
await expectRejects(
manager.spawnChildSession(parent.id, { ...spawnInput, prompt: 'different work' }),
/reused for different work/,
);
assert.strictEqual((await manager.listChildSessions(parent.id)).length, 1);
childGate.release();
const [firstResult, joinedResult] = await Promise.all([first, joined]);
assert.strictEqual(joinedResult.childSessionId, firstResult.childSessionId);
assert.strictEqual(joinedResult.runId, firstResult.runId);
assert.strictEqual(
(await runStore.listSessionInvocations(firstResult.childSessionId)).length,
1,
);
const durableRetry = await manager.spawnChildSession(parent.id, spawnInput);
assert.strictEqual(durableRetry.childSessionId, firstResult.childSessionId);
assert.strictEqual(durableRetry.runId, firstResult.runId);
assert.strictEqual(durableRetry.summary, 'ok');
assert.strictEqual((await manager.listChildSessions(parent.id)).length, 1);
assert.strictEqual(backendsBySession.get(firstResult.childSessionId)?.sendInputs.length, 1);
parentGate.release();
while (!(await parentTurn.next()).done) {}
});
test('starts a metadata-only retry once, notifies once, and rechecks cancellation', async () => {
const store = new MemorySessionStore();
const abortController = new AbortController();
const runStore = new MemoryAgentRunStore({
beforeListSessionRuns: (sessionId) => {
if (sessionId === 'session-3') abortController.abort();
},
});
const backends = new BackendRegistry();
const parentGate = makeGate();
backends.register(
'ai-sdk',
(ctx) => new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(170),
});
const parent = await manager.createSession(makeInput());
const parentTurn = manager
.sendMessage(parent.id, { turnId: 'parent-turn', text: 'keep parent active' })
[Symbol.asyncIterator]();
await parentTurn.next();
const [parentRun] = await runStore.listSessionInvocations(parent.id);
if (!parentRun) throw new Error('parent run was not recorded');
const seedMetadataOnlyChild = async (
toolCallId: string,
prompt: string,
initialTurnId: string,
initialRunId: string,
): Promise<SessionHeader> => {
const requestFingerprint = createHash('sha256')
.update(
JSON.stringify([
1,
parent.id,
parentRun.runId,
parentRun.turnId,
toolCallId,
LOCAL_READ_AGENT_PROFILE,
prompt,
null,
null,
]),
)
.digest('hex');
return (
await store.createSubagent(
makeInput({
permissionMode: 'explore',
collaborationMode: 'agent',
orchestrationMode: 'default',
subagentParent: {
kind: 'subagent',
parentSessionId: parent.id,
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId,
},
lifecycle: 'foreground',
},
subagentRuntime: {
schemaVersion: 1,
definitionVersion: LOCAL_READ_AGENT_DEFINITION.definitionVersion,
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
profile: LOCAL_READ_AGENT_PROFILE,
systemPrompt: LOCAL_READ_AGENT_DEFINITION.systemPrompt,
toolNames: [...LOCAL_READ_AGENT_DEFINITION.tools],
categoryPolicy: {},
},
subagentSpawn: {
schemaVersion: 1,
requestFingerprint,
initialTurnId,
initialRunId,
},
}),
)
).header;
};
const metadataOnly = await seedMetadataOnlyChild(
'metadata-only-tool',
'resume after metadata commit',
'metadata-only-turn',
'metadata-only-run',
);
let readyCalls = 0;
const resumed = await manager.spawnChildSession(parent.id, {
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'metadata-only-tool',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
prompt: 'resume after metadata commit',
onReady: () => {
readyCalls += 1;
},
});
assert.strictEqual(resumed.childSessionId, metadataOnly.id);
assert.strictEqual(resumed.runId, 'metadata-only-run');
assert.strictEqual(readyCalls, 1);
assert.strictEqual((await runStore.listSessionInvocations(metadataOnly.id)).length, 1);
const cancelled = await seedMetadataOnlyChild(
'cancelled-metadata-tool',
'must remain cancelled',
'cancelled-child-turn',
'cancelled-child-run',
);
let cancelledReadyCalls = 0;
await expectRejects(
manager.spawnChildSession(parent.id, {
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'cancelled-metadata-tool',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
prompt: 'must remain cancelled',
abortSignal: abortController.signal,
onReady: () => {
cancelledReadyCalls += 1;
},
}),
/cancelled before its first run/,
);
assert.strictEqual(cancelledReadyCalls, 0);
assert.deepStrictEqual(await runStore.listSessionInvocations(cancelled.id), []);
parentGate.release();
while (!(await parentTurn.next()).done) {}
});
test('reopens a child from its exact runtime snapshot after the builtin profile changes', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const parentGate = makeGate();
const contexts: BackendFactoryContext[] = [];
backends.register('ai-sdk', (ctx) => {
contexts.push(ctx);
return new TestBackend(ctx, ctx.header.subagentRuntime ? undefined : parentGate);
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(175),
});
const parent = await manager.createSession(makeInput());
const parentTurn = manager
.sendMessage(parent.id, { turnId: 'parent-turn', text: 'keep parent active' })
[Symbol.asyncIterator]();
await parentTurn.next();
const [parentRun] = await runStore.listSessionInvocations(parent.id);
if (!parentRun) throw new Error('parent run was not recorded');
const child = await manager.spawnChildSession(parent.id, {
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'snapshot-tool-call',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
prompt: 'first child turn',
});
const durablePrompt = (await store.readHeader(child.childSessionId)).subagentRuntime
?.systemPrompt;
if (!durablePrompt) throw new Error('child runtime snapshot was not persisted');
const originalPrompt = LOCAL_READ_AGENT_DEFINITION.systemPrompt;
let parentTurnDrained = false;
const drainParentTurn = async (): Promise<void> => {
parentGate.release();
while (!(await parentTurn.next()).done) {}
parentTurnDrained = true;
};
try {
LOCAL_READ_AGENT_DEFINITION.systemPrompt = 'Changed catalog prompt that must not leak.';
let refreshSettled = false;
const refresh = manager.refreshIdleBackends().finally(() => {
refreshSettled = true;
});
await Promise.resolve();
assert.strictEqual(refreshSettled, false);
assert.strictEqual(contexts.filter((ctx) => ctx.sessionId === parent.id).length, 1);
await drainParentTurn();
await refresh;
await drain(
manager.sendMessage(child.childSessionId, {
turnId: 'child-follow-up',
text: 'use the durable profile',
}),
);
} finally {
if (!parentTurnDrained) await drainParentTurn();
LOCAL_READ_AGENT_DEFINITION.systemPrompt = originalPrompt;
}
const childContexts = contexts.filter((ctx) => ctx.sessionId === child.childSessionId);
assert.strictEqual(childContexts.length, 2);
assert.strictEqual(childContexts[1]?.systemPrompt, durablePrompt);
assert.deepStrictEqual(
childContexts[1]?.tools?.map((tool) => tool.name),
['Read', 'Glob', 'Grep'],
);
const missingToolManager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob')],
newId: nextId(),
now: nextNow(185),
});
await expectRejects(
drain(
missingToolManager.sendMessage(child.childSessionId, {
turnId: 'missing-tool-follow-up',
text: 'must fail closed',
}),
),
/runtime tool snapshot is unavailable/,
);
});
test('reopens after restart with isolated history, tool activity, usage, and compaction', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const parentGate = makeGate();
const compactCalls: Array<{ turnId: string; runtimeContextCount: number }> = [];
const childBackends: LifecycleChildBackend[] = [];
backends.register('ai-sdk', (ctx) => {
if (!ctx.header.subagentRuntime) return new TestBackend(ctx, parentGate);
const backend = new LifecycleChildBackend(ctx, compactCalls);
childBackends.push(backend);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(186),
});
const parent = await manager.createSession(makeInput());
const parentTurn = manager
.sendMessage(parent.id, { turnId: 'parent-turn', text: 'private parent context' })
[Symbol.asyncIterator]();
await parentTurn.next();
const [parentRun] = await runStore.listSessionInvocations(parent.id);
if (!parentRun) throw new Error('parent run was not recorded');
const child = await manager.spawnChildSession(parent.id, {
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'restart-observation-tool',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
prompt: 'inspect README before restart',
});
// A new manager represents a restarted Runtime Host. It must activate the
// child through the durable Session header and replay only child history.
const restarted = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
// Its own id space: a restarted host mints fresh ids, it does not replay
// the sequence the dead process was on.
newId: nextId('restarted'),
now: nextNow(196),
});
await drain(
restarted.sendMessage(child.childSessionId, {
turnId: 'child-follow-up',
text: 'inspect it again after restart',
}),
);
const restartedBackend = childBackends.at(-1);
const followUpContext = restartedBackend?.sendInputs.at(-1)?.runtimeContext ?? [];
assert.strictEqual(
followUpContext.some((event) => event.runId === child.runId),
true,
);
assert.strictEqual(
followUpContext.some(
(event) =>
event.content?.kind === 'text' && event.content.text.includes('private parent context'),
),
false,
);
await drain(restarted.compactSession(child.childSessionId, { turnId: 'child-compact' }));
assert.strictEqual(compactCalls.length, 1);
assert.strictEqual(compactCalls[0]?.turnId, 'child-compact');
const childMessages = await restarted.getMessages(child.childSessionId);
assert.strictEqual(
childMessages.some((message) => message.type === 'tool_call'),
true,
);
assert.strictEqual(
childMessages.some((message) => message.type === 'tool_result'),
true,
);
assert.strictEqual(
childMessages.some((message) => message.type === 'token_usage'),
true,
);
assert.strictEqual(
childMessages.some(
(message) =>
message.type === 'turn_state' &&
message.turnId === 'child-compact' &&
message.status === 'completed',
),
true,
);
const parentMessages = await restarted.getMessages(parent.id);
assert.strictEqual(
parentMessages.some(
(message) =>
message.turnId === child.turnId ||
message.turnId === 'child-follow-up' ||
message.turnId === 'child-compact',
),
false,
);
parentGate.release();
while (!(await parentTurn.next()).done) {}
});
test('recovers an idempotent retry whose persisted initial run is no longer active', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const parentGate = makeGate();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx, parentGate));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(190),
});
const parent = await manager.createSession(makeInput());
const parentTurn = manager
.sendMessage(parent.id, { turnId: 'parent-turn', text: 'keep parent active' })
[Symbol.asyncIterator]();
await parentTurn.next();
const [parentRun] = await runStore.listSessionInvocations(parent.id);
if (!parentRun) throw new Error('parent run was not recorded');
const toolCallId = 'recovery-tool-call';
const prompt = 'recover this exact request';
const requestFingerprint = createHash('sha256')
.update(
JSON.stringify([
1,
parent.id,
parentRun.runId,
parentRun.turnId,
toolCallId,
LOCAL_READ_AGENT_PROFILE,
prompt,
null,
null,
]),
)
.digest('hex');
const { header: child } = await store.createSubagent(
makeInput({
name: 'Stale child',
permissionMode: 'explore',
collaborationMode: 'agent',
orchestrationMode: 'default',
subagentParent: {
kind: 'subagent',
parentSessionId: parent.id,
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId,
},
lifecycle: 'foreground',
},
subagentRuntime: {
schemaVersion: 1,
definitionVersion: LOCAL_READ_AGENT_DEFINITION.definitionVersion,
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
profile: LOCAL_READ_AGENT_PROFILE,
systemPrompt: LOCAL_READ_AGENT_DEFINITION.systemPrompt,
toolNames: [...LOCAL_READ_AGENT_DEFINITION.tools],
categoryPolicy: {},
},
subagentSpawn: {
schemaVersion: 1,
requestFingerprint,
initialTurnId: 'stale-child-turn',
initialRunId: 'stale-child-run',
},
}),
);
await seedRunningTurn(store, child.id, 'stale-child-turn');
await seedRun(
runStore,
makeRunHeader({
sessionId: child.id,
runId: 'stale-child-run',
turnId: 'stale-child-turn',
status: 'running',
permissionMode: 'explore',
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
}),
[
makeRunEvent({
sessionId: child.id,
runId: 'stale-child-run',
turnId: 'stale-child-turn',
type: 'turn_started',
ts: 191,
}),
],
);
const recovered = await manager.spawnChildSession(parent.id, {
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId,
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
prompt,
});
assert.strictEqual(recovered.childSessionId, child.id);
assert.strictEqual(recovered.runId, 'stale-child-run');
assert.strictEqual(recovered.status, 'failed');
assert.strictEqual(recovered.failureClass, 'app_restarted');
assert.strictEqual((await manager.listChildSessions(parent.id)).length, 1);
parentGate.release();
while (!(await parentTurn.next()).done) {}
});
test('requires the exact parent run to remain active before admitting child work', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(200),
});
const parent = await manager.createSession(makeInput());
await drain(
manager.sendMessage(parent.id, { turnId: 'parent-turn', text: 'already complete' }),
);
const [parentRun] = await runStore.listSessionInvocations(parent.id);
if (!parentRun) throw new Error('parent run was not recorded');
await expectRejects(
manager.spawnChildSession(parent.id, {
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'tool-call-1',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
prompt: 'must not start',
}),
/parent run is not active/,
);
assert.deepStrictEqual(await manager.listChildSessions(parent.id), []);
});
test('admits child work through an external parent-run authority', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
let externalParent:
| {
sessionId: string;
runId: string;
turnId: string;
}
| undefined;
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
isParentRunActive: (sessionId, runId, turnId) =>
externalParent !== undefined &&
externalParent.sessionId === sessionId &&
externalParent.runId === runId &&
externalParent.turnId === turnId,
newId: nextId(),
now: nextNow(250),
});
const parent = await manager.createSession(makeInput());
await drain(manager.sendMessage(parent.id, { turnId: 'parent-turn', text: 'parent' }));
const [parentRun] = await runStore.listSessionInvocations(parent.id);
if (!parentRun) throw new Error('parent run was not recorded');
externalParent = {
sessionId: parent.id,
runId: parentRun.runId,
turnId: parentRun.turnId,
};
const child = await manager.spawnChildSession(parent.id, {
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'tool-call-1',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
prompt: 'inspect',
});
assert.strictEqual(child.status, 'completed');
assert.strictEqual(child.childSessionId === parent.id, false);
});
test('child stop is isolated while parent stop reaches every foreground child session', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const parentGate = makeGate();
const childGates = [makeGate(), makeGate()];
let childGateIndex = 0;
const backendsBySession = new Map<string, TestBackend>();
backends.register('ai-sdk', (ctx) => {
const gate = ctx.header.subagentRuntime ? childGates[childGateIndex++] : parentGate;
const backend = new TestBackend(ctx, gate);
backendsBySession.set(ctx.sessionId, backend);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(300),
});
const parent = await manager.createSession(makeInput());
const parentTurn = manager
.sendMessage(parent.id, { turnId: 'parent-turn', text: 'coordinate children' })
[Symbol.asyncIterator]();
await parentTurn.next();
const [parentRun] = await runStore.listSessionInvocations(parent.id);
if (!parentRun) throw new Error('parent run was not recorded');
const childOneStarted = makeGate();
let childOneId = '';
const childOne = manager.spawnChildSession(parent.id, {
name: 'Child one',
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'tool-call-1',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
prompt: 'first child',
onReady: ({ childSessionId }) => {
childOneId = childSessionId;
},
onEvent: (event) => {
if (event.type === 'text_delta') childOneStarted.release();
},
});
await childOneStarted.promise;
const childTwoStarted = makeGate();
let childTwoId = '';
const childTwo = manager.spawnChildSession(parent.id, {
name: 'Child two',
spawnedBy: {
parentRunId: parentRun.runId,
parentTurnId: parentRun.turnId,
toolCallId: 'tool-call-2',
},
agentProfile: LOCAL_READ_AGENT_PROFILE,
prompt: 'second child',
onReady: ({ childSessionId }) => {
childTwoId = childSessionId;
},
onEvent: (event) => {
if (event.type === 'text_delta') childTwoStarted.release();
},
});
await childTwoStarted.promise;
await manager.stopSession(childOneId, { source: 'stop_button' });
assert.strictEqual(backendsBySession.get(childOneId)?.stopCalls, 1);
assert.strictEqual(backendsBySession.get(childTwoId)?.stopCalls, 0);
assert.strictEqual(backendsBySession.get(parent.id)?.stopCalls, 0);
assert.strictEqual(
runtimeInvocationOutcome(await readInvocation(runStore, parent.id, parentRun.runId)),
undefined,
);
await manager.stopSession(parent.id, { source: 'stop_button' });
assert.strictEqual(backendsBySession.get(parent.id)?.stopCalls, 1);
assert.strictEqual(backendsBySession.get(childOneId)?.stopCalls, 1);
assert.strictEqual(backendsBySession.get(childTwoId)?.stopCalls, 1);
parentGate.release();
for (const gate of childGates) gate.release();
while (!(await parentTurn.next()).done) {}
const [childOneResult, childTwoResult] = await Promise.all([childOne, childTwo]);
assert.strictEqual(childOneResult.status, 'cancelled');
assert.strictEqual(childTwoResult.status, 'cancelled');
});
for (const { name, stopOptions, stop } of [
{
name: 'observes a rejected hosted stop while child lookup is pending',
stopOptions: (rejectStop: () => Promise<never>) => {
const authority = hostedRootAuthority();
authority.stopSession = rejectStop;
return { messageAuthority: authority };
},
stop: (manager: SessionManager) =>
manager.stopSession('session-1', { source: 'stop_button' }),
},
{
name: 'observes a rejected direct stop while hosted child lookup is pending',
stopOptions: (rejectStop: () => Promise<never>) => ({
runtimeKernel: { stopSession: rejectStop } as unknown as RuntimeKernelLike,
messageAuthority: hostedRootAuthority(),
}),
stop: (manager: SessionManager) =>
manager.deliverHostedRootStop('session-1', { source: 'stop_button' }),
},
]) {
test(name, async () => {
const store = new MemorySessionStore();
const listStarted = makeGate();
const releaseList = makeGate();
const childLookupError = new Error('child lookup failed');
store.list = async () => {
listStarted.release();
await releaseList.promise;
throw childLookupError;
};
const runStore = new MemoryAgentRunStore();
const ownStopError = new Error('own stop rejected');
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends: new BackendRegistry(),
...stopOptions(async () => {
throw ownStopError;
}),
newId: nextId(),
now: nextNow(350),
});
const unhandled: unknown[] = [];
const onUnhandledRejection = (reason: unknown) => {
if (reason === ownStopError) unhandled.push(reason);
};
process.on('unhandledRejection', onUnhandledRejection);
const stopping = stop(manager);
const stopRejection = assert.rejects(stopping, (error: unknown) => error === ownStopError);
try {
await listStarted.promise;
await new Promise<void>((resolve) => setImmediate(resolve));
assert.deepStrictEqual(unhandled, []);
} finally {
releaseList.release();
try {
await stopRejection;
} finally {
process.off('unhandledRejection', onUnhandledRejection);
}
}
});
}
test('startup recovery repairs an interrupted child inline run only in the child session', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(400),
});
const parent = await manager.createSession(makeInput());
await store.appendMessage(parent.id, {
type: 'system_note',
id: 'parent-marker',
ts: 1,
kind: 'session_start',
data: { marker: 'parent stays untouched' },
});
const child = await manager.createSession(
makeInput({
name: 'Interrupted child',
status: 'running',
permissionMode: 'explore',
subagentParent: {
kind: 'subagent',
parentSessionId: parent.id,
spawnedBy: {
parentRunId: 'parent-run',
parentTurnId: 'parent-turn',
toolCallId: 'tool-call',
},
lifecycle: 'foreground',
},
subagentRuntime: {
schemaVersion: 1,
definitionVersion: 1,
agentId: LOCAL_READ_AGENT_ID,
agentName: 'Local Read',
profile: LOCAL_READ_AGENT_PROFILE,
systemPrompt: LOCAL_READ_AGENT_DEFINITION.systemPrompt,
toolNames: ['Read', 'Glob', 'Grep'],
categoryPolicy: { read: 'allow' },
},
subagentSpawn: {
schemaVersion: 1,
requestFingerprint: 'a'.repeat(64),
initialTurnId: 'child-turn',
initialRunId: 'child-run',
},
}),
);
await seedRunningTurn(store, child.id, 'child-turn');
await seedRun(
runStore,
makeRunHeader({
sessionId: child.id,
runId: 'child-run',
turnId: 'child-turn',
status: 'running',
permissionMode: 'explore',
agentId: LOCAL_READ_AGENT_ID,
agentName: 'Local Read',
}),
[
makeRunEvent({
sessionId: child.id,
runId: 'child-run',
turnId: 'child-turn',
type: 'turn_started',
ts: 11,
}),
makeRunEvent({
sessionId: child.id,
runId: 'child-run',
turnId: 'child-turn',
type: 'model_stream_started',
ts: 12,
}),
],
);
const parentMessagesBefore = await store.readMessages(parent.id);
const recovered = await manager.recoverInterruptedSessions();
assert.deepStrictEqual(recovered, [child.id]);
const recoveredRun = await readInvocation(runStore, child.id, 'child-run');
assert.strictEqual(recoveredRun.opening.lineage?.parentRunId, undefined);
assert.strictEqual(isSessionInlineInvocation(recoveredRun.opening), true);
assert.strictEqual(runtimeInvocationOutcome(recoveredRun), 'failed');
assert.strictEqual(runtimeInvocationFailureClass(recoveredRun), 'app_restarted');
assert.strictEqual(
(await manager.getMessages(child.id)).some(
(message) =>
message.type === 'turn_state' &&
message.turnId === 'child-turn' &&
message.status === 'failed',
),
true,
);
assert.deepStrictEqual(await store.readMessages(parent.id), parentMessagesBefore);
});
});
describe('SessionManager manual compaction and quiescent session changes', () => {
test('runs backend history compaction as a runtime turn and persists diagnostics', async () => {
const store = new MemorySessionStore();
const runStore = new OrderingAgentRunStore();
const backends = new BackendRegistry();
const compactCalls: Array<{ turnId: string; runtimeContextCount: number }> = [];
backends.register('ai-sdk', (ctx) => new CompactingTestBackend(ctx, compactCalls));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(10_000),
});
const session = await manager.createSession(
makeInput({ permissionMode: 'bypass', llmConnectionId: 'connection-compact' }),
);
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }));
const sourceRun = (await runStore.listSessionInvocations(session.id)).find(
(run) => run.turnId === 'turn-1',
);
assert.ok(sourceRun);
const sourceRoute = sourceRun.opening.route;
assert.equal(sourceRoute.provenance, 'runtime');
runStore.operations = [];
const events = await collectSessionEvents(
manager.compactSession(session.id, { turnId: 'turn-compact' }),
);
assert.deepStrictEqual(compactCalls, [
{
turnId: 'turn-compact',
// Opening fact, prompt, answer, terminal.
runtimeContextCount: 4,
sourceRoutes: [
{
runId: sourceRun.runId,
connectionId:
sourceRoute.provenance === 'runtime' && sourceRoute.backendKind !== 'plugin-executor'
? sourceRoute.llmConnectionId
: undefined,
modelId: sourceRoute.modelId,
},
],
},
]);
assert.deepStrictEqual(
events.map((event) => event.type),
['token_usage', 'complete'],
);
const usage = events[0];
if (usage?.type !== 'token_usage') throw new Error('expected token_usage');
assert.strictEqual(usage.contextBudget?.compactionDecisions?.[0]?.decision, 'replaced');
const complete = events[1];
if (complete?.type !== 'complete') throw new Error('expected complete');
assert.strictEqual(complete.contextCompactionOutcome?.kind, 'compacted');
const messages = await manager.getMessages(session.id);
assert.strictEqual(
messages.some((message) => message.type === 'user' && message.text.includes('compact')),
false,
);
assert.strictEqual(
messages.some(
(message) => message.type === 'token_usage' && message.turnId === 'turn-compact',
),
true,
);
assert.strictEqual(
messages.some(
(message) =>
message.type === 'turn_state' &&
message.turnId === 'turn-compact' &&
message.status === 'completed',
),
true,
);
const compactRun = (await runStore.listSessionInvocations(session.id)).find(
(run) => run.turnId === 'turn-compact',
);
assert.strictEqual(compactRun && runtimeInvocationOutcome(compactRun), 'completed');
assert.deepStrictEqual(runStore.operations, ['terminalRuntimeEvent']);
assert.strictEqual(
(await runStore.readRuntimeEvents(session.id, compactRun!.runId)).some(
(event) => event.actions?.stateDelta?.contextCompactionOutcome,
),
true,
);
await manager.stopSession(session.id, { source: 'stop_button' });
});
test('manual compaction settles one canonical record for the run the kernel opened', async () => {
// `sessions:compact` and CLI `/compact` both land here. The call has no
// send to inherit a run from, so the kernel states the run it opened and
// the record must carry it — otherwise a real, billed summarization is
// silently unmetered (#1679).
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const modelCalls: ModelCallAttempt[] = [];
const summarizerModel = new MockLanguageModelV4({
doGenerate: {
content: [
{
type: 'text',
// Structured so it passes the summarizer's checkpoint validation
// (#3029) while keeping the sentinel greppable.
text: '## Goal\nMANUAL_COMPACT_SUMMARY\n\n## Progress\n- done\n\n## Next Steps\n1. continue\n\n## Critical Context\n- (none)',
},
],
finishReason: { unified: 'stop', raw: 'stop' },
usage: {
inputTokens: { total: 41, noCache: 41, cacheRead: 0, cacheWrite: 0 },
outputTokens: { total: 9, text: 9, reasoning: 0 },
raw: { input_tokens: 41, output_tokens: 9 },
},
warnings: [],
},
});
backends.register('ai-sdk', (ctx) =>
createTestAiSdkBackend({
sessionId: ctx.sessionId,
header: ctx.header,
connection: {
slug: 'mock-main',
providerType: 'anthropic',
defaultModel: 'mock-model-id',
},
apiKey: 'sk-test',
modelId: 'mock-model-id',
modelFactory: () =>
new MockLanguageModelV4({
doStream: async () => ({
stream: simulateReadableStream({
chunks: [
{ type: 'stream-start', warnings: [] },
{ type: 'text-start', id: 'text-1' },
{ type: 'text-delta', id: 'text-1', delta: 'ok '.repeat(80) },
{ type: 'text-end', id: 'text-1' },
{
type: 'finish',
finishReason: { unified: 'stop', raw: 'stop' },
usage: {
inputTokens: { total: 100, noCache: 100, cacheRead: 0, cacheWrite: 0 },
outputTokens: { total: 10, text: 10, reasoning: 0 },
},
},
] as LanguageModelV4StreamPart[],
initialDelayInMs: null,
chunkDelayInMs: null,
}),
}),
}),
tools: [],
newId: nextId(),
now: nextNow(1),
contextBudget: {
name: 'manual-compact-accounting',
charsPerToken: 1,
},
summarizeHistoryCompact: buildLlmHistorySummarizer({
resolveModel: () => summarizerModel,
}),
recordHistoryCompactCheckpoint: () => {},
recordModelCallAttempt: ({ attempt }) => {
modelCalls.push(attempt);
},
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(13_000),
});
const session = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'first '.repeat(400) }));
await drain(manager.sendMessage(session.id, { turnId: 'turn-2', text: 'second' }));
await drain(manager.compactSession(session.id, { turnId: 'turn-compact' }));
const compactRun = (await runStore.listSessionInvocations(session.id)).find(
(run) => run.turnId === 'turn-compact',
);
assert.ok(compactRun, 'the kernel opens a run for a manual compaction');
const compactions = modelCalls
.map((attempt) => decodeModelCallAttempt(attempt))
.filter((attempt) => attempt.callKind === 'history_compact');
assert.equal(compactions.length, 1, 'one manual compaction is one record');
assert.equal(
compactions[0]?.runId,
compactRun.runId,
'attributed to the run the kernel opened, not to whatever ran last',
);
assert.equal(compactions[0]?.turnId, 'turn-compact');
assert.equal(compactions[0]?.inputTokens, 41);
assert.equal(compactions[0]?.usageBasis, 'reported');
await manager.stopSession(session.id, { source: 'stop_button' });
});
test('persists one visible warning when manual compaction fails open', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new FailOpenCompactingBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_000),
});
const session = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }));
await drain(manager.compactSession(session.id, { turnId: 'turn-compact' }));
const warnings = (await manager.getMessages(session.id)).filter(
(message) =>
message.type === 'system_note' &&
message.turnId === 'turn-compact' &&
message.kind === 'context_compaction_failed_open',
);
assert.strictEqual(warnings.length, 1);
});
test('persists exactly one context_compacted note when manual compaction succeeds', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const compactCalls: Array<{ turnId: string; runtimeContextCount: number }> = [];
backends.register('ai-sdk', (ctx) => new CompactingTestBackend(ctx, compactCalls));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_000),
});
const session = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }));
await drain(manager.compactSession(session.id, { turnId: 'turn-compact' }));
const messages = await manager.getMessages(session.id);
const notes = messages.filter(
(message) =>
message.type === 'system_note' &&
message.turnId === 'turn-compact' &&
message.kind === 'context_compacted',
);
// The kernel writes the note on the compaction turn itself, so the row
// appears the moment compaction ends — not one send later.
assert.strictEqual(notes.length, 1);
// And no duplicate failed-open note on a successful compaction.
const failed = messages.filter(
(message) =>
message.type === 'system_note' && message.kind === 'context_compaction_failed_open',
);
assert.strictEqual(failed.length, 0);
});
test('persists a fail-open note when the backend throws during manual compaction', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new ThrowingCompactingBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_000),
});
const session = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }));
let threw = false;
try {
await drain(manager.compactSession(session.id, { turnId: 'turn-compact' }));
} catch {
threw = true;
}
assert.ok(threw, 'a backend that throws surfaces the failure to the caller');
// A thrown compaction still leaves one durable fail-open row: the internal
// compaction turn has no assistant content, so recordFailure alone would
// render an empty turn.
const notes = (await manager.getMessages(session.id)).filter(
(message) =>
message.type === 'system_note' &&
message.turnId === 'turn-compact' &&
message.kind === 'context_compaction_failed_open',
);
assert.strictEqual(notes.length, 1);
});
test('manual compaction stopped before backend start does not write compact artifacts', async () => {
const store = new MemorySessionStore();
const readGate = makeGate();
const readStarted = makeGate();
let blockPriorRead = false;
const runStore = new MemoryAgentRunStore({
beforeRuntimeEventRead: async () => {
if (!blockPriorRead) return;
readStarted.release();
await readGate.promise;
},
});
const backends = new BackendRegistry();
const compactCalls: Array<{ turnId: string; runtimeContextCount: number }> = [];
backends.register('ai-sdk', (ctx) => new CompactingTestBackend(ctx, compactCalls));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(15_000),
});
const session = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }));
blockPriorRead = true;
const compactPromise = collectSessionEvents(
manager.compactSession(session.id, { turnId: 'turn-compact' }),
);
await readStarted.promise;
let stopSettled = false;
const stop = manager.stopSession(session.id, { source: 'stop_button' }).finally(() => {
stopSettled = true;
});
await Promise.resolve();
await Promise.resolve();
assert.strictEqual(stopSettled, false);
assert.deepStrictEqual(compactCalls, []);
readGate.release();
await stop;
const compactEvents = await compactPromise.catch(() => []);
assert.deepStrictEqual(compactCalls, []);
assert.strictEqual(
compactEvents.some((event) => event.type === 'token_usage'),
false,
);
const compactRun = (await runStore.listSessionInvocations(session.id)).find(
(run) => run.turnId === 'turn-compact',
);
assert.strictEqual(compactRun && runtimeInvocationOutcome(compactRun), 'cancelled');
// An interrupted compaction leaves no durable transcript row.
assert.deepStrictEqual(
(await store.readMessages(session.id)).filter(
(message) =>
message.type === 'system_note' &&
(message.kind === 'context_compacted' ||
message.kind === 'context_compaction_failed_open'),
),
[],
);
});
test('cold manual compaction normalizes only its execution cancellation reason', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const factoryStarts = new Map<string, ReturnType<typeof makeGate>>();
const factoryModes = new Map<string, 'execution_cancellation' | 'abort_error'>();
backends.register('ai-sdk', async (ctx) => {
const started = factoryStarts.get(ctx.sessionId);
const mode = factoryModes.get(ctx.sessionId);
if (!started || !mode) throw new Error('cold compact factory was not configured');
const signal = ctx.abortSignal;
if (!signal) throw new Error('cold compact factory did not receive an abort signal');
started.release();
if (!signal.aborted) {
await new Promise<void>((resolve) => {
signal.addEventListener('abort', () => resolve(), { once: true });
});
}
if (mode === 'abort_error') {
const error = new Error('cold compact factory timed out');
error.name = 'AbortError';
throw error;
}
throw signal.reason;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(15_500),
});
const cancelledSession = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
const abortErrorSession = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
const cancelledStart = makeGate();
const abortErrorStart = makeGate();
factoryStarts.set(cancelledSession.id, cancelledStart);
factoryStarts.set(abortErrorSession.id, abortErrorStart);
factoryModes.set(cancelledSession.id, 'execution_cancellation');
factoryModes.set(abortErrorSession.id, 'abort_error');
const cancelledCompact = collectSessionEvents(
manager.compactSession(cancelledSession.id, { turnId: 'compact-cancelled-factory' }),
);
await cancelledStart.promise;
const cancelledStop = manager.stopSession(cancelledSession.id, {
source: 'stop_button',
});
const [cancelledEvents] = await Promise.all([cancelledCompact, cancelledStop]);
assert.deepStrictEqual(cancelledEvents, []);
const abortErrorCompact = collectSessionEvents(
manager.compactSession(abortErrorSession.id, { turnId: 'compact-abort-error-factory' }),
);
await abortErrorStart.promise;
const abortErrorRejection = assert.rejects(abortErrorCompact, /cold compact factory timed out/);
const abortErrorStop = manager.stopSession(abortErrorSession.id, {
source: 'stop_button',
});
await Promise.all([abortErrorRejection, abortErrorStop]);
const [cancelledRun] = await runStore.listSessionInvocations(cancelledSession.id);
const [abortErrorRun] = await runStore.listSessionInvocations(abortErrorSession.id);
assert.strictEqual(cancelledRun && runtimeInvocationOutcome(cancelledRun), 'cancelled');
assert.strictEqual(abortErrorRun && runtimeInvocationOutcome(abortErrorRun), 'cancelled');
});
test('stopSession waits for compaction blocked before Run reservation', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const compactCalls: Array<{ turnId: string; runtimeContextCount: number }> = [];
backends.register('ai-sdk', (ctx) => new CompactingTestBackend(ctx, compactCalls));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(17_000),
});
const session = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
const readStarted = makeGate();
const releaseRead = makeGate();
store.nextReadHeaderGate = { started: readStarted, release: releaseRead };
const compact = collectSessionEvents(
manager.compactSession(session.id, { turnId: 'turn-compact-pending' }),
);
await readStarted.promise;
let stopSettled = false;
const stop = manager.stopSession(session.id, { source: 'stop_button' }).finally(() => {
stopSettled = true;
});
await Promise.resolve();
await Promise.resolve();
assert.strictEqual(stopSettled, false);
assert.deepStrictEqual(compactCalls, []);
releaseRead.release();
await stop;
await compact.catch(() => []);
assert.deepStrictEqual(compactCalls, []);
const compactRun = (await runStore.listSessionInvocations(session.id)).find(
(run) => run.turnId === 'turn-compact-pending',
);
assert.strictEqual(compactRun && runtimeInvocationOutcome(compactRun), 'cancelled');
});
test('manual compaction is stopped through the active runtime run lifecycle', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const compactGate = makeGate();
const compactStarted = makeGate();
let compactingBackend: BlockingCompactBackend | undefined;
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) =>
new BlockingCompactBackend(ctx, {
compactGate,
onCompactStart: (backend) => {
compactingBackend = backend;
compactStarted.release();
},
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(20_000),
});
const session = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }));
const compactPromise = collectSessionEvents(
manager.compactSession(session.id, { turnId: 'turn-compact' }),
);
await compactStarted.promise;
await manager.stopSession(session.id, { source: 'stop_button' });
compactGate.release();
const compactEvents = await compactPromise.catch(() => []);
assert.strictEqual(compactingBackend?.stopCalls, 1);
assert.strictEqual(
compactEvents.some((event) => event.type === 'token_usage'),
false,
);
const compactRun = (await runStore.listSessionInvocations(session.id)).find(
(run) => run.turnId === 'turn-compact',
);
assert.strictEqual(compactRun && runtimeInvocationOutcome(compactRun), 'cancelled');
// A compaction stopped mid-run leaves no durable transcript row: the note is
// written only after the terminal completeEvent commits, and the catch path
// skips it while the run is stopped.
assert.deepStrictEqual(
(await store.readMessages(session.id)).filter(
(message) =>
message.type === 'system_note' &&
(message.kind === 'context_compacted' ||
message.kind === 'context_compaction_failed_open'),
),
[],
);
});
test('compactSession rejects while a turn is running and writes no compact artifacts', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const sendGate = makeGate();
const turnStarted = makeGate();
const compactCalls: Array<{ turnId: string; runtimeContextCount: number }> = [];
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) => new ActiveTurnBackend(ctx, { turnStarted, sendGate, compactCalls }),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(25_000),
});
const session = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
const sendPromise = (async () => {
for await (const _event of manager.sendMessage(session.id, {
turnId: 'turn-1',
text: 'hi',
})) {
// turn held open at the send gate; drained after the compact assertion
}
})();
await turnStarted.promise;
const compactError = await collectSessionEvents(
manager.compactSession(session.id, { turnId: 'turn-compact' }),
).catch((error: unknown) => error);
assert.strictEqual(compactError instanceof RuntimeContextCompactError, true);
assert.strictEqual((compactError as RuntimeContextCompactError).code, 'session_busy');
assert.deepStrictEqual(compactCalls, []);
const messages = await store.readMessages(session.id);
assert.strictEqual(
messages.some((message) => message.turnId === 'turn-compact'),
false,
);
const compactRun = (await runStore.listSessionInvocations(session.id)).find(
(run) => run.turnId === 'turn-compact',
);
assert.strictEqual(compactRun, undefined);
sendGate.release();
await sendPromise;
});
test('configuration transitions fence active runs and unavailable resource side effects', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const kernel = new DelegatingRuntimeKernel();
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(26_400),
runtimeKernel: kernel,
});
const session = await manager.createSession(
makeInput({ permissionMode: 'bypass', orchestrationMode: 'default' }),
);
const baseConfiguration = {
backend: session.backend,
llmConnectionId: 'test-connection-id',
llmConnectionSlug: session.llmConnectionSlug,
connectionLocked: true,
model: session.model,
thinkingLevel: session.thinkingLevel,
permissionMode: session.permissionMode,
collaborationMode: session.collaborationMode ?? 'agent',
orchestrationMode: 'graph' as const,
};
kernel.activeRuns = true;
await assert.rejects(
manager.transitionSessionConfiguration(session.id, {
expectedRevision: 1,
clearConnectionBlock: false,
permissionModeOnly: false,
configuration: baseConfiguration,
}),
(error: unknown) => {
assert.ok(error instanceof SessionConfigurationTransitionError);
assert.equal(error.code, 'session_busy');
return true;
},
);
assert.deepEqual(kernel.disposed, []);
kernel.activeRuns = false;
const committed = await manager.transitionSessionConfiguration(session.id, {
expectedRevision: 1,
clearConnectionBlock: false,
permissionModeOnly: false,
configuration: baseConfiguration,
});
assert.equal(committed.revision, 2);
assert.equal(committed.header.orchestrationMode, 'graph');
assert.deepEqual(kernel.disposed, [session.id]);
await assert.rejects(
manager.transitionSessionConfiguration(session.id, {
expectedRevision: 1,
clearConnectionBlock: false,
permissionModeOnly: false,
configuration: baseConfiguration,
}),
(error: unknown) => {
assert.ok(error instanceof SessionConfigurationRevisionConflictError);
assert.equal(error.expectedRevision, 1);
assert.equal(error.actualRevision, 2);
return true;
},
);
await assert.rejects(
manager.transitionSessionConfiguration(session.id, {
expectedRevision: 2,
clearConnectionBlock: false,
permissionModeOnly: true,
configuration: {
...baseConfiguration,
permissionMode: 'explore',
},
}),
(error: unknown) => {
assert.ok(error instanceof SessionConfigurationTransitionError);
assert.equal(error.code, 'operation_unavailable');
return true;
},
);
assert.deepEqual(kernel.disposed, [session.id]);
});
test('configuration transitions clear a connection block only on explicit Host authority', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(26_425),
});
const session = await manager.createSession(makeInput());
await store.updateHeader(session.id, {
status: 'blocked',
blockedReason: 'NO_REAL_CONNECTION',
});
const configuration = {
backend: session.backend,
llmConnectionId: 'test-connection-id',
llmConnectionSlug: session.llmConnectionSlug,
connectionLocked: true,
model: session.model,
thinkingLevel: session.thinkingLevel,
permissionMode: session.permissionMode,
collaborationMode: session.collaborationMode ?? 'agent',
orchestrationMode: session.orchestrationMode ?? 'default',
};
const preserved = await manager.transitionSessionConfiguration(session.id, {
expectedRevision: 1,
clearConnectionBlock: false,
permissionModeOnly: false,
configuration,
});
assert.equal(preserved.header.blockedReason, 'NO_REAL_CONNECTION');
assert.equal(preserved.header.status, 'blocked');
const recovered = await manager.transitionSessionConfiguration(session.id, {
expectedRevision: 2,
clearConnectionBlock: true,
permissionModeOnly: false,
configuration,
});
assert.equal(recovered.header.blockedReason, undefined);
assert.equal(recovered.header.status, 'active');
});
test('workspace relocation uses the same quiescent revision fence as execution configuration', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const kernel = new DelegatingRuntimeKernel();
const resourceCalls: string[] = [];
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(26_425),
runtimeKernel: kernel,
shellRuns: {
async terminateSession(sessionId: string) {
resourceCalls.push(`terminate:${sessionId}`);
return { sessionId, token: Symbol('relocate') };
},
async commitSessionClose() {
resourceCalls.push('commit');
},
rollbackSessionClose() {
resourceCalls.push('rollback');
},
resumeSession(sessionId: string) {
resourceCalls.push(`resume:${sessionId}`);
},
} as never,
});
const session = await manager.createSession(makeInput({ cwd: '/workspace/old' }));
kernel.activeRuns = true;
await assert.rejects(
manager.relocateSessionWorkspace(session.id, {
expectedRevision: 1,
cwd: '/workspace/new',
}),
(error: unknown) => {
assert.ok(error instanceof SessionConfigurationTransitionError);
assert.equal(error.code, 'session_busy');
return true;
},
);
kernel.activeRuns = false;
const committed = await manager.relocateSessionWorkspace(session.id, {
expectedRevision: 1,
cwd: '/workspace/new',
projectId: 'project-2',
});
assert.equal(committed.revision, 2);
assert.equal(committed.header.cwd, '/workspace/new');
assert.equal(committed.header.projectId, 'project-2');
assert.deepEqual(kernel.disposed, [session.id]);
assert.deepEqual(resourceCalls, [`terminate:${session.id}`, 'commit', `resume:${session.id}`]);
await assert.rejects(
manager.relocateSessionWorkspace(session.id, {
expectedRevision: 1,
cwd: '/workspace/stale',
}),
(error: unknown) => {
assert.ok(error instanceof SessionConfigurationRevisionConflictError);
assert.equal(error.actualRevision, 2);
return true;
},
);
assert.equal((await store.readHeader(session.id)).cwd, '/workspace/new');
});
test('configuration transitions reject a claimed turn without waiting for it to settle', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const readStarted = makeGate();
const releaseRead = makeGate();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const manager = new SessionManager({
store,
backends,
newId: nextId(),
now: nextNow(26_450),
});
const session = await manager.createSession(makeInput({ orchestrationMode: 'default' }));
store.nextReadHeaderGate = { started: readStarted, release: releaseRead };
const turn = drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'start' }));
await readStarted.promise;
const transitionResult = await manager
.transitionSessionConfiguration(session.id, {
expectedRevision: 1,
clearConnectionBlock: false,
permissionModeOnly: false,
configuration: {
backend: session.backend,
llmConnectionId: 'test-connection-id',
llmConnectionSlug: session.llmConnectionSlug,
connectionLocked: true,
model: session.model,
thinkingLevel: session.thinkingLevel,
permissionMode: session.permissionMode,
collaborationMode: session.collaborationMode ?? 'agent',
orchestrationMode: 'graph',
},
})
.then(
(value) => ({ ok: true as const, value }),
(error: unknown) => ({ ok: false as const, error }),
);
assert.equal(transitionResult.ok, false);
if (transitionResult.ok) assert.fail('Configuration transition unexpectedly committed');
assert.ok(transitionResult.error instanceof SessionConfigurationTransitionError);
assert.equal(transitionResult.error.code, 'session_busy');
assert.equal((await store.readHeader(session.id)).orchestrationMode, 'default');
releaseRead.release();
await turn;
});
test('a claimed turn waits for an in-flight session mutation before reading its header', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const runStore = new MemoryAgentRunStore();
const updateStarted = makeGate();
const releaseUpdate = makeGate();
const activatedModels: string[] = [];
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => {
activatedModels.push(ctx.header.model);
return new TestBackend(ctx);
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(26_475),
});
const session = await manager.createSession(makeInput({ model: 'old-model' }));
store.nextConfigurationUpdateGate = {
started: updateStarted,
release: releaseUpdate,
};
const transition = manager.transitionSessionConfiguration(session.id, {
expectedRevision: 1,
clearConnectionBlock: false,
permissionModeOnly: false,
configuration: {
backend: session.backend,
llmConnectionId: 'test-connection-id',
llmConnectionSlug: session.llmConnectionSlug,
connectionLocked: true,
model: 'new-model',
thinkingLevel: session.thinkingLevel,
permissionMode: session.permissionMode,
collaborationMode: session.collaborationMode ?? 'agent',
orchestrationMode: session.orchestrationMode ?? 'default',
},
});
await updateStarted.promise;
const turn = drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'start' }));
await new Promise<void>((resolve) => setImmediate(resolve));
assert.deepEqual(activatedModels, []);
releaseUpdate.release();
await transition;
await turn;
assert.deepEqual(activatedModels, ['new-model']);
});
test('backend refresh propagates delayed disposal failure after an active turn settles', async () => {
const store = new MemorySessionStore();
const sendGate = makeGate();
const turnStarted = makeGate();
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) =>
new (class extends ActiveTurnBackend {
override async dispose(): Promise<void> {
throw new Error('injected delayed disposal failure');
}
})(ctx, { turnStarted, sendGate, compactCalls: [] }),
);
const manager = new SessionManager({
store,
backends,
newId: nextId(),
now: nextNow(26_500),
});
const session = await manager.createSession(makeInput());
const firstTurn = drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hi' }));
await turnStarted.promise;
const refreshResult = manager.refreshIdleBackends().then(
() => undefined,
(error: unknown) => error,
);
sendGate.release();
await firstTurn;
const refreshError = await refreshResult;
assert.strictEqual(refreshError instanceof Error, true);
assert.strictEqual((refreshError as Error).message, 'injected delayed disposal failure');
});
test('backend refresh joins an in-flight best-effort disposal and observes its failure', async () => {
const store = new MemorySessionStore();
const disposeStarted = makeGate();
const releaseDispose = makeGate();
let disposeCalls = 0;
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) =>
new (class extends TestBackend {
override async dispose(): Promise<void> {
disposeCalls += 1;
disposeStarted.release();
await releaseDispose.promise;
throw new Error('injected concurrent disposal failure');
}
})(ctx),
);
const newId = nextId();
const now = nextNow(26_750);
const runtimeKernel = new RuntimeKernel({ store, backends, newId, now });
const manager = new SessionManager({
store,
backends,
runtimeKernel,
newId,
now,
});
const session = await manager.createSession(makeInput());
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'cache me' }));
const bestEffort = runtimeKernel.invalidateBackend(session.id);
await disposeStarted.promise;
let strictSettled = false;
const strictResult = manager.refreshIdleBackends().then(
() => ({ ok: true as const }),
(error: unknown) => ({ ok: false as const, error }),
);
void strictResult.finally(() => {
strictSettled = true;
});
await Promise.resolve();
assert.strictEqual(strictSettled, false);
releaseDispose.release();
await bestEffort;
const strict = await strictResult;
assert.strictEqual(strict.ok, false);
if (strict.ok) throw new Error('Strict refresh unexpectedly succeeded');
assert.strictEqual(strict.error instanceof Error, true);
assert.strictEqual((strict.error as Error).message, 'injected concurrent disposal failure');
assert.strictEqual(disposeCalls, 1);
});
test('backend activation waits for disposal and a later mutation invalidates its replacement', async () => {
const store = new MemorySessionStore();
const firstDisposeStarted = makeGate();
const releaseFirstDispose = makeGate();
const replacementPreBuildReached = makeGate();
const releaseReplacementPreBuild = makeGate();
let replacementCheckpointArmed = true;
const runStore = new MemoryAgentRunStore({
beforeRuntimeEventAppend: async (_sessionId, _runId, event) => {
if (!replacementCheckpointArmed || event.turnId !== 'turn-b') return;
replacementCheckpointArmed = false;
replacementPreBuildReached.release();
await releaseReplacementPreBuild.promise;
},
});
const { runBackendActivation, runMutation } = makeTestRuntimePolicyGate();
let builds = 0;
const disposed: number[] = [];
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => {
const generation = ++builds;
return new (class extends TestBackend {
override async dispose(): Promise<void> {
disposed.push(generation);
if (generation === 1) {
firstDisposeStarted.release();
await releaseFirstDispose.promise;
}
}
})(ctx);
});
const newId = nextId();
const now = nextNow(26_875);
const runtimeKernel = new RuntimeKernel({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId,
now,
runBackendActivation,
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
runtimeKernel,
newId,
now,
});
const session = await manager.createSession(makeInput());
await drain(manager.sendMessage(session.id, { turnId: 'turn-a', text: 'build A' }));
const bestEffort = runtimeKernel.invalidateBackend(session.id);
await firstDisposeStarted.promise;
const replacementTurn = drain(
manager.sendMessage(session.id, { turnId: 'turn-b', text: 'build B' }),
);
await replacementPreBuildReached.promise;
releaseReplacementPreBuild.release();
await new Promise<void>((resolve) => setImmediate(resolve));
assert.strictEqual(builds, 1);
const mutation = runMutation(() => manager.refreshIdleBackends());
releaseFirstDispose.release();
await bestEffort;
await replacementTurn;
await mutation;
assert.strictEqual(builds, 2);
assert.deepStrictEqual(disposed, [1, 2]);
await drain(manager.sendMessage(session.id, { turnId: 'turn-c', text: 'build C' }));
assert.strictEqual(builds, 3);
});
test('backend refresh invalidates only cached sessions without listing persisted history', async () => {
const store = new MemorySessionStore();
const disposed: string[] = [];
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) =>
new (class extends TestBackend {
override async dispose(): Promise<void> {
disposed.push(this.sessionId);
}
})(ctx),
);
const manager = new SessionManager({
store,
backends,
newId: nextId(),
now: nextNow(27_000),
});
const cached = await manager.createSession(makeInput());
await manager.createSession(makeInput());
await drain(manager.sendMessage(cached.id, { turnId: 'turn-1', text: 'cache me' }));
store.list = async () => {
throw new Error('refresh must not list persisted sessions');
};
await manager.refreshIdleBackends();
assert.deepStrictEqual(disposed, [cached.id]);
});
});
describe('SessionManager permission mode updates', () => {
for (const route of ['direct', 'legacy'] as const) {
test(`serializes concurrent ${route} boundary commits before they can become narrowing`, {
timeout: 10_000,
}, async (t) => {
const root = await mkdtemp(join(tmpdir(), 'maka-boundary-commit-race-'));
const store = createSessionStore(root);
// Hide optional capabilities from Runtime, without changing SQLite's own
// internal method calls, to exercise the legacy SessionStore contract.
const runtimeStore =
route === 'legacy'
? new Proxy(store, {
get(target, key) {
if (key === 'readHeaderRecordSnapshot' || key === 'updateSessionConfiguration')
return undefined;
const value = Reflect.get(target, key, target);
return typeof value === 'function' ? value.bind(target) : value;
},
})
: store;
const gate = makeGate();
t.after(async () => {
gate.release();
await store.close?.();
await rm(root, { recursive: true, force: true });
});
const calls: string[] = [];
const backends = new BackendRegistry();
let backend: TestBackend | undefined;
backends.register('ai-sdk', (ctx) => (backend = new TestBackend(ctx, gate)));
const manager = new SessionManager({
store: runtimeStore,
backends,
newId: nextId(),
now: nextNow(979),
shellRuns: {
async terminateSession(sessionId: string) {
calls.push(`terminate:${sessionId}`);
return { sessionId, token: Symbol('test') };
},
async commitSessionClose() {
calls.push('commit');
},
rollbackSessionClose() {
calls.push('rollback');
},
resumeSession(sessionId: string) {
calls.push(`resume:${sessionId}`);
},
} as never,
});
const session = await manager.createSession(makeInput({ permissionMode: 'explore' }));
const update = (bypass: boolean) =>
route === 'direct'
? manager.setExecutionBoundaryKind(session.id, bypass ? 'bypass' : 'managed')
: manager.setPermissionMode(session.id, bypass ? 'bypass' : 'ask');
const turn = manager
.sendMessage(session.id, { turnId: 'turn-racing', text: 'keep running' })
[Symbol.asyncIterator]();
try {
await turn.next();
// Both requests initially observe Explore. The second must not reuse
// that classification after the first has committed Bypass.
const results = await Promise.allSettled([update(true), update(false)]);
assert.deepStrictEqual(
results.map((result) => result.status),
['fulfilled', 'rejected'],
);
const conflict = results[1];
assert.ok(conflict?.status === 'rejected');
assert.ok(conflict.reason instanceof SessionConfigurationTransitionError);
assert.strictEqual(conflict.reason.code, 'operation_conflict');
assert.deepStrictEqual(await store.readExecutionBoundary(session.id), {
kind: 'bypass',
revision: 1,
});
assert.strictEqual((await store.readHeader(session.id)).permissionMode, 'bypass');
assert.deepStrictEqual(manager.runningTurnIds(session.id), ['turn-racing']);
assert.strictEqual(backend?.stopCalls, 0);
assert.deepStrictEqual(calls, []);
// A fresh retry is now correctly classified as narrowing.
await assert.rejects(update(false), (error: unknown) => {
assert.ok(error instanceof SessionConfigurationTransitionError);
assert.strictEqual(error.code, 'session_busy');
return true;
});
} finally {
gate.release();
while (!(await turn.next()).done) {}
}
// The conflict released the mutation lane; idle narrowing still revokes shells.
await update(false);
assert.strictEqual((await store.readHeader(session.id)).permissionMode, 'ask');
assert.deepStrictEqual(calls, [`terminate:${session.id}`, 'commit', `resume:${session.id}`]);
});
}
test('rejects unprotected boundary commits when admission mutation authority is unavailable', async () => {
const store = new MemorySessionStore();
const kernel = new DelegatingRuntimeKernel();
Object.defineProperty(kernel, 'runSessionAdmissionMutation', { value: undefined });
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
runtimeKernel: kernel,
newId: nextId(),
now: nextNow(979),
});
const session = await manager.createSession(makeInput({ permissionMode: 'explore' }));
await assert.rejects(
manager.setExecutionBoundaryKind(session.id, 'bypass'),
(error: unknown) => {
assert.ok(error instanceof SessionConfigurationTransitionError);
assert.strictEqual(error.code, 'operation_unavailable');
return true;
},
);
assert.strictEqual((await store.readHeader(session.id)).permissionMode, 'explore');
assert.strictEqual((await store.readExecutionBoundary(session.id)).kind, 'managed');
assert.deepStrictEqual(kernel.disposed, []);
});
for (const checkpoint of [
'header',
'safety',
'admission',
'activation_gate',
'prepare',
'build',
] as const) {
test(`retains a permission refresh during backend ${checkpoint} until the admitted turn exits`, {
timeout: 10_000,
}, async (t) => {
const store = new VersionedConfigurationMemorySessionStore();
const activationStarted = makeGate();
const releaseActivation = makeGate();
const sendGate = makeGate();
t.after(() => {
releaseActivation.release();
sendGate.release();
});
const pause = async () => {
activationStarted.release();
await releaseActivation.promise;
};
const builds: PermissionMode[] = [];
const dispatched: Array<{ tools: string[]; prompt: string }> = [];
const instances: TestBackend[] = [];
const backends = new BackendRegistry();
backends.register('ai-sdk', {
async prepare() {
if (checkpoint === 'prepare' && builds.length === 0) {
activationStarted.release();
await releaseActivation.promise;
}
return {
async build(ctx) {
builds.push(ctx.header.permissionMode);
if (checkpoint === 'build' && builds.length === 1) {
activationStarted.release();
await releaseActivation.promise;
}
const fullAccess = ctx.header.permissionMode === 'bypass';
const composition = {
tools: selectCollaborationTools({
mode: 'plan',
tools: [testTool('Read'), testTool('Write')],
hasActiveExecution: false,
fullAccess,
}).map((tool) => tool.name),
prompt: renderPlanModePrompt({ fullAccess }),
};
const backend = new (class extends TestBackend {
override async *send(input: BackendSendInput) {
dispatched.push(composition);
yield* super.send(input);
}
})(ctx, sendGate);
instances.push(backend);
return backend;
},
};
},
});
const manager = new SessionManager({
store,
backends,
newId: nextId(),
now: nextNow(980),
inspectContinuationSafety: async () => {
if (checkpoint === 'safety' && builds.length === 0) await pause();
return {
workspaceIdentity: 'workspace',
workspacePath: '/tmp/cwd',
backgroundOperationsSettled: true,
availableToolNames: [],
};
},
runBackendActivation: async (operation) => {
if (checkpoint === 'activation_gate' && builds.length === 0) await pause();
return operation();
},
});
const session = await manager.createSession(
makeInput({ permissionMode: 'ask', collaborationMode: 'plan' }),
);
if (checkpoint === 'header') {
const readHeader = store.readHeader.bind(store);
let firstRead = true;
store.readHeader = async (id) => {
const header = await readHeader(id);
if (firstRead) {
firstRead = false;
await pause();
}
return header;
};
}
const firstTurn = manager
.sendMessage(
session.id,
{ turnId: 'turn-building', text: 'plan' },
{
admitTurn: async () => {
if (checkpoint === 'admission') await pause();
return 'admitted';
},
},
)
[Symbol.asyncIterator]();
const firstEvent = firstTurn.next();
await activationStarted.promise;
try {
const current = await store.readHeaderRecordSnapshot(session.id);
await manager.transitionSessionConfiguration(session.id, {
expectedRevision: current.revision,
clearConnectionBlock: false,
permissionModeOnly: true,
configuration: configurationForHeader(current.header, { permissionMode: 'bypass' }),
});
assert.strictEqual((await store.readHeader(session.id)).permissionMode, 'bypass');
assert.strictEqual(store.disposeCount, 0);
if (checkpoint === 'activation_gate') {
// A policy mutation owns the gate and refreshes before releasing it.
// Waiting for the queued activation here would deadlock that mutation.
await manager.refreshIdleBackends();
}
} finally {
releaseActivation.release();
}
try {
await firstEvent;
assert.strictEqual(store.disposeCount, 0);
assert.strictEqual(instances[0]?.stopCalls, 0);
} finally {
sendGate.release();
while (!(await firstTurn.next()).done) {}
}
assert.strictEqual(store.disposeCount, 1);
await drain(manager.sendMessage(session.id, { turnId: 'turn-fresh', text: 'write now' }));
assert.deepStrictEqual(builds, ['ask', 'bypass']);
assert.deepStrictEqual(
dispatched.map((input) => input.tools),
[['Read'], ['Read', 'Write']],
);
assert.strictEqual(dispatched[0]?.prompt, renderPlanModePrompt());
assert.strictEqual(dispatched[1]?.prompt, renderPlanModePrompt({ fullAccess: true }));
});
}
test('strict refresh marks cold snapshots without waiting for the policy gate', {
timeout: 10_000,
}, async (t) => {
const store = new MemorySessionStore();
const queued = makeGate();
const releasePolicyMutation = makeGate();
t.after(() => releasePolicyMutation.release());
let builds = 0;
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => {
builds += 1;
return new TestBackend(ctx);
});
const manager = new SessionManager({
store,
backends,
newId: nextId(),
now: nextNow(981),
runBackendActivation: async (operation) => {
queued.release();
await releasePolicyMutation.promise;
return operation();
},
});
const session = await manager.createSession(makeInput());
const firstTurn = drain(manager.sendMessage(session.id, { turnId: 'queued', text: 'start' }));
await queued.promise;
// This is the policy mutation's final step before opening its gate again.
// There is no cached generation and no previous per-session invalidation.
await manager.refreshIdleBackends();
assert.strictEqual(builds, 0);
releasePolicyMutation.release();
await firstTurn;
assert.strictEqual(store.disposeCount, 1);
await drain(manager.sendMessage(session.id, { turnId: 'next', text: 'fresh' }));
assert.strictEqual(builds, 2);
});
for (const outcome of ['cancelled', 'failed'] as const) {
test(`a pre-activation refresh does not retain a ${outcome} admission`, async () => {
const store = new VersionedConfigurationMemorySessionStore();
const admissionStarted = makeGate();
const releaseAdmission = makeGate();
const builds: PermissionMode[] = [];
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => {
builds.push(ctx.header.permissionMode);
return new TestBackend(ctx);
});
const manager = new SessionManager({ store, backends, newId: nextId(), now: nextNow(981) });
const session = await manager.createSession(makeInput());
const firstTurn = assert.rejects(
drain(
manager.sendMessage(
session.id,
{ turnId: 'cancelled', text: 'start' },
{
admitTurn: async () => {
admissionStarted.release();
await releaseAdmission.promise;
if (outcome === 'failed') throw new Error('injected admission failure');
return 'cancelled';
},
},
),
),
/cancelled before runtime admission|injected admission failure/,
);
await admissionStarted.promise;
try {
const current = await store.readHeaderRecordSnapshot(session.id);
await manager.transitionSessionConfiguration(session.id, {
expectedRevision: current.revision,
clearConnectionBlock: false,
permissionModeOnly: true,
configuration: configurationForHeader(current.header, { permissionMode: 'bypass' }),
});
} finally {
releaseAdmission.release();
}
await firstTurn;
await manager.refreshIdleBackends();
await drain(manager.sendMessage(session.id, { turnId: 'retry', text: 'retry' }));
await drain(manager.sendMessage(session.id, { turnId: 'reuse', text: 'reuse' }));
assert.deepStrictEqual(builds, ['bypass']);
assert.strictEqual(store.disposeCount, 0);
});
}
test('settles a backend refresh when an in-flight build fails', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const buildStarted = makeGate();
const releaseBuild = makeGate();
const builds: PermissionMode[] = [];
const backends = new BackendRegistry();
backends.register('ai-sdk', async (ctx) => {
builds.push(ctx.header.permissionMode);
if (builds.length === 1) {
buildStarted.release();
await releaseBuild.promise;
throw new Error('injected activation failure');
}
return new TestBackend(ctx);
});
const manager = new SessionManager({ store, backends, newId: nextId(), now: nextNow(982) });
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
const firstTurn = assert.rejects(
drain(manager.sendMessage(session.id, { turnId: 'turn-failing', text: 'start' })),
/injected activation failure/,
);
await buildStarted.promise;
const current = await store.readHeaderRecordSnapshot(session.id);
await manager.transitionSessionConfiguration(session.id, {
expectedRevision: current.revision,
clearConnectionBlock: false,
permissionModeOnly: true,
configuration: configurationForHeader(current.header, { permissionMode: 'bypass' }),
});
let refreshed = false;
const refresh = manager.refreshIdleBackends().then(() => {
refreshed = true;
});
try {
await new Promise<void>((resolve) => setImmediate(resolve));
assert.strictEqual(refreshed, false);
} finally {
releaseBuild.release();
}
await firstTurn;
await refresh;
await drain(manager.sendMessage(session.id, { turnId: 'turn-retry', text: 'retry' }));
assert.deepStrictEqual(builds, ['ask', 'bypass']);
});
test('revokes background shell authority before narrowing Auto to Explore', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const calls: string[] = [];
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(985),
shellRuns: {
async terminateSession(sessionId: string) {
calls.push(`terminate:${sessionId}`);
return { sessionId, token: Symbol('test') };
},
async commitSessionClose() {
calls.push('commit');
},
rollbackSessionClose() {
calls.push('rollback');
},
resumeSession(sessionId: string) {
calls.push(`resume:${sessionId}`);
},
} as never,
});
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
const current = await store.readHeaderRecordSnapshot(session.id);
await manager.transitionSessionConfiguration(session.id, {
expectedRevision: current.revision,
clearConnectionBlock: false,
permissionModeOnly: true,
configuration: configurationForHeader(current.header, { permissionMode: 'explore' }),
});
assert.deepStrictEqual(calls, [`terminate:${session.id}`, 'commit', `resume:${session.id}`]);
const boundary = await store.readExecutionBoundary(session.id);
assert.strictEqual(boundary.kind, 'managed');
if (boundary.kind === 'managed') assert.strictEqual(boundary.profile.name, 'read-only');
});
test('treats an expanded Explore profile as narrowing before restoring Explore', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const gate = makeGate();
const calls: string[] = [];
const backends = new BackendRegistry();
const runStore = new MemoryAgentRunStore();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx, gate));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(987),
shellRuns: {
async terminateSession(sessionId: string) {
calls.push(`terminate:${sessionId}`);
return { sessionId, token: Symbol('test') };
},
async commitSessionClose() {
calls.push('commit');
},
rollbackSessionClose() {
calls.push('rollback');
},
resumeSession(sessionId: string) {
calls.push(`resume:${sessionId}`);
},
} as never,
});
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
store.forceBoundary(session.id, {
kind: 'managed',
profile: applySandboxBoundaryExpansion(createReadOnlyPermissionProfile(), {
filesystem: {
entries: [{ path: '/approved/output', access: 'write', scope: 'subtree' }],
},
}),
revision: 1,
});
const activeTurn = manager
.sendMessage(session.id, { turnId: 'turn-expanded-explore', text: 'keep running' })
[Symbol.asyncIterator]();
await activeTurn.next();
const current = await store.readHeaderRecordSnapshot(session.id);
const narrowing = {
expectedRevision: current.revision,
clearConnectionBlock: false,
permissionModeOnly: true,
configuration: configurationForHeader(current.header, { permissionMode: 'explore' }),
} as const;
await expectRejects(
manager.transitionSessionConfiguration(session.id, narrowing),
/linked Turn is active/,
);
assert.deepStrictEqual(calls, []);
assert.strictEqual((await store.readHeader(session.id)).permissionMode, 'ask');
gate.release();
while (!(await activeTurn.next()).done) {}
await manager.transitionSessionConfiguration(session.id, narrowing);
assert.deepStrictEqual(calls, [`terminate:${session.id}`, 'commit', `resume:${session.id}`]);
assert.strictEqual((await store.readHeader(session.id)).permissionMode, 'explore');
});
for (const route of ['configuration', 'boundary'] as const) {
for (const grant of ['read', 'write', 'network'] as const) {
test(`restoring Explore revokes an approved ${grant} through the durable ${route} path`, async (t) => {
const root = await mkdtemp(join(tmpdir(), 'maka-explore-read-revocation-'));
const store = createSessionStore(root);
t.after(async () => {
await store.close?.();
await rm(root, { recursive: true, force: true });
});
const gate = makeGate();
const calls: string[] = [];
const backends = new BackendRegistry();
const runStore = new MemoryAgentRunStore();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx, gate));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(988),
shellRuns: {
async terminateSession(sessionId: string) {
calls.push(`terminate:${sessionId}`);
return { sessionId, token: Symbol('test') };
},
async commitSessionClose() {
calls.push('commit');
},
rollbackSessionClose() {
calls.push('rollback');
},
resumeSession(sessionId: string) {
calls.push(`resume:${sessionId}`);
},
} as never,
});
const workspaceRoot = join(root, 'workspace');
const outsidePath = join(root, 'approved', 'input.txt');
const session = await manager.createSession(
makeInput({ permissionMode: 'explore', cwd: workspaceRoot }),
);
const updatePermissionMode = async (permissionMode: PermissionMode) => {
const current = await store.readHeaderRecordSnapshot(session.id);
return manager.transitionSessionConfiguration(session.id, {
expectedRevision: current.revision,
clearConnectionBlock: false,
permissionModeOnly: true,
configuration: configurationForHeader(current.header, { permissionMode }),
});
};
await store.createSandboxBoundaryRequest({
sessionId: session.id,
requestId: 'approved-expansion',
turnId: 'turn-approval',
runId: 'run-approval',
expansion:
grant === 'network'
? { network: { enabled: true } }
: {
filesystem: { entries: [{ path: outsidePath, access: grant, scope: 'exact' }] },
},
justification: 'Approve one specific sandbox expansion.',
});
const settlement = await store.settleSandboxBoundaryRequest({
sessionId: session.id,
requestId: 'approved-expansion',
decision: 'allow',
});
assert.strictEqual(settlement.request.status, 'approved');
if (route === 'configuration') await updatePermissionMode('ask');
const restoreExplore = () =>
route === 'configuration'
? updatePermissionMode('explore')
: manager.setExecutionBoundaryKind(session.id, 'managed');
const expanded = await store.readExecutionBoundary(session.id);
assert.strictEqual(expanded.kind, 'managed');
if (expanded.kind !== 'managed') throw new Error('Expected a managed boundary');
assert.strictEqual(expanded.profile.name, 'read-only');
assert.strictEqual(isReadOnlyPermissionProfile(expanded.profile), grant === 'read');
assert.strictEqual(
expanded.profile.network.kind,
grant === 'network' ? 'enabled' : 'restricted',
);
assert.strictEqual(
canReadPath(expanded.profile, outsidePath, { workspaceRoots: [workspaceRoot] }),
grant !== 'network',
);
assert.deepStrictEqual(calls, []);
const activeTurn = manager
.sendMessage(session.id, { turnId: 'turn-expanded-read', text: 'keep reading' })
[Symbol.asyncIterator]();
try {
await activeTurn.next();
await assert.rejects(restoreExplore(), (error: unknown) => {
if (route === 'boundary') {
assert.ok(error instanceof SessionConfigurationTransitionError);
assert.strictEqual(error.code, 'session_busy');
return true;
}
assert.ok(error instanceof SessionConfigurationTransitionError);
assert.strictEqual(error.code, 'session_busy');
return true;
});
assert.deepStrictEqual(await store.readExecutionBoundary(session.id), expanded);
assert.strictEqual(
(await store.readHeader(session.id)).permissionMode,
route === 'configuration' ? 'ask' : 'explore',
);
assert.deepStrictEqual(calls, []);
} finally {
gate.release();
while (!(await activeTurn.next()).done) {}
}
await restoreExplore();
assert.deepStrictEqual(calls, [
`terminate:${session.id}`,
'commit',
`resume:${session.id}`,
]);
const narrowed = await store.readExecutionBoundary(session.id);
assert.strictEqual(narrowed.kind, 'managed');
if (narrowed.kind !== 'managed') throw new Error('Expected a managed boundary');
assert.deepStrictEqual(narrowed.profile, createReadOnlyPermissionProfile());
assert.strictEqual(
canReadPath(narrowed.profile, outsidePath, { workspaceRoots: [workspaceRoot] }),
false,
);
assert.strictEqual((await store.readHeader(session.id)).permissionMode, 'explore');
});
}
}
test('revokes descendant background shell authority through the direct boundary API', async () => {
const store = new AtomicBoundaryMemorySessionStore();
const calls: string[] = [];
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const manager = new SessionManager({
store,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(990),
shellRuns: {
async terminateSession(sessionId: string) {
calls.push(`terminate:${sessionId}`);
return { sessionId, token: Symbol('test') };
},
async commitSessionClose() {
calls.push('commit');
},
rollbackSessionClose() {
calls.push('rollback');
},
resumeSession(sessionId: string) {
calls.push(`resume:${sessionId}`);
},
} as never,
});
const session = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
const child = await manager.createSession(
makeInput({
permissionMode: 'bypass',
subagentParent: {
kind: 'subagent',
parentSessionId: session.id,
spawnedBy: {
parentRunId: 'parent-run',
parentTurnId: 'parent-turn',
toolCallId: 'child-tool',
},
lifecycle: 'foreground',
},
subagentRuntime: {
schemaVersion: 1,
definitionVersion: LOCAL_READ_AGENT_DEFINITION.definitionVersion,
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
profile: LOCAL_READ_AGENT_PROFILE,
systemPrompt: LOCAL_READ_AGENT_DEFINITION.systemPrompt,
toolNames: ['Read', 'Glob', 'Grep'],
categoryPolicy: {},
},
subagentSpawn: {
schemaVersion: 1,
requestFingerprint: 'a'.repeat(64),
initialTurnId: 'child-turn',
initialRunId: 'child-run',
},
}),
);
const grandchild = await manager.createSession(
makeInput({
permissionMode: 'bypass',
subagentParent: {
kind: 'subagent',
parentSessionId: child.id,
spawnedBy: {
parentRunId: 'child-run',
parentTurnId: 'child-turn',
toolCallId: 'grandchild-tool',
},
lifecycle: 'foreground',
},
subagentRuntime: {
schemaVersion: 1,
definitionVersion: LOCAL_READ_AGENT_DEFINITION.definitionVersion,
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
profile: LOCAL_READ_AGENT_PROFILE,
systemPrompt: LOCAL_READ_AGENT_DEFINITION.systemPrompt,
toolNames: ['Read', 'Glob', 'Grep'],
categoryPolicy: {},
},
subagentSpawn: {
schemaVersion: 1,
requestFingerprint: 'b'.repeat(64),
initialTurnId: 'grandchild-turn',
initialRunId: 'grandchild-run',
},
}),
);
await drain(manager.sendMessage(session.id, { turnId: 'parent-turn', text: 'parent' }));
await drain(manager.sendMessage(child.id, { turnId: 'child-turn', text: 'child' }));
await drain(
manager.sendMessage(grandchild.id, { turnId: 'grandchild-turn', text: 'grandchild' }),
);
store.disposeCount = 0;
await manager.setExecutionBoundaryKind(session.id, 'managed');
assert.deepStrictEqual(calls, [
`terminate:${session.id}`,
`terminate:${child.id}`,
`terminate:${grandchild.id}`,
'commit',
'commit',
'commit',
`resume:${session.id}`,
]);
assert.strictEqual(store.disposeCount, 3);
});
test('keeps narrowing blocked until all overlapping turns finish', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const firstGate = makeGate();
const secondGate = makeGate();
const gates = [firstGate, secondGate];
backends.register('ai-sdk', (ctx) => new TestBackend(ctx, gates.shift()));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(4_000),
});
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
const first = manager
.sendMessage(session.id, { turnId: 'turn-1', text: 'first' })
[Symbol.asyncIterator]();
await first.next();
const second = manager
.sendMessage(session.id, { turnId: 'turn-2', text: 'second' })
[Symbol.asyncIterator]();
await second.next();
firstGate.release();
await first.next();
await first.next();
assert.strictEqual((await store.readHeader(session.id)).status, 'running');
const afterFirstRuns = await runStore.listSessionInvocations(session.id);
assert.deepStrictEqual(
afterFirstRuns.map((run) => [run.turnId, runtimeInvocationOutcome(run)]),
[
['turn-1', 'completed'],
['turn-2', undefined],
],
);
// Widening is a grant, so it commits against the live Turn instead of
// making the user wait for it out (#3349).
const current = await store.readHeaderRecordSnapshot(session.id);
const widened = await manager.transitionSessionConfiguration(session.id, {
expectedRevision: current.revision,
clearConnectionBlock: false,
permissionModeOnly: true,
configuration: configurationForHeader(current.header, { permissionMode: 'bypass' }),
});
assert.strictEqual(widened.header.permissionMode, 'bypass');
assert.strictEqual((await manager.readExecutionBoundary(session.id)).kind, 'bypass');
// Narrowing still requires quiescence: that is what lets it terminate the
// lineage's shells safely.
await expectRejects(
manager.transitionSessionConfiguration(session.id, {
expectedRevision: widened.revision,
clearConnectionBlock: false,
permissionModeOnly: true,
configuration: configurationForHeader(widened.header, { permissionMode: 'explore' }),
}),
/linked Turn is active/,
);
secondGate.release();
await second.next();
await second.next();
assert.strictEqual((await store.readHeader(session.id)).status, 'active');
const finalRuns = await runStore.listSessionInvocations(session.id);
assert.deepStrictEqual(
finalRuns.map((run) => [run.turnId, runtimeInvocationOutcome(run)]),
[
['turn-1', 'completed'],
['turn-2', 'completed'],
],
);
const summary = await manager.setPermissionMode(session.id, 'bypass');
assert.strictEqual(summary.permissionMode, 'bypass');
});
test('the setPermissionMode wrapper delegates deep research cleanup to configuration authority', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const manager = new SessionManager({ store, backends, newId: nextId(), now: nextNow(6_000) });
const session = await manager.createSession(
makeInput({
permissionMode: 'explore',
labels: [DEEP_RESEARCH_SESSION_LABEL, 'kept'],
}),
);
const summary = await manager.setPermissionMode(session.id, 'ask');
assert.strictEqual(summary.permissionMode, 'ask');
assert.deepStrictEqual(summary.labels, ['kept']);
assert.deepStrictEqual((await store.readHeader(session.id)).labels, ['kept']);
});
test('temporarily preserves setPermissionMode for legacy SessionStore implementations', async () => {
const store = new MemorySessionStore();
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(6_100),
});
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
const summary = await manager.setPermissionMode(session.id, 'bypass');
assert.strictEqual(summary.permissionMode, 'bypass');
assert.strictEqual((await store.readHeader(session.id)).permissionMode, 'bypass');
assert.strictEqual((await store.readExecutionBoundary(session.id)).kind, 'bypass');
});
test('starts a new turn without workspace identity when safety inspection fails', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new FinalTextTestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
safeBoundaryResumeEnabled: true,
inspectContinuationSafety: async () => {
throw new Error('workspace marker is unavailable');
},
newId: nextId(),
now: nextNow(6_526),
});
const session = await manager.createSession(
makeInput({ llmConnectionId: '11111111-1111-4111-8111-111111111111' }),
);
const events = await collectSessionEvents(
manager.sendMessage(session.id, {
turnId: 'turn-workspace-identity-unavailable',
text: 'continue without resumability',
}),
);
assert.deepStrictEqual(
events.map((event) => event.type),
['text_complete', 'complete'],
);
const [run] = await runStore.listSessionInvocations(session.id);
assert.partialDeepStrictEqual(run?.opening.route, {
provenance: 'runtime',
llmConnectionId: '11111111-1111-4111-8111-111111111111',
});
assert.strictEqual(run?.opening.configuration.workspaceIdentity, undefined);
});
test('records handoff workspace identity even while manual resume is disabled', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new FinalTextTestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: async () => {
return {
workspaceIdentity: 'workspace-for-handoff',
backgroundOperationsSettled: true,
availableToolNames: [],
};
},
newId: nextId(),
now: nextNow(6_527),
});
const session = await manager.createSession(makeInput());
await collectSessionEvents(
manager.sendMessage(session.id, {
turnId: 'turn-resume-disabled',
text: 'normal happy path',
}),
);
const [run] = await runStore.listSessionInvocations(session.id);
assert.strictEqual(run?.opening.configuration.workspaceIdentity, 'workspace-for-handoff');
const plan = await manager.planAuthoritativeSafeBoundaryContinuation(session.id, {
sourceRunId: run!.runId,
});
assert.deepEqual(plan.rejectionReasons, ['resume_feature_disabled']);
});
test('declares the T1 protocol for an AiSdk run when the host wires the durable boundary', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new CountingFinalTextBackend(ctx, () => {}));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
toolBoundaryProtocol: 't1_after_preflight_v1',
backends,
newId: nextId(),
now: nextNow(6_060),
});
const session = await manager.createSession(makeInput());
await collectSessionEvents(
manager.sendMessage(session.id, {
turnId: 'turn-with-tool-boundary',
text: 'hello',
}),
);
const [run] = await runStore.listSessionInvocations(session.id);
if (!run) throw new Error('expected run');
const events = await runStore.readRuntimeEvents(session.id, run.runId);
assert.deepStrictEqual(events[0]?.actions?.runtimeProtocol, {
toolBoundary: 't1_after_preflight_v1',
});
});
test('plans continuation from authoritative host facts without caller-supplied safety claims', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', () => {
throw new Error('continuation planning must not build a backend');
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
safeBoundaryResumeEnabled: true,
inspectContinuationSafety: async () => ({
workspaceIdentity: 'workspace-authoritative',
backgroundOperationsSettled: true,
availableToolNames: [],
}),
newId: nextId(),
now: nextNow(6_530),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run-authoritative-plan';
const sourceTurnId = 'source-turn-authoritative-plan';
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
cwd: header.cwd,
workspaceIdentity: 'workspace-authoritative',
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: 'source-user-authoritative-plan',
invocationId: 'source-invocation-authoritative-plan',
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue from host facts' },
}),
runtimeEvent({
id: 'source-terminal-authoritative-plan',
invocationId: 'source-invocation-authoritative-plan',
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
const plan = await manager.planAuthoritativeSafeBoundaryContinuation(session.id, {
sourceRunId,
});
assert.strictEqual(plan.disposition, 'continue');
assert.deepStrictEqual(plan.continuation?.safetySnapshot, {
workspaceIdentity: 'workspace-authoritative',
backgroundOperationsSettled: true,
availableToolNames: [],
});
});
test('parks when authoritative continuation safety inspection fails', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
safeBoundaryResumeEnabled: true,
inspectContinuationSafety: async () => {
throw new Error('safety authority unavailable');
},
newId: nextId(),
now: nextNow(6_535),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
await seedInvocationFromHeader(
runStore,
makeRunHeader({
runId: 'source-run-safety-failure',
sessionId: session.id,
turnId: 'source-turn-safety-failure',
status: 'failed',
cwd: header.cwd,
workspaceIdentity: 'workspace-safety-failure',
completedAt: 2,
failureClass: 'app_restarted',
}),
);
const plan = await manager.planAuthoritativeSafeBoundaryContinuation(session.id, {
sourceRunId: 'source-run-safety-failure',
});
assert.strictEqual(plan.disposition, 'park');
assert.deepStrictEqual(plan.rejectionReasons, ['safety_observation_unavailable']);
});
test('keeps the authoritative continuation entry disabled unless the host enables it', async () => {
const store = new MemorySessionStore();
const backends = new BackendRegistry();
const lifecycleEvents: Array<{ type: string; rejectionReasons?: readonly string[] }> = [];
const manager = new SessionManager({
store,
backends,
onContinuationLifecycleEvent: (event) => {
lifecycleEvents.push(event);
},
newId: nextId(),
now: nextNow(6_535),
});
const plan = await manager.planAuthoritativeSafeBoundaryContinuation('session-disabled', {
sourceRunId: 'source-run-disabled',
});
assert.strictEqual(plan.disposition, 'park');
assert.deepStrictEqual(plan.rejectionReasons, ['resume_feature_disabled']);
assert.deepStrictEqual(lifecycleEvents, [
{
type: 'plan_parked',
sessionId: 'session-disabled',
sourceRunId: 'source-run-disabled',
rejectionReasons: ['resume_feature_disabled'],
},
]);
});
test('discovers the newest resumable top-level source run from durable state', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', () => {
throw new Error('continuation planning must not build a backend');
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
safeBoundaryResumeEnabled: true,
inspectContinuationSafety: inspectStableContinuationSafety,
newId: nextId(),
now: nextNow(6_540),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
for (const [index, runId] of ['source-run-older', 'source-run-newer'].entries()) {
const turnId = `source-turn-${index}`;
const invocationId = `source-invocation-${index}`;
await seedRuntimeRun(
runStore,
makeRunHeader({
runId,
sessionId: session.id,
turnId,
status: 'failed',
cwd: header.cwd,
workspaceIdentity: 'workspace-1',
createdAt: index + 1,
updatedAt: index + 2,
completedAt: index + 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: `source-user-${index}`,
invocationId,
runId,
sessionId: session.id,
turnId,
ts: index + 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: `continue source ${index}` },
}),
runtimeEvent({
id: `source-terminal-${index}`,
invocationId,
runId,
sessionId: session.id,
turnId,
ts: index + 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
}
const plan = await manager.planLatestAuthoritativeSafeBoundaryContinuation(session.id);
assert.strictEqual(plan.disposition, 'continue');
assert.strictEqual(plan.continuation?.sourceRunId, 'source-run-newer');
});
test('fresh-turn tool policy changes only new runs and preserves Session defaults', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const instances = new Map<string, TestBackend>();
const gate = makeGate();
let mode: ToolMode = 'code_mode';
let dynamicSessionId: string | undefined;
backends.register('ai-sdk', (ctx) => {
const backend = new TestBackend(ctx, gate);
instances.set(ctx.sessionId, backend);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_450),
resolveFreshTurnToolMode: async (header) =>
header.id === dynamicSessionId ? mode : undefined,
});
const dynamic = await manager.createSession({ ...makeInput(), toolMode: 'direct' });
const ordinary = await manager.createSession({ ...makeInput(), toolMode: 'code_mode' });
dynamicSessionId = dynamic.id;
const active = manager
.sendMessage(dynamic.id, { turnId: 'dynamic-1', text: 'first' })
[Symbol.asyncIterator]();
await active.next();
mode = 'direct';
assert.equal(instances.get(dynamic.id)?.sendInputs[0]?.toolMode, 'code_mode');
gate.release();
while (!(await active.next()).done) {
/* Drain the running turn. */
}
await drainAll(manager.sendMessage(dynamic.id, { turnId: 'dynamic-2', text: 'second' }));
await drainAll(manager.sendMessage(ordinary.id, { turnId: 'ordinary-1', text: 'ordinary' }));
assert.deepEqual(
instances.get(dynamic.id)?.sendInputs.map((input) => input.toolMode),
['code_mode', 'direct'],
);
assert.equal(instances.get(ordinary.id)?.sendInputs[0]?.toolMode, 'code_mode');
assert.equal((await store.readHeader(dynamic.id)).toolMode, 'direct');
assert.equal((await store.readHeader(ordinary.id)).toolMode, 'code_mode');
assert.deepEqual(
(await runStore.listSessionInvocations(dynamic.id)).map(
(run) => run.opening.configuration.toolMode,
),
['code_mode', 'direct'],
);
});
test('RuntimeKernel drives the backend while preserving the SessionEvent stream', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const runtimeEventStore = new MemoryRuntimeEventStore();
const backends = new BackendRegistry();
let backend: FinalTextTestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new FinalTextTestBackend(ctx);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore,
backends,
newId: nextId(),
now: nextNow(6_500),
});
const session = await manager.createSession(makeInput());
const sessionEvents = await collectSessionEvents(
manager.sendMessage(session.id, {
turnId: 'turn-1',
text: 'hello',
toolMode: 'code_mode',
}),
);
assert.deepStrictEqual(
sessionEvents.map((event) => event.type),
['text_complete', 'complete'],
);
assert.deepStrictEqual(
sessionEvents.map((event) => event.id),
['turn-1-final', 'turn-1-complete'],
);
assert.strictEqual(backend?.sendInputs[0]?.toolMode, 'code_mode');
const [run] = await runtimeEventStore.listSessionInvocations(session.id);
if (!run) throw new Error('the run opened no invocation');
const runtimeEvents = await runtimeEventStore.readRuntimeEvents(session.id, run.runId);
assert.deepStrictEqual(backend?.sendInputs[0]?.headAnchorRuntimeEvent, runtimeEvents[1]);
assert.deepStrictEqual(
runtimeEvents.map((event) => event.runId),
[run.runId, run.runId, run.runId, run.runId],
);
assert.deepStrictEqual(
runtimeEvents.map((event) => event.sessionId),
[session.id, session.id, session.id, session.id],
);
assert.deepStrictEqual(
runtimeEvents.map((event) => event.turnId),
['turn-1', 'turn-1', 'turn-1', 'turn-1'],
);
assert.deepStrictEqual(
runtimeEvents.map((event) => event.role),
['system', 'user', 'model', 'system'],
);
assert.strictEqual(runtimeEvents[0]?.content?.kind, 'invocation_opened');
assert.deepStrictEqual(runtimeEvents[1]?.content, { kind: 'text', text: 'hello' });
assert.deepStrictEqual(runtimeEvents[2]?.content, { kind: 'text', text: 'ok' });
assert.strictEqual(runtimeEvents[3]?.status, 'completed');
});
test('the invocation opening fact is durable before any dispatch', async () => {
const store = new MemorySessionStore();
const trace: string[] = [];
const runStore = new MemoryAgentRunStore({
beforeRuntimeEventAppend: (_sessionId, _runId, event, options) => {
trace.push(
`runtime:${event.content?.kind ?? event.status ?? 'fact'}:durable=${options?.durable === true}`,
);
},
beforeAgentRunEventAppend: (_sessionId, _runId, event) => {
trace.push(`ledger:${event.type}`);
},
});
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => {
trace.push('backend:activated');
return new FinalTextTestBackend(ctx);
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(7_100),
});
const session = await manager.createSession(makeInput());
await collectSessionEvents(
manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }),
);
const openingIndex = trace.findIndex((entry) => entry.startsWith('runtime:invocation_opened'));
assert.notStrictEqual(openingIndex, -1, 'the invocation must commit an opening fact');
assert.strictEqual(
trace.slice(0, openingIndex).some((entry) => entry.startsWith('runtime:')),
false,
'the opening fact must be the first RuntimeEvent of the invocation',
);
assert.ok(
openingIndex < trace.findIndex((entry) => entry.startsWith('runtime:text')),
'the opening fact must precede the first model-visible event of the turn',
);
});
test('a rejected opening fact stops the turn before the backend can dispatch', async () => {
const store = new MemorySessionStore();
const sends: string[] = [];
const runStore = new MemoryAgentRunStore({
beforeRuntimeEventAppend: (_sessionId, _runId, event) => {
if (event.content?.kind === 'invocation_opened') {
throw new Error('opening fact store is unavailable');
}
},
});
const canonicalRuntimeEventStore: RuntimeEventStore = Object.assign(
Object.create(Object.getPrototypeOf(runStore) as object) as MemoryAgentRunStore,
runStore,
{ durability: 'canonical' as const },
);
const backends = new BackendRegistry();
let backend: FinalTextTestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new FinalTextTestBackend(ctx);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: canonicalRuntimeEventStore,
backends,
newId: nextId(),
now: nextNow(7_150),
});
const session = await manager.createSession(makeInput());
await assert.rejects(
collectSessionEvents(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })),
/opening fact store is unavailable/,
);
assert.deepStrictEqual(
backend?.sendInputs ?? [],
[],
'no provider dispatch may happen without a durable opening fact',
);
assert.deepStrictEqual(sends, []);
});
test('snapshots mutable turn content before durable commit and backend dispatch', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const durableEvents = new MemoryRuntimeEventStore();
const initialCommitReached = makeGate();
const releaseInitialCommit = makeGate();
let heldInitialCommit = false;
const runtimeEventStore: RuntimeEventStore = {
appendRuntimeEvent: async (sessionId, runId, event) => {
await durableEvents.appendRuntimeEvent(sessionId, runId, event);
if (!heldInitialCommit && event.role === 'user') {
heldInitialCommit = true;
initialCommitReached.release();
await releaseInitialCommit.promise;
}
},
ensureTerminalRuntimeEventDurable: (sessionId, runId, event) =>
durableEvents.ensureTerminalRuntimeEventDurable(sessionId, runId, event),
readRuntimeEvents: (sessionId, runId) => durableEvents.readRuntimeEvents(sessionId, runId),
readSessionRuntimeEventEntries: (sessionId) =>
durableEvents.readSessionRuntimeEventEntries(sessionId),
resequenceSessionEventOrdinals: (sessionId) =>
durableEvents.resequenceSessionEventOrdinals(sessionId),
readSessionRuntimeEvents: (sessionId) => durableEvents.readSessionRuntimeEvents(sessionId),
listSessionInvocations: (sessionId) => durableEvents.listSessionInvocations(sessionId),
};
const backends = new BackendRegistry();
let providerInput: BackendSendInput | undefined;
let providerAttachmentMutationRejected = false;
let headAnchorMutationRejected = false;
backends.register('ai-sdk', (ctx) => ({
kind: 'ai-sdk' as const,
sessionId: ctx.sessionId,
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
providerInput = input;
try {
if (input.attachments?.[0]) input.attachments[0].name = 'backend-mutated';
} catch {
providerAttachmentMutationRejected = true;
}
try {
const content = input.headAnchorRuntimeEvent?.content;
if (content?.kind === 'text' && content.attachments?.[0]) {
content.attachments[0].name = 'backend-mutated-anchor';
}
} catch {
headAnchorMutationRejected = true;
}
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 2,
stopReason: 'end_turn',
};
},
async stop() {},
async respondToSandboxBoundary() {},
async dispose() {},
}));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore,
backends,
newId: nextId(),
now: nextNow(6_510),
});
const session = await manager.createSession(makeInput());
const attachments: NonNullable<UserMessageInput['attachments']> = [
{
kind: 'code',
name: 'accepted.ts',
mimeType: 'text/typescript',
bytes: 10,
ref: { kind: 'workspace_file', relativePath: 'accepted.ts' },
},
];
const quotes: NonNullable<UserMessageInput['quotes']> = [
{ text: 'accepted quote', sourceTurnId: 'source-turn' },
];
const inlineReferences: NonNullable<UserMessageInput['inlineReferences']> = [
{ kind: 'workspace_file', value: '@accepted.ts', label: 'accepted.ts', start: 0 },
];
const execution = collectSessionEvents(
manager.sendMessage(session.id, {
turnId: 'turn-snapshot',
text: 'inspect the attachment',
attachments,
quotes,
inlineReferences,
}),
);
await initialCommitReached.promise;
attachments[0]!.name = 'caller-mutated';
attachments[0]!.ref = { kind: 'workspace_file', relativePath: 'caller-mutated.ts' };
quotes[0]!.text = 'caller-mutated quote';
inlineReferences[0]!.label = 'caller-mutated.ts';
releaseInitialCommit.release();
await execution;
assert.strictEqual(providerAttachmentMutationRejected, true);
assert.strictEqual(headAnchorMutationRejected, true);
assert.deepStrictEqual(providerInput?.attachments?.[0], {
kind: 'code',
name: 'accepted.ts',
mimeType: 'text/typescript',
bytes: 10,
ref: { kind: 'workspace_file', relativePath: 'accepted.ts' },
});
assert.deepStrictEqual(providerInput?.quotes, [
{ text: 'accepted quote', sourceTurnId: 'source-turn' },
]);
assert.strictEqual(Object.isFrozen(providerInput?.attachments), true);
assert.strictEqual(Object.isFrozen(providerInput?.attachments?.[0]?.ref), true);
const headContent = providerInput?.headAnchorRuntimeEvent?.content;
assert.strictEqual(headContent?.kind, 'text');
if (headContent?.kind !== 'text') throw new Error('Expected text head anchor');
assert.deepStrictEqual(headContent.attachments, providerInput?.attachments);
assert.strictEqual(headContent.attachments === providerInput?.attachments, false);
assert.strictEqual(Object.isFrozen(providerInput?.headAnchorRuntimeEvent), true);
assert.strictEqual(Object.isFrozen(headContent), true);
const [run] = await runtimeEventStore.listSessionInvocations(session.id);
if (!run) throw new Error('the run opened no invocation');
const [openingFact, storedUserEvent] = await durableEvents.readRuntimeEvents(
session.id,
run.runId,
);
assert.strictEqual(openingFact?.content?.kind, 'invocation_opened');
assert.deepStrictEqual(storedUserEvent?.content, {
kind: 'text',
text: 'inspect the attachment',
attachments: [
{
kind: 'code',
name: 'accepted.ts',
mimeType: 'text/typescript',
bytes: 10,
ref: { kind: 'workspace_file', relativePath: 'accepted.ts' },
},
],
quotes: [{ text: 'accepted quote', sourceTurnId: 'source-turn' }],
inlineReferences: [
{ kind: 'workspace_file', value: '@accepted.ts', label: 'accepted.ts', start: 0 },
],
});
const storedUserMessage = (await manager.getMessages(session.id)).find(
(message) => message.type === 'user' && message.turnId === 'turn-snapshot',
);
assert.deepStrictEqual(
storedUserMessage?.type === 'user' ? storedUserMessage.attachments : undefined,
providerInput?.attachments,
);
});
test('RuntimeKernel preserves the per-turn step cap through backend dispatch', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let providerSteps = 0;
const model = new MockLanguageModelV4({
doStream: async () => {
providerSteps += 1;
return {
stream: simulateReadableStream({
chunks: [
{ type: 'stream-start', warnings: [] },
{
type: 'tool-call',
toolCallId: `tool-${providerSteps}`,
toolName: 'Read',
input: JSON.stringify({ path: `notes-${providerSteps}.md` }),
},
{
type: 'finish',
finishReason: { unified: 'tool-calls', raw: 'tool_calls' },
usage: {
inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 },
outputTokens: { total: 1, text: 1, reasoning: 0 },
},
},
] as LanguageModelV4StreamPart[],
initialDelayInMs: null,
chunkDelayInMs: null,
}),
};
},
});
backends.register('ai-sdk', (ctx) =>
createTestAiSdkBackend({
sessionId: ctx.sessionId,
header: ctx.header,
connection: {
slug: 'mock-main',
providerType: 'anthropic',
defaultModel: 'mock-model-id',
},
apiKey: 'sk-test',
modelId: 'mock-model-id',
modelFactory: () => model,
tools: [
{
name: 'Read',
description: 'Read a file',
parameters: z.object({ path: z.string() }),
impl: async () => ({ ok: true }),
},
],
maxSteps: 3,
...(ctx.loadTurnRuntimeEvents ? { loadTurnRuntimeEvents: ctx.loadTurnRuntimeEvents } : {}),
newId: nextId(),
now: nextNow(1),
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_525),
});
const session = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
const events = await collectSessionEvents(
manager.sendMessage(session.id, {
turnId: 'turn-step-cap',
text: 'Keep calling Read.',
maxSteps: 1,
}),
);
assert.strictEqual(providerSteps, 1);
assert.partialDeepStrictEqual(
events.find((event) => event.type === 'token_usage' && event.runtimeSteps !== undefined),
{ type: 'token_usage', runtimeSteps: 1 },
);
assert.partialDeepStrictEqual(events.at(-1), { type: 'complete', stopReason: 'step_limit' });
});
for (const decision of [
'client_denied',
'turn_stopped',
'turn_terminal',
'host_restarted',
'missing_reader',
] as const) {
test(`manual continuation preserves sandbox denial authority: ${decision}`, async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backend: FinalTextTestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new FinalTextTestBackend(ctx);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: inspectStableContinuationSafety,
newId: nextId(),
now: nextNow(6_549),
});
const session = await manager.createSession(makeInput());
const source = {
sessionId: session.id,
runId: 'denied-source-run',
turnId: 'denied-source-turn',
};
await seedRuntimeRun(
runStore,
makeRunHeader({
...source,
status: 'failed',
failureClass: 'runtime_interrupted',
cwd: session.cwd,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
}),
[
runtimeEvent({
...source,
id: 'denied-source-user',
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue safely' },
}),
runtimeEvent({
...source,
id: 'denied-source-terminal',
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'runtime_interrupted' } },
}),
],
);
// An unrelated Run's explicit answer never belongs to this source.
for (const runId of [source.runId, 'unrelated-run']) {
await store.createSandboxBoundaryRequest({
...source,
runId,
requestId: `boundary-${runId}`,
expansion: { network: { enabled: true } },
justification: 'Fetch a dependency.',
});
await store.settleSandboxBoundaryRequest({
sessionId: source.sessionId,
requestId: `boundary-${runId}`,
decision: 'deny',
...(runId === source.runId &&
decision !== 'client_denied' &&
decision !== 'missing_reader'
? { closureReason: decision }
: {}),
});
}
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId: source.runId,
currentCwd: session.cwd!,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
assert.ok(plan.continuation);
if (decision === 'missing_reader') {
Object.defineProperty(store, 'hasExplicitSandboxBoundaryDenial', { value: undefined });
await assert.rejects(
collectSessionEvents(manager.resumeSafeBoundaryContinuation(plan.continuation)),
/authoritative sandbox boundary decision lookup/,
);
assert.equal(backend?.sendInputs.length ?? 0, 0);
await assert.rejects(
readInvocation(runStore, session.id, plan.continuation.runId),
/Unknown run/,
);
} else {
await collectSessionEvents(manager.resumeSafeBoundaryContinuation(plan.continuation));
assert.ok(backend);
assert.equal(backend.sendInputs.length, 1);
assert.equal(
backend.sendInputs[0]?.continuation?.sandboxBoundaryDenied,
decision === 'client_denied',
);
}
});
}
test('executes an approved continuation after a path move without another user message', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const lifecycleEvents: Array<{ type: string }> = [];
let backend: FinalTextTestBackend | undefined;
const providerStateIdentity = `sha256:${'c'.repeat(64)}` as const;
backends.register('ai-sdk', {
prepare: async () => ({
providerStateIdentity,
build: (ctx) => {
backend = new FinalTextTestBackend(ctx);
return backend;
},
}),
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
toolBoundaryProtocol: 't1_after_preflight_v1',
resolveFreshTurnToolMode: async () => 'direct',
backends,
childTools: [testTool('Read')],
inspectContinuationSafety: async () => ({
workspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: ['Read'],
}),
onContinuationLifecycleEvent: (event) => {
lifecycleEvents.push(event);
},
newId: nextId(),
now: nextNow(6_550),
});
const session = await manager.createSession(
makeInput({ llmConnectionId: 'connection-continuation' }),
);
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run';
const sourceTurnId = 'source-turn';
const sourceInvocationId = 'source-invocation';
// The source run states its own terminal event below, so only its opening
// is seeded here: a second ending would leave the invocation ambiguous.
await seedInvocationOpening(runStore, {
runId: sourceRunId,
invocationId: sourceInvocationId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
failureClass: 'runtime_interrupted',
backendKind: header.backend,
llmConnectionId: header.llmConnectionId,
providerStateIdentity,
llmConnectionSlug: header.llmConnectionSlug,
modelId: header.model,
cwd: header.cwd,
permissionMode: header.permissionMode,
orchestrationMode: 'swarm',
orchestrationSource: 'turn_override',
agentSwarmAuthorization: 'turn_override',
toolMode: 'code_mode',
createdAt: 1,
updatedAt: 2,
completedAt: 2,
});
const sourceEvents: RuntimeEvent[] = [
{
id: 'source-user',
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 1,
partial: false,
author: 'user',
role: 'user',
content: { kind: 'text', text: 'continue safely' },
},
{
id: 'source-thinking',
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 2,
partial: false,
author: 'agent',
role: 'model',
content: {
kind: 'thinking',
text: 'same-route provider reasoning',
signature: 'same-route-signature',
},
refs: { stepId: 'source-step' },
},
{
id: 'source-tool-call',
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 3,
partial: false,
author: 'agent',
role: 'model',
content: {
kind: 'function_call',
id: 'source-read',
name: 'Read',
args: { path: 'package.json' },
},
refs: { stepId: 'source-step' },
},
{
id: 'source-tool-result',
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 4,
partial: false,
author: 'tool',
role: 'tool',
content: {
kind: 'function_response',
id: 'source-read',
name: 'Read',
result: 'package contents',
},
},
{
id: 'source-terminal',
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 5,
partial: false,
author: 'system',
role: 'system',
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'runtime_interrupted' } },
},
];
for (const event of sourceEvents) {
await runStore.appendRuntimeEvent(session.id, sourceRunId, event);
}
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: ['Read'],
});
assert.strictEqual(plan.disposition, 'continue');
if (!plan.continuation) throw new Error('expected continuation');
const movedCwd = '/fresh-sandbox/repo';
await store.updateHeader(session.id, { cwd: movedCwd });
const sessionEvents = await collectSessionEvents(
manager.resumeSafeBoundaryContinuation(plan.continuation),
);
assert.deepStrictEqual(
sessionEvents.map((event) => event.type),
['text_complete', 'complete'],
);
const continuationRun = await readInvocation(runStore, session.id, plan.continuation.runId);
assert.strictEqual(continuationRun.invocationId, plan.continuation.invocationId);
assert.strictEqual(continuationRun.turnId, plan.continuation.turnId);
assert.strictEqual(continuationRun.opening.lineage?.parentRunId, sourceRunId);
assert.strictEqual(continuationRun.opening.lineage?.parentTurnId, sourceTurnId);
assert.strictEqual(continuationRun.opening.configuration.cwd, movedCwd);
assert.strictEqual(runtimeInvocationOutcome(continuationRun), 'completed');
assert.partialDeepStrictEqual(continuationRun.opening.route, {
provenance: 'runtime',
providerStateIdentity,
});
assert.partialDeepStrictEqual(continuationRun.opening.configuration, {
orchestrationMode: 'swarm',
orchestrationSource: 'turn_override',
agentSwarmAuthorization: 'turn_override',
toolMode: 'code_mode',
});
assert.strictEqual(backend?.sendInputs[0]?.toolMode, 'code_mode');
assert.deepStrictEqual(
backend?.sendInputs[0]?.runtimeContextInvocations?.map((invocation) => ({
runId: invocation.runId,
route: invocation.opening.route,
})),
[
{
runId: sourceRunId,
route: {
provenance: 'runtime',
backendKind: 'ai-sdk',
llmConnectionId: header.llmConnectionId,
llmConnectionSlug: header.llmConnectionSlug,
modelId: header.model,
providerStateIdentity,
},
},
],
);
const continuationEvents = await runStore.readRuntimeEvents(
session.id,
plan.continuation.runId,
);
assert.partialDeepStrictEqual(continuationEvents[0]?.actions?.continuationStart, {
protocol: 'continuation_start_v2',
claimId: plan.continuation.claimId,
boundaryDigest: plan.continuation.boundary?.manifestDigest,
immediateSource: {
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
// The opening fact is event 1 of the source invocation.
highWater: sourceEvents.length + 1,
prefixDigest: plan.continuation.boundary?.segments.at(-1)?.prefixDigest,
},
replayManifestDigest: plan.continuation.boundary?.manifestDigest,
providerProjectionVersion: 2,
providerReplayDigest: plan.continuation.providerReplayDigest,
});
assert.deepStrictEqual(continuationEvents[0]?.actions?.runtimeProtocol, {
toolBoundary: 't1_after_preflight_v1',
});
assert.strictEqual(continuationEvents[0]?.refs, undefined);
assert.strictEqual(
continuationEvents.some((event) => event.role === 'user'),
false,
);
assert.deepStrictEqual(
(await runStore.readRuntimeEvents(session.id, sourceRunId)).slice(1),
sourceEvents,
);
assert.deepStrictEqual(
lifecycleEvents.map((event) => event.type),
['plan_approved', 'execution_started', 'execution_completed'],
);
await collectSessionEvents(
manager.sendMessage(session.id, {
turnId: 'turn-after-continuation',
text: 'what happened after recovery?',
}),
);
const followUpContext = backend?.sendInputs.at(-1)?.runtimeContext ?? [];
assert.strictEqual(
followUpContext.some((event) => event.runId === plan.continuation?.runId),
true,
);
const followUpRun = (await runStore.listSessionInvocations(session.id)).find(
(runHeader) => runHeader.turnId === 'turn-after-continuation',
);
assert.partialDeepStrictEqual(followUpRun?.opening.route, {
provenance: 'runtime',
providerStateIdentity,
});
assert.equal(followUpRun?.opening.configuration.toolMode, 'direct');
assert.equal(backend?.sendInputs.at(-1)?.toolMode, 'direct');
});
test('authenticates the exact target-aware continuation projection that reaches the provider', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let providerRequest: unknown;
const model = new MockLanguageModelV4({
doStream: async (request) => {
providerRequest = request;
return {
stream: simulateReadableStream({
chunks: [
{ type: 'stream-start', warnings: [] },
{ type: 'text-start', id: 'text-1' },
{ type: 'text-delta', id: 'text-1', delta: 'continued' },
{ type: 'text-end', id: 'text-1' },
{
type: 'finish',
finishReason: { unified: 'stop', raw: 'stop' },
usage: {
inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 },
outputTokens: { total: 1, text: 1, reasoning: 0 },
},
},
] as LanguageModelV4StreamPart[],
initialDelayInMs: null,
chunkDelayInMs: null,
}),
};
},
});
backends.register('ai-sdk', (ctx) =>
createTestAiSdkBackend({
sessionId: ctx.sessionId,
header: ctx.header,
connection: {
slug: ctx.header.llmConnectionSlug,
providerType: 'anthropic',
defaultModel: ctx.header.model,
},
apiKey: 'sk-test',
modelId: ctx.header.model,
modelFactory: () => model,
tools: [
{
name: 'Read',
description: 'Read a file',
parameters: z.object({ path: z.string() }),
impl: async () => ({ ok: true }),
},
],
...(ctx.loadTurnRuntimeEvents ? { loadTurnRuntimeEvents: ctx.loadTurnRuntimeEvents } : {}),
newId: (() => {
let id = 0;
return () => `cross-route-backend-${++id}`;
})(),
now: nextNow(1),
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
toolBoundaryProtocol: 't1_after_preflight_v1',
backends,
childTools: [testTool('Read')],
inspectContinuationSafety: async () => ({
workspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: ['Read'],
}),
newId: nextId(),
now: nextNow(6_575),
});
const session = await manager.createSession(
makeInput({
llmConnectionId: 'connection-a',
llmConnectionSlug: 'anthropic-a',
model: 'claude-a',
permissionMode: 'bypass',
}),
);
const sourceRunId = 'source-run-cross-route';
const sourceInvocationId = 'source-invocation-cross-route';
const sourceTurnId = 'source-turn-cross-route';
// The source run states its own terminal event below.
await seedInvocationOpening(runStore, {
runId: sourceRunId,
invocationId: sourceInvocationId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
failureClass: 'runtime_interrupted',
backendKind: 'ai-sdk',
llmConnectionId: 'connection-a',
llmConnectionSlug: 'anthropic-a',
modelId: 'claude-a',
cwd: '/tmp/cwd',
workspaceIdentity: 'workspace-1',
permissionMode: 'bypass',
orchestrationMode: 'default',
orchestrationSource: 'session',
toolMode: 'code_mode',
createdAt: 1,
updatedAt: 5,
completedAt: 5,
});
const sourceEvents: RuntimeEvent[] = [
{
id: 'cross-route-user',
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 1,
partial: false,
author: 'user',
role: 'user',
content: { kind: 'text', text: 'continue across routes' },
},
{
id: 'cross-route-thinking',
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 2,
partial: false,
author: 'agent',
role: 'model',
content: {
kind: 'thinking',
text: 'source-only reasoning',
signature: 'source-only-signature',
},
refs: { stepId: 'cross-route-step' },
},
{
id: 'cross-route-tool-call',
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 3,
partial: false,
author: 'agent',
role: 'model',
content: {
kind: 'function_call',
id: 'cross-route-read',
name: 'Read',
args: { path: 'package.json' },
},
refs: { stepId: 'cross-route-step' },
},
{
id: 'cross-route-tool-result',
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 4,
partial: false,
author: 'tool',
role: 'tool',
content: {
kind: 'function_response',
id: 'cross-route-read',
name: 'Read',
result: 'package contents',
},
},
{
id: 'cross-route-terminal',
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 5,
partial: false,
author: 'system',
role: 'system',
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'runtime_interrupted' } },
},
];
for (const event of sourceEvents) {
await runStore.appendRuntimeEvent(session.id, sourceRunId, event);
}
const planInput = {
sourceRunId,
currentCwd: '/tmp/cwd',
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true as const,
availableToolNames: ['Read'],
};
const stalePlan = await manager.planSafeBoundaryContinuation(session.id, planInput);
assert.strictEqual(stalePlan.disposition, 'continue');
if (!stalePlan.continuation) throw new Error('expected stale continuation');
const staleContinuation = stalePlan.continuation;
await store.updateHeader(session.id, {
llmConnectionId: 'connection-b',
llmConnectionSlug: 'anthropic-b',
model: 'claude-b',
});
await assert.rejects(
() => collectSessionEvents(manager.resumeSafeBoundaryContinuation(staleContinuation)),
/replay changed after planning/,
);
assert.strictEqual(providerRequest, undefined);
const sameProjectionStalePlan = await manager.planSafeBoundaryContinuation(
session.id,
planInput,
);
assert.strictEqual(sameProjectionStalePlan.disposition, 'continue');
if (!sameProjectionStalePlan.continuation) {
throw new Error('expected same-projection stale continuation');
}
await store.updateHeader(session.id, {
llmConnectionId: 'connection-c',
llmConnectionSlug: 'anthropic-c',
model: 'claude-c',
});
await assert.rejects(
() =>
collectSessionEvents(
manager.resumeSafeBoundaryContinuation(sameProjectionStalePlan.continuation!),
),
/replay changed after planning/,
);
assert.strictEqual(providerRequest, undefined);
const plan = await manager.planSafeBoundaryContinuation(session.id, planInput);
assert.strictEqual(plan.disposition, 'continue');
if (!plan.continuation) throw new Error('expected continuation');
const sessionEvents = await collectSessionEvents(
manager.resumeSafeBoundaryContinuation(plan.continuation),
);
assert.ok(providerRequest, JSON.stringify(sessionEvents));
const promptJson = JSON.stringify(providerRequest);
assert.doesNotMatch(promptJson, /source-only reasoning/);
assert.doesNotMatch(promptJson, /source-only-signature/);
assert.match(promptJson, /continue across routes/, promptJson);
assert.match(promptJson, /cross-route-read/);
assert.match(promptJson, /package contents/);
assert.notEqual(
plan.continuation.providerReplayDigest,
sameProjectionStalePlan.continuation.providerReplayDigest,
);
const continuationEvents = await runStore.readRuntimeEvents(
session.id,
plan.continuation.runId,
);
assert.strictEqual(
continuationEvents[0]?.actions?.continuationStart?.providerReplayDigest,
plan.continuation.providerReplayDigest,
);
});
test('strict backend refresh settles after an active continuation finalizes', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const turnStarted = makeGate();
const sendGate = makeGate();
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) =>
new ActiveTurnBackend(ctx, {
turnStarted,
sendGate,
compactCalls: [],
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: inspectStableContinuationSafety,
newId: nextId(),
now: nextNow(6_558),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'stopped-source-run';
const sourceTurnId = 'stopped-source-turn';
const sourceInvocationId = 'stopped-source-invocation';
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
invocationId: sourceInvocationId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
failureClass: 'runtime_interrupted',
cwd: header.cwd,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
}),
[
runtimeEvent({
id: 'stopped-source-user',
invocationId: sourceInvocationId,
sessionId: session.id,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue until stopped' },
}),
runtimeEvent({
id: 'stopped-source-terminal',
invocationId: sourceInvocationId,
sessionId: session.id,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'runtime_interrupted' } },
}),
],
);
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
if (!plan.continuation) throw new Error('expected continuation');
const execution = collectSessionEvents(
manager.resumeSafeBoundaryContinuation(plan.continuation),
);
await turnStarted.promise;
let refreshSettled = false;
const refresh = manager.refreshIdleBackends();
void refresh.finally(() => {
refreshSettled = true;
});
await Promise.resolve();
assert.strictEqual(refreshSettled, false);
await manager.stopSession(session.id, { source: 'stop_button' });
sendGate.release();
await execution;
await refresh;
assert.strictEqual(store.disposeCount, 1);
const cachedMessages = await manager.getMessages(session.id);
assert.partialDeepStrictEqual(
cachedMessages
.filter(
(message) =>
message.type === 'turn_state' && message.turnId === plan.continuation?.turnId,
)
.at(-1),
{
type: 'turn_state',
status: 'aborted',
abortSource: 'renderer.stop_button',
parentTurnId: sourceTurnId,
},
);
});
test('parks repeated planning after the source run already produced a continuation', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new FinalTextTestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: inspectStableContinuationSafety,
newId: nextId(),
now: nextNow(6_565),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run-idempotent';
const sourceTurnId = 'source-turn-idempotent';
const sourceInvocationId = 'source-invocation-idempotent';
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
cwd: header.cwd,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: 'source-user-idempotent',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue once' },
}),
runtimeEvent({
id: 'source-terminal-idempotent',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
const firstPlan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
if (!firstPlan.continuation) throw new Error('expected first continuation');
await collectSessionEvents(manager.resumeSafeBoundaryContinuation(firstPlan.continuation));
const repeatedPlan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
assert.strictEqual(repeatedPlan.disposition, 'park');
assert.deepStrictEqual(repeatedPlan.rejectionReasons, ['continuation_already_exists']);
const targetRunId = firstPlan.continuation.runId;
const targetRun = await readInvocation(runStore, session.id, targetRunId);
runStore.seedRuntimeEvent(
session.id,
targetRunId,
runtimeEvent({
id: 'post-terminal-continuation-output',
invocationId: targetRun.invocationId,
runId: targetRun.runId,
sessionId: targetRun.sessionId,
turnId: targetRun.turnId,
ts: (targetRun.terminalEvent?.ts ?? targetRun.openedAt) + 1,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'must not follow a terminal fact' },
}),
);
const postTerminalMismatch = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
assert.deepStrictEqual(postTerminalMismatch.rejectionReasons, [
'continuation_claim_repair_required',
]);
});
test('rejects continuation while a normal turn is still registering', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new FinalTextTestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: inspectStableContinuationSafety,
newId: nextId(),
now: nextNow(6_575),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: 'race-source-run',
invocationId: 'race-source-invocation',
sessionId: session.id,
turnId: 'race-source-turn',
status: 'failed',
failureClass: 'runtime_interrupted',
cwd: header.cwd,
workspaceIdentity: 'workspace-1',
createdAt: 1,
updatedAt: 2,
completedAt: 2,
}),
[
runtimeEvent({
id: 'race-source-user',
invocationId: 'race-source-invocation',
sessionId: session.id,
runId: 'race-source-run',
turnId: 'race-source-turn',
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue safely' },
}),
runtimeEvent({
id: 'race-source-terminal',
invocationId: 'race-source-invocation',
sessionId: session.id,
runId: 'race-source-run',
turnId: 'race-source-turn',
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'runtime_interrupted' } },
}),
],
);
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId: 'race-source-run',
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
if (!plan.continuation) throw new Error('expected continuation');
const readStarted = makeGate();
const releaseRead = makeGate();
store.nextReadHeaderGate = { started: readStarted, release: releaseRead };
const pendingTurn = manager
.sendMessage(session.id, {
turnId: 'turn-still-registering',
text: 'new work',
})
[Symbol.asyncIterator]();
const pendingFirst = pendingTurn.next();
await readStarted.promise;
await expectRejects(
collectSessionEvents(manager.resumeSafeBoundaryContinuation(plan.continuation)),
/another run is active/,
);
releaseRead.release();
await pendingFirst;
while (!(await pendingTurn.next()).done) {}
const continuationReadStarted = makeGate();
const releaseContinuationRead = makeGate();
store.nextReadHeaderGate = {
started: continuationReadStarted,
release: releaseContinuationRead,
};
const pendingContinuation = manager
.resumeSafeBoundaryContinuation(plan.continuation)
[Symbol.asyncIterator]();
const continuationFirst = pendingContinuation.next();
await continuationReadStarted.promise;
await expectRejects(
collectSessionEvents(
manager.sendMessage(session.id, {
turnId: 'turn-racing-continuation',
text: 'must not race',
}),
),
/runtime continuation is being claimed/,
);
releaseContinuationRead.release();
await continuationFirst;
while (!(await pendingContinuation.next()).done) {}
});
test('rejects a stale second plan after another continuation claims the same source', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backendCalls = 0;
backends.register(
'ai-sdk',
(ctx) =>
new CountingFinalTextBackend(ctx, () => {
backendCalls += 1;
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: inspectStableContinuationSafety,
newId: nextId(),
now: nextNow(6_570),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run-stale-plan';
const sourceTurnId = 'source-turn-stale-plan';
const sourceInvocationId = 'source-invocation-stale-plan';
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
cwd: header.cwd,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: 'source-user-stale-plan',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue exactly once' },
}),
runtimeEvent({
id: 'source-terminal-stale-plan',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
const planInput = {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [] as string[],
};
const firstPlan = await manager.planSafeBoundaryContinuation(session.id, planInput);
const stalePlan = await manager.planSafeBoundaryContinuation(session.id, planInput);
if (!firstPlan.continuation || !stalePlan.continuation) {
throw new Error('expected two pre-claim continuation plans');
}
await collectSessionEvents(manager.resumeSafeBoundaryContinuation(firstPlan.continuation));
await expectRejects(
collectSessionEvents(manager.resumeSafeBoundaryContinuation(stalePlan.continuation)),
/boundary is already claimed/i,
);
assert.strictEqual(backendCalls, 1);
});
test('serializes concurrent continuation claims for the same source boundary', async () => {
const store = new MemorySessionStore();
const runStore = new ContinuationClaimBarrierRunStore();
const backends = new BackendRegistry();
let backendCalls = 0;
backends.register(
'ai-sdk',
(ctx) =>
new CountingFinalTextBackend(ctx, () => {
backendCalls += 1;
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: inspectStableContinuationSafety,
newId: nextId(),
now: nextNow(6_572),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run-concurrent-claim';
const sourceTurnId = 'source-turn-concurrent-claim';
const sourceInvocationId = 'source-invocation-concurrent-claim';
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
cwd: header.cwd,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: 'source-user-concurrent-claim',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue once under concurrency' },
}),
runtimeEvent({
id: 'source-terminal-concurrent-claim',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
const planInput = {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [] as string[],
};
const firstPlan = await manager.planSafeBoundaryContinuation(session.id, planInput);
const secondPlan = await manager.planSafeBoundaryContinuation(session.id, planInput);
if (!firstPlan.continuation || !secondPlan.continuation) {
throw new Error('expected two pre-claim continuation plans');
}
runStore.armContinuationClaimBarrier();
const firstExecution = collectSessionEvents(
manager.resumeSafeBoundaryContinuation(firstPlan.continuation),
);
await runStore.waitForContinuationClaimRead();
const secondResult = await Promise.allSettled([
collectSessionEvents(manager.resumeSafeBoundaryContinuation(secondPlan.continuation)),
]);
runStore.releaseContinuationClaimRead();
const firstResult = await Promise.allSettled([firstExecution]);
const results = [...firstResult, ...secondResult];
assert.strictEqual(results.filter((result) => result.status === 'fulfilled').length, 1);
assert.strictEqual(results.filter((result) => result.status === 'rejected').length, 1);
assert.strictEqual(backendCalls, 1);
});
test('does not call the backend and claim recovery closes a continuation-start persistence failure', async () => {
const store = new MemorySessionStore();
// The source invocation's opening and its two events are the three appends
// that must succeed; the continuation-start is the one that fails.
const runStore = new MemoryAgentRunStore({ failRuntimeEventAppendAfter: 3 });
const backends = new BackendRegistry();
let backendCalls = 0;
backends.register(
'ai-sdk',
(ctx) =>
new CountingFinalTextBackend(ctx, () => {
backendCalls += 1;
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: inspectStableContinuationSafety,
newId: nextId(),
now: nextNow(6_575),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run-write-failure';
const sourceTurnId = 'source-turn-write-failure';
const sourceInvocationId = 'source-invocation-write-failure';
// The source states its own terminal event below.
await seedInvocationOpening(runStore, {
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
backendKind: header.backend,
llmConnectionSlug: header.llmConnectionSlug,
modelId: header.model,
cwd: header.cwd,
permissionMode: header.permissionMode,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
});
const sourceEvents: RuntimeEvent[] = [
{
id: 'source-user-write-failure',
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 1,
partial: false,
author: 'user',
role: 'user',
content: { kind: 'text', text: 'continue safely' },
},
{
id: 'source-terminal-write-failure',
sessionId: session.id,
invocationId: sourceInvocationId,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 2,
partial: false,
author: 'system',
role: 'system',
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
},
];
for (const event of sourceEvents) {
await runStore.appendRuntimeEvent(session.id, sourceRunId, event);
}
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
if (!plan.continuation) throw new Error('expected continuation');
await expectRejects(
collectSessionEvents(manager.resumeSafeBoundaryContinuation(plan.continuation)),
/runtime event append failed/,
);
assert.strictEqual(backendCalls, 0);
// The continuation-start never reached the ledger, so nothing opened the
// target invocation and the inventory does not know it.
await assert.rejects(readInvocation(runStore, session.id, plan.continuation.runId));
assert.deepStrictEqual(
await runStore.readRuntimeEvents(session.id, plan.continuation.runId),
[],
);
await manager.recoverInterruptedSessions();
const recoveredRun = await readInvocation(runStore, session.id, plan.continuation.runId);
const recoveredEvents = await runStore.readRuntimeEvents(session.id, plan.continuation.runId);
assert.strictEqual(runtimeInvocationOutcome(recoveredRun), 'failed');
assert.strictEqual(
runtimeInvocationFailureClass(recoveredRun),
'continuation_abandoned_before_provider_dispatch',
);
assert.strictEqual(recoveredEvents.length, 2);
assert.strictEqual(
recoveredEvents[0]?.actions?.continuationStart?.claimId,
plan.continuation.claimId,
);
assert.strictEqual(recoveredEvents.filter(isTerminalRuntimeEvent).length, 1);
assert.partialDeepStrictEqual(recoveredEvents.at(-1)?.actions?.stateDelta, {
recovered: true,
recoveryReason: 'continuation_abandoned_before_provider_dispatch',
failureClass: 'continuation_abandoned_before_provider_dispatch',
});
});
test('stop after the durable continuation-start commit fences backend dispatch', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const continuationCommitted = makeGate();
const releaseContinuationCommit = makeGate();
let backendCalls = 0;
backends.register(
'ai-sdk',
(ctx) =>
new CountingFinalTextBackend(ctx, () => {
backendCalls += 1;
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: inspectStableContinuationSafety,
continuationFailpoint: async (point) => {
if (point !== 'after_continuation_start_committed') return;
continuationCommitted.release();
await releaseContinuationCommit.promise;
},
newId: nextId(),
now: nextNow(6_576),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run-post-commit-stop';
const sourceTurnId = 'source-turn-post-commit-stop';
const sourceInvocationId = 'source-invocation-post-commit-stop';
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
cwd: header.cwd,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: 'source-user-post-commit-stop',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue only if the Run still owns dispatch' },
}),
runtimeEvent({
id: 'source-terminal-post-commit-stop',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
if (!plan.continuation) throw new Error('expected continuation');
const execution = collectSessionEvents(
manager.resumeSafeBoundaryContinuation(plan.continuation),
);
await continuationCommitted.promise;
const stop = manager.stopSession(session.id, { source: 'stop_button' });
releaseContinuationCommit.release();
await stop;
await execution.catch(() => []);
assert.strictEqual(backendCalls, 0);
const targetRun = await readInvocation(runStore, session.id, plan.continuation.runId);
assert.strictEqual(runtimeInvocationOutcome(targetRun), 'cancelled');
const targetEvents = await runStore.readRuntimeEvents(session.id, plan.continuation.runId);
assert.strictEqual(
targetEvents.filter((event) => event.actions?.continuationStart !== undefined).length,
1,
);
assert.strictEqual(targetEvents.filter(isTerminalRuntimeEvent).length, 1);
});
test('does not call the backend when the durable continuation claim cannot be created', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore({ failContinuationCreate: true });
const backends = new BackendRegistry();
let backendCalls = 0;
backends.register(
'ai-sdk',
(ctx) =>
new CountingFinalTextBackend(ctx, () => {
backendCalls += 1;
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: inspectStableContinuationSafety,
newId: nextId(),
now: nextNow(6_577),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run-claim-create-failure';
const sourceTurnId = 'source-turn-claim-create-failure';
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
cwd: header.cwd,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: 'source-user-claim-create-failure',
invocationId: 'source-invocation-claim-create-failure',
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue only with a durable claim' },
}),
runtimeEvent({
id: 'source-terminal-claim-create-failure',
invocationId: 'source-invocation-claim-create-failure',
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
if (!plan.continuation) throw new Error('expected continuation');
await expectRejects(
collectSessionEvents(manager.resumeSafeBoundaryContinuation(plan.continuation)),
/continuation claim create failed/,
);
assert.strictEqual(backendCalls, 0);
await expectRejects(
readInvocation(runStore, session.id, plan.continuation.runId),
/unknown run/i,
);
});
test('refuses a planned continuation whose source ledger changed after planning', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backendCalls = 0;
backends.register(
'ai-sdk',
(ctx) =>
new CountingFinalTextBackend(ctx, () => {
backendCalls += 1;
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: inspectStableContinuationSafety,
newId: nextId(),
now: nextNow(6_590),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run-race';
const sourceTurnId = 'source-turn-race';
const sourceInvocationId = 'source-invocation-race';
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
cwd: header.cwd,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: 'source-user-race',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue safely' },
}),
runtimeEvent({
id: 'source-terminal-race',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
if (!plan.continuation) throw new Error('expected continuation');
runStore.seedRuntimeEvent(
session.id,
sourceRunId,
runtimeEvent({
id: 'source-second-terminal-race',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 3,
status: 'completed',
actions: { endInvocation: true },
}),
);
// The second ending moved the source boundary the plan was cut against, so
// the plan no longer describes the ledger it would continue from.
await expectRejects(
collectSessionEvents(manager.resumeSafeBoundaryContinuation(plan.continuation)),
/continuation boundary changed/i,
);
assert.strictEqual(backendCalls, 0);
});
test('startup recovery retries claim-only terminal projection without dispatching the provider', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backendCalls = 0;
let failOnce = true;
backends.register(
'ai-sdk',
(ctx) =>
new CountingFinalTextBackend(ctx, () => {
backendCalls += 1;
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: inspectStableContinuationSafety,
continuationFailpoint: async (point) => {
if (point === 'after_continuation_claim_committed' && failOnce) {
failOnce = false;
throw new Error('simulated claim-only crash');
}
},
newId: nextId(),
now: nextNow(6_591),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run-claim-only-crash';
const sourceTurnId = 'source-turn-claim-only-crash';
const sourceInvocationId = 'source-invocation-claim-only-crash';
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
invocationId: sourceInvocationId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
cwd: header.cwd,
workspaceIdentity: 'workspace-1',
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: 'source-user-claim-only-crash',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue after a claim-only crash' },
}),
runtimeEvent({
id: 'source-terminal-claim-only-crash',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
if (!plan.continuation) throw new Error('expected continuation');
await expectRejects(
collectSessionEvents(manager.resumeSafeBoundaryContinuation(plan.continuation)),
/simulated claim-only crash/,
);
await expectRejects(
readInvocation(runStore, session.id, plan.continuation.runId),
/unknown run/i,
);
assert.strictEqual(backendCalls, 0);
// One pass finishes the claim: the continuation-start the crash never
// committed, and the terminal event that ends a run nothing will dispatch.
// There is no second record left to settle afterwards.
assert.ok((await manager.recoverInterruptedSessions()).includes(session.id));
const repairedRun = await readInvocation(runStore, session.id, plan.continuation.runId);
assert.strictEqual(runtimeInvocationOutcome(repairedRun), 'failed');
assert.strictEqual(
runtimeInvocationFailureClass(repairedRun),
'continuation_abandoned_before_provider_dispatch',
);
assert.partialDeepStrictEqual(repairedRun.opening.source, {
kind: 'continuation',
sourceRunId,
claimId: plan.continuation.claimId,
});
const repairedEvents = await runStore.readRuntimeEvents(session.id, plan.continuation.runId);
assert.strictEqual(repairedEvents.length, 2);
assert.strictEqual(
repairedEvents[0]?.actions?.continuationStart?.claimId,
plan.continuation.claimId,
);
assert.strictEqual(repairedEvents[1]?.status, 'failed');
assert.strictEqual(backendCalls, 0);
const snapshot = JSON.stringify({
run: repairedRun,
events: repairedEvents,
});
await manager.recoverInterruptedSessions();
assert.strictEqual(
JSON.stringify({
run: await readInvocation(runStore, session.id, plan.continuation.runId),
events: await runStore.readRuntimeEvents(session.id, plan.continuation.runId),
}),
snapshot,
);
assert.strictEqual(backendCalls, 0);
const nextPlan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId: plan.continuation.runId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
assert.strictEqual(nextPlan.disposition, 'continue');
});
test('rejects continuation when the authoritative workspace identity changes after planning', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backendCalls = 0;
let workspaceIdentity = 'workspace-1';
const lifecycleEvents: Array<{ type: string; errorClass?: string }> = [];
backends.register(
'ai-sdk',
(ctx) =>
new CountingFinalTextBackend(ctx, () => {
backendCalls += 1;
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: async () => ({
workspaceIdentity,
backgroundOperationsSettled: true,
availableToolNames: [],
}),
newId: nextId(),
now: nextNow(6_595),
onContinuationLifecycleEvent: (event) => {
lifecycleEvents.push(event);
},
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run-workspace-race';
const sourceTurnId = 'source-turn-workspace-race';
const sourceInvocationId = 'source-invocation-workspace-race';
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
cwd: header.cwd,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: 'source-user-workspace-race',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue in the same workspace' },
}),
runtimeEvent({
id: 'source-terminal-workspace-race',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: workspaceIdentity,
currentWorkspaceIdentity: workspaceIdentity,
backgroundOperationsSettled: true,
availableToolNames: [],
});
if (!plan.continuation) throw new Error('expected continuation');
workspaceIdentity = 'workspace-2';
await expectRejects(
collectSessionEvents(manager.resumeSafeBoundaryContinuation(plan.continuation)),
/workspace identity changed/i,
);
assert.strictEqual(backendCalls, 0);
assert.deepStrictEqual(lifecycleEvents.at(-1), {
type: 'execution_failed',
sessionId: session.id,
sourceRunId,
targetRunId: plan.continuation.runId,
errorClass: 'workspace_identity_changed',
});
});
test('requires exact tool-catalog equality between continuation planning and execution', async () => {
const scenarios = [
{ name: 'added', planned: ['Write'], current: ['Read', 'Write'] },
{ name: 'removed', planned: ['Read', 'Write'], current: ['Write'] },
{ name: 'replaced', planned: ['Write'], current: ['Edit'] },
] as const;
for (const scenario of scenarios) {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backendCalls = 0;
let availableToolNames: readonly string[] = scenario.planned;
backends.register(
'ai-sdk',
(ctx) =>
new CountingFinalTextBackend(ctx, () => {
backendCalls += 1;
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: async () => ({
workspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames,
}),
newId: nextId(),
now: nextNow(6_597),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = `source-run-tool-catalog-${scenario.name}`;
const sourceTurnId = `source-turn-tool-catalog-${scenario.name}`;
const sourceInvocationId = `source-invocation-tool-catalog-${scenario.name}`;
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
cwd: header.cwd,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: `source-user-tool-catalog-${scenario.name}`,
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue with the same tools' },
}),
runtimeEvent({
id: `source-terminal-tool-catalog-${scenario.name}`,
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: scenario.planned,
});
if (!plan.continuation) throw new Error(`expected ${scenario.name} continuation`);
availableToolNames = scenario.current;
await expectRejects(
collectSessionEvents(manager.resumeSafeBoundaryContinuation(plan.continuation)),
/tool catalog changed/i,
);
assert.strictEqual(backendCalls, 0);
}
});
test('revalidates continuation safety inside the backend activation barrier', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backendCalls = 0;
let availableToolNames: readonly string[] = ['Write'];
backends.register(
'ai-sdk',
(ctx) =>
new CountingFinalTextBackend(ctx, () => {
backendCalls += 1;
}),
);
const newId = nextId();
const now = nextNow(6_599);
const runtimeKernel = new RuntimeKernel({
store,
runStore,
runtimeEventStore: runStore,
backends,
inspectContinuationSafety: async () => ({
workspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames,
}),
runBackendActivation: async (operation) => {
availableToolNames = ['Read', 'Write'];
return operation();
},
newId,
now,
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
runtimeKernel,
inspectContinuationSafety: async () => ({
workspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames,
}),
newId,
now,
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run-activation-safety-race';
const sourceTurnId = 'source-turn-activation-safety-race';
const sourceInvocationId = 'source-invocation-activation-safety-race';
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
cwd: header.cwd,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: 'source-user-activation-safety-race',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue under one activation snapshot' },
}),
runtimeEvent({
id: 'source-terminal-activation-safety-race',
invocationId: sourceInvocationId,
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames,
});
if (!plan.continuation) throw new Error('expected continuation');
await expectRejects(
collectSessionEvents(manager.resumeSafeBoundaryContinuation(plan.continuation)),
/tool catalog changed/i,
);
assert.strictEqual(backendCalls, 0);
await expectRejects(
readInvocation(runStore, session.id, plan.continuation.runId),
/Unknown run/,
);
});
test('fails closed when continuation execution has no authoritative safety inspector', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backendCalls = 0;
backends.register(
'ai-sdk',
(ctx) =>
new CountingFinalTextBackend(ctx, () => {
backendCalls += 1;
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_600),
});
const session = await manager.createSession(makeInput());
const header = await store.readHeader(session.id);
const sourceRunId = 'source-run-no-safety-inspector';
const sourceTurnId = 'source-turn-no-safety-inspector';
await seedRuntimeRun(
runStore,
makeRunHeader({
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
status: 'failed',
cwd: header.cwd,
createdAt: 1,
updatedAt: 2,
completedAt: 2,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: 'source-user-no-safety-inspector',
invocationId: 'source-invocation-no-safety-inspector',
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'continue only with authoritative facts' },
}),
runtimeEvent({
id: 'source-terminal-no-safety-inspector',
invocationId: 'source-invocation-no-safety-inspector',
runId: sourceRunId,
sessionId: session.id,
turnId: sourceTurnId,
ts: 2,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
const plan = await manager.planSafeBoundaryContinuation(session.id, {
sourceRunId,
currentCwd: header.cwd,
sourceWorkspaceIdentity: 'workspace-1',
currentWorkspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [],
});
if (!plan.continuation) throw new Error('expected continuation');
await expectRejects(
collectSessionEvents(manager.resumeSafeBoundaryContinuation(plan.continuation)),
/safety inspector/i,
);
assert.strictEqual(backendCalls, 0);
});
test('sendMessage completes interrupted imported history beside native runs', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore({ failRuntimeEventAppendAfter: 3 });
const backends = new BackendRegistry();
let backend: TestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new TestBackend(ctx);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(7_025),
});
const session = await manager.createSession(makeInput());
await store.updateHeader(session.id, { transcriptLedgerVersion: 0 });
await store.appendMessages(session.id, [
{ type: 'user', id: 'imported-user-1', turnId: 'turn-1', ts: 101, text: 'First question' },
{
type: 'assistant',
id: 'imported-assistant-1',
turnId: 'turn-1',
ts: 102,
text: 'First answer',
modelId: 'external-model',
},
{
type: 'turn_state',
id: 'imported-state-1',
turnId: 'turn-1',
ts: 103,
status: 'completed',
},
{ type: 'user', id: 'imported-user-2', turnId: 'turn-2', ts: 104, text: 'Second question' },
{
type: 'assistant',
id: 'imported-assistant-2',
turnId: 'turn-2',
ts: 105,
text: 'Second answer',
modelId: 'external-model',
},
{
type: 'turn_state',
id: 'imported-state-2',
turnId: 'turn-2',
ts: 106,
status: 'completed',
},
]);
// The first conversion dies partway through. A staged import stays staged
// until one whole conversion lands, so the retry is another import — a live
// Turn is refused meanwhile, and the second pass re-derives the same event
// ids and appends only what the interrupted one never wrote.
await expectRejects(
manager.prepareImportedSessionHistory(session.id),
/runtime event append failed/,
);
await expectRejects(
drain(manager.sendMessage(session.id, { turnId: 'turn-early', text: 'Too early' })),
/history is still being prepared/,
);
await manager.prepareImportedSessionHistory(session.id);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'native-run',
invocationId: 'native-invocation',
turnId: 'turn-3',
status: 'completed',
createdAt: 10,
updatedAt: 12,
completedAt: 12,
}),
[
runtimeEvent({
id: 'native-user',
invocationId: 'native-invocation',
sessionId: session.id,
runId: 'native-run',
turnId: 'turn-3',
ts: 10,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'Native question' },
}),
runtimeEvent({
id: 'native-assistant',
invocationId: 'native-invocation',
sessionId: session.id,
runId: 'native-run',
turnId: 'turn-3',
ts: 11,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'Native answer' },
}),
runtimeEvent({
id: 'native-complete',
invocationId: 'native-invocation',
sessionId: session.id,
runId: 'native-run',
turnId: 'turn-3',
ts: 12,
status: 'completed',
actions: { endInvocation: true },
}),
],
);
await drain(manager.sendMessage(session.id, { turnId: 'turn-4', text: 'Continue' }));
assert.deepStrictEqual(
backend?.sendInputs[0]?.runtimeContext?.flatMap((event) =>
event.content?.kind === 'text' ? [event.content.text] : [],
),
[
'First question',
'First answer',
'Second question',
'Second answer',
'Native question',
'Native answer',
],
);
assert.strictEqual((await store.readHeader(session.id)).transcriptLedgerVersion, 1);
const repairedRuns = await runStore.listSessionInvocations(session.id);
assert.strictEqual(repairedRuns.filter((run) => run.turnId === 'turn-1').length, 1);
assert.strictEqual(repairedRuns.filter((run) => run.turnId === 'turn-2').length, 1);
});
test('sendMessage admits assistant-first repaired history at a user boundary', async () => {
{
const externalOrigin = { adapterId: 'opencode', sourceSessionId: 'assistant-first' };
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const model = new MockLanguageModelV4({
doStream: async () => ({
stream: simulateReadableStream({
chunks: [
{ type: 'stream-start', warnings: [] },
{ type: 'text-start', id: 'text-1' },
{ type: 'text-delta', id: 'text-1', delta: 'continued' },
{ type: 'text-end', id: 'text-1' },
{
type: 'finish',
finishReason: { unified: 'stop', raw: 'stop' },
usage: {
inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 },
outputTokens: { total: 1, text: 1, reasoning: 0 },
},
},
] as LanguageModelV4StreamPart[],
initialDelayInMs: null,
chunkDelayInMs: null,
}),
}),
});
backends.register('ai-sdk', (ctx) =>
createTestAiSdkBackend({
sessionId: ctx.sessionId,
header: ctx.header,
connection: {
slug: 'anthropic',
providerType: 'anthropic',
defaultModel: 'claude-test',
},
apiKey: 'sk-test',
modelId: 'claude-test',
modelFactory: () => model,
tools: [],
loadTurnRuntimeEvents: ctx.loadTurnRuntimeEvents,
newId: nextId(),
now: nextNow(1),
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(7_035),
});
const session = await manager.createSession(
makeInput({
llmConnectionSlug: 'anthropic',
model: 'claude-test',
permissionMode: 'bypass',
}),
);
await store.updateHeader(session.id, {
createdAt: 100,
transcriptLedgerVersion: 0,
...(externalOrigin ? { externalOrigin } : {}),
});
await store.appendMessages(session.id, [
{
type: 'assistant',
id: 'imported-assistant',
turnId: 'imported-turn',
ts: 1,
text: 'Imported opening reply',
modelId: 'external-model',
},
{
type: 'turn_state',
id: 'imported-state',
turnId: 'imported-turn',
ts: 2,
status: 'completed',
},
{
type: 'user',
id: 'imported-user',
turnId: 'imported-user-turn',
ts: 3,
text: 'Imported question',
},
{
type: 'assistant',
id: 'imported-answer',
turnId: 'imported-user-turn',
ts: 4,
text: 'Imported answer',
modelId: 'external-model',
},
{
type: 'turn_state',
id: 'imported-user-state',
turnId: 'imported-user-turn',
ts: 5,
status: 'completed',
},
]);
await manager.prepareImportedSessionHistory(session.id);
await drain(manager.sendMessage(session.id, { turnId: 'continued-turn', text: 'Continue' }));
assert.deepStrictEqual(
model.doStreamCalls[0]?.prompt.map((message) => message.role),
['user', 'assistant', 'user'],
);
assert.doesNotMatch(JSON.stringify(model.doStreamCalls[0]?.prompt), /Imported opening reply/);
assert.strictEqual(
(await manager.getMessages(session.id)).some(
(message) => message.type === 'assistant' && message.text === 'Imported opening reply',
),
true,
);
}
});
test('sendMessage rejects an imported Session while its history is staging', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(7_040),
});
const session = await manager.createSession(makeInput());
await store.updateHeader(session.id, { transcriptLedgerVersion: 0 });
await expectRejects(
drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'Too early' })),
/history is still being prepared/,
);
assert.strictEqual((await runStore.listSessionInvocations(session.id)).length, 0);
});
test('sendMessage replays a prior run left non-terminal by an unanswered interaction', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backend: TestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new TestBackend(ctx);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(7_075),
});
const session = await manager.createSession(makeInput());
// A run parked on AskUserQuestion and then stopped: the header never left
// `waiting_for_user` and the ledger never received a terminal fact. Its
// turn is still conversation the model must see.
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
status: 'waiting_for_user',
createdAt: 100,
updatedAt: 102,
}),
[
runtimeEvent({
id: 'rt-user',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 101,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'prior question' },
}),
runtimeEvent({
id: 'rt-assistant',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 102,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'prior answer' },
}),
],
);
await drain(manager.sendMessage(session.id, { turnId: 'turn-2', text: 'follow up' }));
assert.deepStrictEqual(
backend?.sendInputs[0]?.runtimeContext?.map((event) => event.id),
['run-1-invocation-opened', 'rt-user', 'rt-assistant'],
);
});
test('RuntimeReadModel projects messages turns and terminal facts without SessionStore messages', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const session = await store.create(makeInput());
const seeded = await seedRuntimeReadTurn({
store,
runStore,
sessionId: session.id,
turnId: 'turn-1',
runId: 'run-1',
userText: 'runtime question',
assistantText: 'runtime answer',
legacyIdPrefix: 'cache',
});
store.failReadMessagesFor.add(session.id);
const view = await new RuntimeReadModel({
runtimeEventStore: runStore,
}).getSessionView(session.id);
assert.deepStrictEqual(view.messages, seeded.projectedMessages);
assert.deepStrictEqual(view.turns, [
{
turnId: 'turn-1',
status: 'completed',
statusSource: 'recorded',
},
]);
assert.deepStrictEqual(
view.terminalFacts.map((fact) => fact.runStatus),
['completed'],
);
});
test('SessionManager projects hosted permission details from the canonical outcome', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const header = makeRunHeader({ status: 'completed' });
await store.create(makeInput());
await seedCanonicalPermissionRun(runStore, header);
const canonicalPermissionOutcomes: CanonicalPermissionOutcomeReader = {
readPermissionOutcome: async (requestId) =>
canonicalPermissionRecord(header, {
requestId,
outcome: {
kind: 'permission_answer' as const,
decision: 'allow' as const,
rememberForTurn: true,
reviewer: 'auto_review' as const,
rationale: 'The requested write is limited to the reviewed path.',
riskLevel: 'medium' as const,
committedAt: 125,
},
}),
};
const backends = new BackendRegistry();
const interactionAuthority: RuntimeInteractionAuthority = {
bindRun: () => {
throw new Error('read-only test authority cannot bind a Run');
},
};
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(125),
interactionAuthority,
canonicalPermissionOutcomes,
});
const messages = await manager.getMessages(header.sessionId);
assert.deepStrictEqual(
messages.find((message) => message.type === 'permission_decision'),
{
type: 'permission_decision',
id: 'request-canonical',
turnId: header.turnId,
ts: 125,
toolUseId: 'tool-canonical',
toolName: 'Write',
decision: 'allow',
rememberForTurn: true,
reviewer: 'auto_review',
rationale: 'The requested write is limited to the reviewed path.',
riskLevel: 'medium',
hint: 'write approval',
},
);
});
test('SessionManager joins a canonical hosted permission without a ledger request', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const header = makeRunHeader({ status: 'completed' });
await store.create(makeInput());
await seedCanonicalPermissionRun(runStore, header, false);
const canonicalPermissionOutcomes: CanonicalPermissionOutcomeReader = {
readPermissionOutcome: async (requestId) =>
canonicalPermissionRecord(header, {
requestId,
outcome: {
kind: 'permission_answer',
decision: 'allow',
rememberForTurn: true,
reviewer: 'auto_review',
committedAt: 125,
},
}),
};
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(125),
interactionAuthority: {
bindRun: () => {
throw new Error('read-only test authority cannot bind a Run');
},
},
canonicalPermissionOutcomes,
});
const messages = await manager.getMessages(header.sessionId);
assert.deepStrictEqual(
messages.find((message) => message.type === 'permission_decision'),
{
type: 'permission_decision',
id: 'request-canonical',
turnId: header.turnId,
ts: 125,
toolUseId: 'tool-canonical',
toolName: 'Write',
decision: 'allow',
rememberForTurn: true,
reviewer: 'auto_review',
},
);
});
test('RuntimeReadModel fails closed for a missing or mismatched hosted permission outcome', async () => {
const header = makeRunHeader({ status: 'completed' });
const canonical = canonicalPermissionRecord(header);
const outcomes: Array<CanonicalPermissionOutcomeRecord | undefined> = [
undefined,
{
...canonical,
sessionId: 'wrong-session',
outcome: {
kind: 'permission_answer' as const,
decision: 'deny' as const,
rememberForTurn: false as const,
reviewer: 'user' as const,
committedAt: 125,
},
},
];
for (const outcome of outcomes) {
const runStore = new MemoryAgentRunStore();
await seedCanonicalPermissionRun(runStore, header);
await assert.rejects(
new RuntimeReadModel({
runtimeEventStore: runStore,
canonicalPermissionOutcomes: {
readPermissionOutcome: async () => outcome,
},
}).getSessionView(header.sessionId),
(error: unknown) =>
error instanceof RuntimeReadModelError &&
error.diagnostics.some((diagnostic) => diagnostic.code === 'incomplete_event'),
);
}
});
test('RuntimeReadModel bounds concurrent hosted permission outcome reads', {
timeout: 2_000,
}, async (t) => {
const runStore = new MemoryAgentRunStore();
const header = makeRunHeader({ status: 'completed' });
const requestIds = Array.from({ length: 20 }, (_, index) => `request-${index}`);
await seedRuntimeRun(runStore, header, [
runtimeEvent({
id: 'permission-concurrency-user',
sessionId: header.sessionId,
runId: header.runId,
turnId: header.turnId,
ts: 99,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'approve the operations' },
}),
...requestIds.map((requestId, index) =>
runtimeEvent({
id: `permission-concurrency-${index}`,
sessionId: header.sessionId,
runId: header.runId,
turnId: header.turnId,
ts: 100 + index,
author: 'user',
actions: { permissionAnswerAccepted: { requestId } },
refs: { toolCallId: `tool-${requestId}` },
}),
),
runtimeEvent({
id: 'permission-concurrency-terminal',
sessionId: header.sessionId,
runId: header.runId,
turnId: header.turnId,
ts: 200,
status: 'completed',
actions: { endInvocation: true },
}),
]);
let activeReads = 0;
let maxActiveReads = 0;
let startedReads = 0;
let releaseReads!: () => void;
const readsReleased = new Promise<void>((resolve) => {
releaseReads = resolve;
});
t.after(() => releaseReads());
let initialWorkersStarted!: () => void;
const initialWorkers = new Promise<void>((resolve) => {
initialWorkersStarted = resolve;
});
const viewPromise = new RuntimeReadModel({
runtimeEventStore: runStore,
canonicalPermissionOutcomes: {
readPermissionOutcome: async (requestId) => {
activeReads += 1;
startedReads += 1;
maxActiveReads = Math.max(maxActiveReads, activeReads);
if (startedReads === 8) initialWorkersStarted();
await readsReleased;
activeReads -= 1;
const canonical = canonicalPermissionRecord(header);
return {
...canonical,
requestId,
request: {
...canonical.request,
toolUseId: `tool-${requestId}`,
},
};
},
},
}).getSessionView(header.sessionId);
await initialWorkers;
assert.strictEqual(startedReads, 8);
assert.strictEqual(maxActiveReads, 8);
releaseReads();
const view = await viewPromise;
assert.strictEqual(startedReads, requestIds.length);
assert.strictEqual(maxActiveReads, 8);
assert.strictEqual(
view.messages.filter((message) => message.type === 'permission_decision').length,
requestIds.length,
);
});
test('RuntimeReadModel excludes child runs from the default session transcript', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const session = await store.create(makeInput());
await seedRuntimeReadTurnWithHeader({
store,
runStore,
sessionId: session.id,
turnId: 'parent-turn',
runId: 'parent-run',
userText: 'parent question',
assistantText: 'parent answer',
legacyIdPrefix: 'parent',
header: {},
tsBase: 100,
});
await seedRuntimeReadTurnWithHeader({
store,
runStore,
sessionId: session.id,
turnId: 'child-turn',
runId: 'child-run',
userText: 'child prompt',
assistantText: 'child private answer',
legacyIdPrefix: 'child',
header: { parentRunId: 'parent-run', agentName: 'Researcher' },
tsBase: 200,
});
const view = await new RuntimeReadModel({
runtimeEventStore: runStore,
}).getSessionView(session.id);
assert.deepStrictEqual(
view.invocations.map((run) => run.runId),
['parent-run'],
);
assert.deepStrictEqual(
view.messages.map((message) => message.turnId),
['parent-turn', 'parent-turn', 'parent-turn'],
);
});
test('projection/cache mismatch does not override RuntimeEvent read output', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const manager = makeManagerForReadCutover(store, runStore);
const session = await manager.createSession(makeInput());
const legacyMessages: StoredMessage[] = [
{ type: 'user', id: 'legacy-user', turnId: 'turn-1', ts: 101, text: 'question' },
{
type: 'assistant',
id: 'legacy-assistant',
turnId: 'turn-1',
ts: 102,
text: 'legacy answer',
modelId: 'fake-model',
},
{
type: 'turn_state',
id: 'legacy-state',
turnId: 'turn-1',
ts: 103,
status: 'completed',
},
];
await store.appendMessages(session.id, legacyMessages);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
status: 'completed',
createdAt: 100,
updatedAt: 103,
completedAt: 103,
}),
[
runtimeEvent({
id: 'rt-user',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 101,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'question' },
}),
runtimeEvent({
id: 'rt-complete',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 103,
role: 'system',
author: 'system',
status: 'completed',
actions: { endInvocation: true },
}),
],
);
assert.deepStrictEqual(await manager.getMessages(session.id), [
{ type: 'user', id: 'rt-user', turnId: 'turn-1', ts: 101, text: 'question' },
{
type: 'turn_state',
id: 'rt-complete',
turnId: 'turn-1',
ts: 103,
status: 'completed',
},
]);
});
test('getMessages repairs missing failed header class from an existing terminal RuntimeEvent', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const manager = makeManagerForReadCutover(store, runStore);
const session = await manager.createSession(makeInput());
await store.appendMessages(session.id, [
{ type: 'user', id: 'legacy-user', turnId: 'turn-1', ts: 101, text: 'question' },
{
type: 'assistant',
id: 'legacy-assistant',
turnId: 'turn-1',
ts: 102,
text: 'answer',
modelId: 'fake-model',
},
]);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
status: 'failed',
failureClass: undefined,
createdAt: 100,
updatedAt: 103,
completedAt: 103,
}),
[
runtimeEvent({
id: 'rt-user',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 101,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'question' },
}),
runtimeEvent({
id: 'rt-assistant',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 102,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'answer' },
}),
runtimeEvent({
id: 'rt-failed',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 103,
role: 'system',
author: 'system',
status: 'failed',
content: {
kind: 'error',
code: 'tool_failed',
reason: 'tool_failed',
message: 'tool failed',
},
actions: { endInvocation: true, stateDelta: { failureClass: 'tool_failed' } },
}),
],
);
const messages = await manager.getMessages(session.id);
await manager.getMessages(session.id);
const repairedRun = await readInvocation(runStore, session.id, 'run-1');
const runtimeEvents = await runStore.readRuntimeEvents(session.id, 'run-1');
assert.strictEqual(runtimeInvocationFailureClass(repairedRun), 'tool_failed');
assert.deepStrictEqual(messages.at(-1), {
type: 'turn_state',
id: 'rt-failed',
turnId: 'turn-1',
ts: 103,
status: 'failed',
errorClass: 'tool_failed',
failureMessage: 'tool failed',
});
assert.strictEqual(runtimeEvents.filter((event) => event.status === 'failed').length, 1);
});
test('getMessages leaves a failed terminal RuntimeEvent that states no class alone', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const manager = makeManagerForReadCutover(store, runStore);
const session = await manager.createSession(makeInput());
await store.appendMessages(session.id, [
{ type: 'user', id: 'legacy-user', turnId: 'turn-1', ts: 101, text: 'question' },
{
type: 'assistant',
id: 'legacy-assistant',
turnId: 'turn-1',
ts: 102,
text: 'answer',
modelId: 'fake-model',
},
]);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
status: 'failed',
failureClass: undefined,
createdAt: 100,
updatedAt: 103,
completedAt: 103,
}),
[
runtimeEvent({
id: 'rt-user',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 101,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'question' },
}),
runtimeEvent({
id: 'rt-assistant',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 102,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'answer' },
}),
runtimeEvent({
id: 'rt-failed',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 103,
role: 'system',
author: 'system',
status: 'failed',
actions: { endInvocation: true },
}),
],
);
await manager.getMessages(session.id);
const messages = await manager.getMessages(session.id);
const repairedRun = await readInvocation(runStore, session.id, 'run-1');
const runtimeEvents = await runStore.readRuntimeEvents(session.id, 'run-1');
// The run already ended. Its ending is immutable, so the class it never
// stated stays unstated, and no read appends a second ending to supply one.
assert.strictEqual(runtimeInvocationFailureClass(repairedRun), undefined);
assert.strictEqual(runtimeEvents.filter((event) => event.status === 'failed').length, 1);
const [turnState] = messages.filter((message) => message.type === 'turn_state');
assert.partialDeepStrictEqual(turnState, { status: 'failed', errorClass: 'unknown' });
});
test('getMessages includes continuation output without inlining child agent output', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const manager = makeManagerForReadCutover(store, runStore);
const session = await manager.createSession(makeInput());
const sourceRunId = 'source-run';
const sourceTurnId = 'source-turn';
const sourceInvocationId = 'source-invocation';
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: sourceRunId,
turnId: sourceTurnId,
status: 'failed',
createdAt: 100,
updatedAt: 102,
completedAt: 102,
failureClass: 'app_restarted',
}),
[
runtimeEvent({
id: 'source-user',
invocationId: sourceInvocationId,
sessionId: session.id,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 101,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'write an article' },
}),
runtimeEvent({
id: 'source-failed',
invocationId: sourceInvocationId,
sessionId: session.id,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 102,
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'app_restarted' } },
}),
],
);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'continuation-run',
turnId: 'continuation-turn',
status: 'completed',
parentRunId: sourceRunId,
parentTurnId: sourceTurnId,
continuationSource: {
sourceInvocationId,
sourceRunId,
sourceTurnId,
sourceRuntimeEventHighWater: 2,
},
createdAt: 103,
updatedAt: 105,
completedAt: 105,
}),
[
runtimeEvent({
id: 'continuation-start',
invocationId: 'continuation-invocation',
sessionId: session.id,
runId: 'continuation-run',
turnId: 'continuation-turn',
ts: 103,
role: 'system',
author: 'system',
actions: {
stateDelta: { continuationStart: true },
runtimeProtocol: { toolBoundary: 't1_after_preflight_v1' },
},
refs: {
sourceInvocationId,
sourceRunId,
sourceTurnId,
sourceRuntimeEventHighWater: 2,
},
}),
runtimeEvent({
id: 'continuation-article',
invocationId: 'continuation-invocation',
sessionId: session.id,
runId: 'continuation-run',
turnId: 'continuation-turn',
ts: 104,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'the resumed article' },
}),
runtimeEvent({
id: 'continuation-complete',
invocationId: 'continuation-invocation',
sessionId: session.id,
runId: 'continuation-run',
turnId: 'continuation-turn',
ts: 105,
status: 'completed',
actions: { endInvocation: true },
}),
],
);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
status: 'completed',
parentRunId: sourceRunId,
parentTurnId: sourceTurnId,
createdAt: 106,
updatedAt: 108,
completedAt: 108,
}),
[
runtimeEvent({
id: 'child-answer',
invocationId: 'child-invocation',
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: 107,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'private child output' },
}),
runtimeEvent({
id: 'child-complete',
invocationId: 'child-invocation',
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: 108,
status: 'completed',
actions: { endInvocation: true },
}),
],
);
const messages = await manager.getMessages(session.id);
const assistantTexts = messages.flatMap((message) =>
message.type === 'assistant' ? [message.text] : [],
);
assert.ok(assistantTexts.includes('the resumed article'));
assert.ok(!assistantTexts.some((text) => text.includes('private child output')));
assert.strictEqual(
messages.some((message) => message.id === 'continuation-start'),
false,
);
});
test('getMessages reads an active run from its own ledger', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const manager = makeManagerForReadCutover(store, runStore);
const session = await manager.createSession(makeInput());
const completed = await seedRuntimeReadTurn({
store,
runStore,
sessionId: session.id,
turnId: 'turn-1',
runId: 'run-1',
userText: 'completed question',
assistantText: 'completed answer',
legacyIdPrefix: 'legacy',
});
const activeHeader = makeRunHeader({
sessionId: session.id,
runId: 'run-2',
turnId: 'turn-2',
status: 'running',
createdAt: 200,
updatedAt: 203,
});
await seedInvocationFromHeader(runStore, activeHeader);
await runStore.appendRuntimeEvent(
session.id,
'run-2',
runtimeEvent({
id: 'active-user-event',
sessionId: session.id,
runId: 'run-2',
turnId: 'turn-2',
ts: 201,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'active question' },
refs: { storedMessageId: 'active-user' },
}),
);
// Still arriving: the row a reader sees now, with more of it to come.
await runStore.appendRuntimeEvent(
session.id,
'run-2',
runtimeEvent({
id: 'active-assistant-event',
sessionId: session.id,
runId: 'run-2',
turnId: 'turn-2',
ts: 202,
partial: true,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'partial active answer' },
refs: { storedMessageId: 'active-assistant' },
}),
);
const messages = await manager.getMessages(session.id);
assert.deepStrictEqual(messages, [
...completed.projectedMessages,
{ type: 'user', id: 'active-user', turnId: 'turn-2', ts: 201, text: 'active question' },
{
type: 'assistant',
id: 'active-assistant',
turnId: 'turn-2',
ts: 202,
text: 'partial active answer',
modelId: 'fake-model',
},
]);
assert.deepStrictEqual(await manager.listTurns(session.id), [
{
turnId: 'turn-1',
status: 'completed',
statusSource: 'recorded',
},
{
turnId: 'turn-2',
status: 'running',
statusSource: 'recorded',
},
]);
});
test('getMessages overlays a canonical permission acceptance from a running ledger', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const session = await store.create(makeInput());
const header = makeRunHeader({
sessionId: session.id,
runId: 'active-run',
turnId: 'active-turn',
status: 'running',
createdAt: 100,
updatedAt: 125,
});
await seedInvocationFromHeader(runStore, header);
await store.appendMessages(session.id, [
{
type: 'user',
id: 'active-user',
turnId: header.turnId,
ts: 100,
text: 'write the file',
},
{
type: 'turn_state',
id: 'active-state',
turnId: header.turnId,
ts: 101,
status: 'running',
},
]);
await runStore.appendRuntimeEvent(
session.id,
header.runId,
runtimeEvent({
id: 'active-permission-request',
sessionId: session.id,
runId: header.runId,
turnId: header.turnId,
ts: 110,
actions: {
permissionRequest: {
kind: 'tool_permission',
requestId: 'request-canonical',
toolUseId: 'tool-canonical',
toolName: 'Write',
category: 'file_write',
reason: 'file_write',
args: { path: '/tmp/file' },
rememberForTurnAllowed: true,
hint: 'write approval',
},
},
refs: { toolCallId: 'tool-canonical' },
}),
);
await runStore.appendRuntimeEvent(
session.id,
header.runId,
runtimeEvent({
id: 'active-permission-acceptance',
sessionId: session.id,
runId: header.runId,
turnId: header.turnId,
ts: 126,
actions: { permissionAnswerAccepted: { requestId: 'request-canonical' } },
refs: { toolCallId: 'tool-canonical' },
}),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(125),
interactionAuthority: {
bindRun: () => {
throw new Error('read-only test authority cannot bind a Run');
},
},
canonicalPermissionOutcomes: {
readPermissionOutcome: async () =>
canonicalPermissionRecord(header, {
outcome: {
kind: 'permission_answer',
decision: 'allow',
rememberForTurn: true,
reviewer: 'auto_review',
committedAt: 125,
},
}),
},
});
assert.partialDeepStrictEqual(
(await manager.getMessages(session.id)).find(
(message) => message.type === 'permission_decision',
),
{
type: 'permission_decision',
id: 'request-canonical',
turnId: header.turnId,
toolUseId: 'tool-canonical',
decision: 'allow',
reviewer: 'auto_review',
},
);
});
test('the in-flight overlay keeps a pending sandbox boundary request visible in the view', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const session = await store.create(makeInput());
const header = makeRunHeader({
sessionId: session.id,
runId: 'active-run',
turnId: 'active-turn',
status: 'running',
createdAt: 100,
updatedAt: 125,
});
await seedInvocationFromHeader(runStore, header);
await store.appendMessages(session.id, [
{ type: 'user', id: 'active-user', turnId: header.turnId, ts: 100, text: 'build it' },
{
type: 'turn_state',
id: 'active-state',
turnId: header.turnId,
ts: 101,
status: 'running',
},
]);
await runStore.appendRuntimeEvent(
session.id,
header.runId,
runtimeEvent({
id: 'active-boundary-request',
sessionId: session.id,
runId: header.runId,
turnId: header.turnId,
ts: 110,
actions: {
stateDelta: {
sandboxBoundaryRequest: {
requestId: 'boundary-pending',
toolUseId: 'tool-boundary',
justification: 'Write outside the workspace.',
expansion: { network: { enabled: true } },
},
},
},
refs: { toolCallId: 'tool-boundary' },
}),
);
await runStore.appendRuntimeEvent(
session.id,
header.runId,
runtimeEvent({
id: 'active-boundary-decision',
sessionId: session.id,
runId: header.runId,
turnId: header.turnId,
ts: 111,
author: 'user',
actions: {
stateDelta: {
sandboxBoundaryDecision: {
requestId: 'boundary-pending',
decision: 'deny',
status: 'denied',
revision: 0,
},
},
},
refs: { toolCallId: 'tool-boundary' },
}),
);
const view = await new RuntimeReadModel({
runtimeEventStore: runStore,
}).getSessionView(session.id);
const readRequestId = (value: unknown): string[] =>
value !== null && typeof value === 'object' && 'requestId' in value
? [(value as { requestId: string }).requestId]
: [];
// Both halves of the interaction survive: without the decision the view
// would show a request that never resolves.
assert.deepStrictEqual(
view.events.flatMap((event) =>
readRequestId(event.actions?.stateDelta?.sandboxBoundaryRequest),
),
['boundary-pending'],
);
assert.deepStrictEqual(
view.events.flatMap((event) =>
readRequestId(event.actions?.stateDelta?.sandboxBoundaryDecision),
),
['boundary-pending'],
);
});
test('mixed projection-cache-only system notes do not override RuntimeEvent projection', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const manager = makeManagerForReadCutover(store, runStore);
const session = await manager.createSession(makeInput());
const seeded = await seedRuntimeReadTurn({
store,
runStore,
sessionId: session.id,
turnId: 'turn-1',
runId: 'run-1',
userText: 'question',
assistantText: 'answer',
legacyIdPrefix: 'legacy',
});
const legacyNote: StoredMessage = {
type: 'system_note',
id: 'legacy-note',
ts: 104,
kind: 'mode_change',
data: { from: 'ask', to: 'bypass' },
};
await store.appendMessage(session.id, legacyNote);
const messages = await manager.getMessages(session.id);
assert.deepStrictEqual(messages, seeded.projectedMessages);
});
test('getMessages orders RuntimeEvent-primary reads by durable session chronology', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const manager = makeManagerForReadCutover(store, runStore);
const session = await manager.createSession(makeInput());
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'slow-run',
turnId: 'slow',
status: 'completed',
createdAt: 100,
updatedAt: 107,
completedAt: 107,
}),
[
runtimeEvent({
id: 'slow-user',
sessionId: session.id,
runId: 'slow-run',
turnId: 'slow',
ts: 101,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'slow question' },
refs: { storedMessageId: 'slow-user-message' },
}),
runtimeEvent({
id: 'slow-assistant',
sessionId: session.id,
runId: 'slow-run',
turnId: 'slow',
ts: 106,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'slow answer' },
refs: { storedMessageId: 'slow-assistant-message' },
}),
runtimeEvent({
id: 'slow-complete',
sessionId: session.id,
runId: 'slow-run',
turnId: 'slow',
ts: 107,
role: 'system',
author: 'system',
status: 'completed',
actions: { endInvocation: true },
}),
],
);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'fast-run',
turnId: 'fast',
status: 'completed',
createdAt: 102,
updatedAt: 105,
completedAt: 105,
}),
[
runtimeEvent({
id: 'fast-user',
sessionId: session.id,
runId: 'fast-run',
turnId: 'fast',
ts: 103,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'fast question' },
refs: { storedMessageId: 'fast-user-message' },
}),
runtimeEvent({
id: 'fast-assistant',
sessionId: session.id,
runId: 'fast-run',
turnId: 'fast',
ts: 104,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'fast answer' },
refs: { storedMessageId: 'fast-assistant-message' },
}),
runtimeEvent({
id: 'fast-complete',
sessionId: session.id,
runId: 'fast-run',
turnId: 'fast',
ts: 105,
role: 'system',
author: 'system',
status: 'completed',
actions: { endInvocation: true },
}),
],
);
store.failNextReadMessagesFor.set(session.id, 1);
const messages = await manager.getMessages(session.id);
assert.deepStrictEqual(
messages.map(
(message) =>
`${message.type}:${'turnId' in message ? message.turnId : 'none'}:${message.ts}`,
),
[
'user:slow:101',
'assistant:slow:106',
'turn_state:slow:107',
'user:fast:103',
'assistant:fast:104',
'turn_state:fast:105',
],
);
});
test('regenerate finds completed source turns through the RuntimeEvent-primary view', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'end_turn' }]),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_770),
});
const session = await manager.createSession(makeInput());
await seedRuntimeReadTurn({
store,
runStore,
sessionId: session.id,
turnId: 'source',
runId: 'source-run',
userText: 'runtime regenerate text',
assistantText: 'runtime answer',
legacyIdPrefix: 'legacy',
legacyUserText: 'stale transcript text',
});
await drain(manager.regenerateTurn(session.id, { sourceTurnId: 'source', turnId: 'regen-1' }));
const messages = await manager.getMessages(session.id);
const regenUser = messages.find(
(message) => message.type === 'user' && message.turnId === 'regen-1',
);
assert.strictEqual(
regenUser?.type === 'user' ? regenUser.text : undefined,
'runtime regenerate text',
);
const regenState = deriveTurnRecords(messages).find((turn) => turn.turnId === 'regen-1');
assert.strictEqual(regenState?.regeneratedFromTurnId, 'source');
});
test('regenerate is stop-visible during its first source-ledger preflight', async () => {
const preflightStarted = makeGate();
const releasePreflight = makeGate();
let gatePreflight = false;
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore({
beforeListSessionRuns: async () => {
if (!gatePreflight) return;
gatePreflight = false;
preflightStarted.release();
await releasePreflight.promise;
},
});
const backends = new BackendRegistry();
let backend: TestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new TestBackend(ctx);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_775),
});
const session = await manager.createSession(makeInput());
await seedRuntimeReadTurn({
store,
runStore,
sessionId: session.id,
turnId: 'source',
runId: 'source-run',
userText: 'do not regenerate after stop',
assistantText: 'source answer',
legacyIdPrefix: 'legacy-stop',
});
gatePreflight = true;
const regenerating = manager
.regenerateTurn(session.id, {
sourceTurnId: 'source',
turnId: 'regen-stopped-preflight',
})
[Symbol.asyncIterator]();
const firstEvent = regenerating.next();
await preflightStarted.promise;
let stopSettled = false;
const stopping = manager.stopSession(session.id, { source: 'stop_button' }).finally(() => {
stopSettled = true;
});
await Promise.resolve();
assert.strictEqual(stopSettled, false);
releasePreflight.release();
await stopping;
assert.strictEqual((await firstEvent).done, true);
assert.deepStrictEqual(backend?.sendInputs, []);
const regenerated = (await runStore.listSessionInvocations(session.id)).find(
(run) => run.turnId === 'regen-stopped-preflight',
);
assert.strictEqual(regenerated && runtimeInvocationOutcome(regenerated), 'cancelled');
assert.strictEqual((await store.readHeader(session.id)).status === 'blocked', false);
});
test('regenerate accepts an aborted source turn (retry semantics merged into regenerate)', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'end_turn' }]),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_780),
});
const session = await manager.createSession(makeInput());
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'source-run',
turnId: 'source',
status: 'cancelled',
createdAt: 100,
updatedAt: 102,
completedAt: 102,
}),
[
runtimeEvent({
id: 'source-user',
sessionId: session.id,
runId: 'source-run',
turnId: 'source',
ts: 101,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'aborted turn text' },
}),
runtimeEvent({
id: 'source-abort',
sessionId: session.id,
runId: 'source-run',
turnId: 'source',
ts: 102,
role: 'system',
author: 'system',
status: 'aborted',
actions: { endInvocation: true, stateDelta: { abortSource: 'renderer.stop_button' } },
}),
],
);
// The transcript store holds no source rows at all, so what regenerate
// finds can only have come from the ledger.
await drain(
manager.regenerateTurn(session.id, { sourceTurnId: 'source', turnId: 'regen-aborted' }),
);
const regenUser = (await manager.getMessages(session.id)).find(
(message) => message.type === 'user' && message.turnId === 'regen-aborted',
);
assert.strictEqual(
regenUser?.type === 'user' ? regenUser.text : undefined,
'aborted turn text',
);
});
test('multi-run RuntimeEvent projection preserves retry regenerate and branch lineage on turns', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const manager = makeManagerForReadCutover(store, runStore);
const session = await manager.createSession(makeInput());
await seedRuntimeReadTurn({
store,
runStore,
sessionId: session.id,
turnId: 'root',
runId: 'root-run',
userText: 'root question',
assistantText: 'root answer',
legacyIdPrefix: 'root-legacy',
});
await seedRuntimeReadTurnWithHeader({
store,
runStore,
sessionId: session.id,
turnId: 'retry',
runId: 'retry-run',
userText: 'retry question',
assistantText: 'retry answer',
legacyIdPrefix: 'retry-legacy',
header: { parentTurnId: 'root', retriedFromTurnId: 'root' },
tsBase: 200,
});
await seedRuntimeReadTurnWithHeader({
store,
runStore,
sessionId: session.id,
turnId: 'regen',
runId: 'regen-run',
userText: 'regen question',
assistantText: 'regen answer',
legacyIdPrefix: 'regen-legacy',
header: { parentTurnId: 'root', regeneratedFromTurnId: 'root' },
tsBase: 300,
});
await seedRuntimeReadTurnWithHeader({
store,
runStore,
sessionId: session.id,
turnId: 'branch',
runId: 'branch-run',
userText: 'branch question',
assistantText: 'branch answer',
legacyIdPrefix: 'branch-legacy',
header: { parentSessionId: 'parent-session', branchOfTurnId: 'root' },
tsBase: 400,
});
store.failNextReadMessagesFor.set(session.id, 1);
const turns = await manager.listTurns(session.id);
assert.partialDeepStrictEqual(
turns.find((turn) => turn.turnId === 'retry'),
{
status: 'completed',
parentTurnId: 'root',
retriedFromTurnId: 'root',
},
);
assert.partialDeepStrictEqual(
turns.find((turn) => turn.turnId === 'regen'),
{
status: 'completed',
parentTurnId: 'root',
regeneratedFromTurnId: 'root',
},
);
assert.partialDeepStrictEqual(
turns.find((turn) => turn.turnId === 'branch'),
{
status: 'completed',
parentSessionId: 'parent-session',
branchOfTurnId: 'root',
},
);
});
test('next turn projects failed prior RuntimeEvents with the terminal fact failure class', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const backendInstances: TestBackend[] = [];
backends.register('ai-sdk', (ctx) => {
const backend = new TestBackend(ctx);
backendInstances.push(backend);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_810),
});
const session = await manager.createSession(makeInput());
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
status: 'running',
createdAt: 101,
updatedAt: 103,
}),
[
runtimeEvent({
id: 'rt-user',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 101,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'first' },
}),
runtimeEvent({
id: 'rt-assistant',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 102,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'before failure' },
}),
runtimeEvent({
id: 'rt-failed',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 103,
role: 'system',
author: 'system',
status: 'failed',
actions: { endInvocation: true, stateDelta: { failureClass: 'tool_failed' } },
}),
],
);
await drain(manager.sendMessage(session.id, { turnId: 'turn-2', text: 'second' }));
const secondInput = backendInstances[0]?.sendInputs[0];
if (!secondInput) throw new Error('backend input was not recorded');
assert.deepStrictEqual(
// The opening fact rides the same context as the three events it opened.
secondInput.runtimeContext?.map((event) => event.turnId),
['turn-1', 'turn-1', 'turn-1', 'turn-1'],
);
const failed = secondInput.runtimeContext?.find((event) => event.status === 'failed');
assert.strictEqual(failed?.actions?.stateDelta?.failureClass, 'tool_failed');
assert.strictEqual(secondInput.context, undefined);
});
test('next parent turn excludes child run RuntimeEvents from model context', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const backendInstances: TestBackend[] = [];
backends.register('ai-sdk', (ctx) => {
const backend = new TestBackend(ctx);
backendInstances.push(backend);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_825),
});
const session = await manager.createSession(makeInput());
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'first' }));
const [parentRun] = await runStore.listSessionInvocations(session.id);
if (!parentRun) throw new Error('parent run was not recorded');
const parentRunEndedAt = parentRun.terminalEvent?.ts ?? parentRun.openedAt;
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
status: 'completed',
createdAt: parentRunEndedAt + 1,
updatedAt: parentRunEndedAt + 4,
completedAt: parentRunEndedAt + 4,
parentRunId: parentRun.runId,
agentName: 'Researcher',
}),
[
runtimeEvent({
id: 'child-user',
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: parentRunEndedAt + 2,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'child prompt' },
}),
runtimeEvent({
id: 'child-assistant',
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: parentRunEndedAt + 3,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'child private answer' },
}),
runtimeEvent({
id: 'child-complete',
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: parentRunEndedAt + 4,
role: 'system',
author: 'system',
status: 'completed',
actions: { endInvocation: true },
}),
],
);
await drain(manager.sendMessage(session.id, { turnId: 'turn-2', text: 'second' }));
const secondInput = backendInstances[0]?.sendInputs[1];
if (!secondInput) throw new Error('second backend input was not recorded');
assert.deepStrictEqual(
secondInput.runtimeContext?.map((event) => event.turnId),
['turn-1', 'turn-1', 'turn-1', 'turn-1'],
);
assert.strictEqual(
secondInput.runtimeContext?.some((event) => event.turnId === 'child-turn'),
false,
);
assert.strictEqual(secondInput.context, undefined);
});
test('stopSession owns an active Run and a parent turn admitted before reservation', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const activeGate = makeGate();
let backend: TestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new TestBackend(ctx, activeGate);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_845),
});
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
const active = manager
.sendMessage(session.id, { turnId: 'active-parent-turn', text: 'hold active' })
[Symbol.asyncIterator]();
assert.strictEqual((await active.next()).value?.type, 'text_delta');
const readStarted = makeGate();
const releaseRead = makeGate();
store.nextReadHeaderGate = { started: readStarted, release: releaseRead };
const pending = manager
.sendMessage(session.id, {
turnId: 'pending-parent-turn',
text: 'must become stopped, not failed',
})
[Symbol.asyncIterator]();
const pendingEvent = pending.next();
await readStarted.promise;
let stopSettled = false;
const stopping = manager.stopSession(session.id, { source: 'stop_button' }).finally(() => {
stopSettled = true;
});
await Promise.resolve();
assert.strictEqual(stopSettled, false);
releaseRead.release();
activeGate.release();
await stopping;
assert.strictEqual((await pendingEvent).done, true);
while (!(await active.next()).done) {}
assert.deepStrictEqual(
backend?.sendInputs.map((input) => input.turnId),
['active-parent-turn'],
);
const runs = await runStore.listSessionInvocations(session.id);
assert.deepStrictEqual(
runs
.filter((run) => run.turnId.endsWith('-parent-turn'))
.map((run) => [run.turnId, runtimeInvocationOutcome(run)]),
[
['active-parent-turn', 'cancelled'],
['pending-parent-turn', 'cancelled'],
],
);
assert.strictEqual((await store.readHeader(session.id)).status, 'aborted');
});
test('a factory AbortError is not normalized as execution cancellation', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const buildStarted = makeGate();
const releaseBuild = makeGate();
backends.register('ai-sdk', async () => {
buildStarted.release();
await releaseBuild.promise;
const error = new Error('backend activation timed out');
error.name = 'AbortError';
throw error;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_845),
});
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
const turn = manager
.sendMessage(session.id, {
turnId: 'stop-owned-build-reject',
text: 'must remain a cancelled Run',
})
[Symbol.asyncIterator]();
const firstEvent = turn.next();
await buildStarted.promise;
let stopSettled = false;
const stopping = manager.stopSession(session.id, { source: 'stop_button' }).finally(() => {
stopSettled = true;
});
await Promise.resolve();
assert.strictEqual(stopSettled, false);
releaseBuild.release();
await expectRejects(firstEvent, /backend activation timed out/);
await stopping;
const run = (await runStore.listSessionInvocations(session.id))[0]!;
assert.strictEqual(runtimeInvocationOutcome(run), 'cancelled');
assert.strictEqual(runtimeInvocationFailureClass(run), undefined);
assert.strictEqual((await store.readHeader(session.id)).status, 'aborted');
});
test('stop signal cooperatively releases a blocked backend factory', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const buildStarted = makeGate();
let factorySignal: AbortSignal | undefined;
let dispatches = 0;
backends.register('ai-sdk', async (ctx) => {
factorySignal = ctx.abortSignal;
if (!factorySignal) throw new Error('backend factory did not receive an abort signal');
buildStarted.release();
await new Promise<void>((resolve) => {
factorySignal?.addEventListener('abort', () => resolve(), { once: true });
});
throw factorySignal.reason;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_845),
});
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
const turn = manager
.sendMessage(session.id, {
turnId: 'cooperative-factory-stop',
text: 'stop must release backend activation',
})
[Symbol.asyncIterator]();
const firstEvent = turn.next().then((result) => {
dispatches += result.done ? 0 : 1;
return result;
});
await buildStarted.promise;
await manager.stopSession(session.id, { source: 'stop_button' });
assert.strictEqual(factorySignal?.aborted, true);
assert.strictEqual((await firstEvent).done, true);
assert.strictEqual(dispatches, 0);
const [run] = await runStore.listSessionInvocations(session.id);
assert.strictEqual(run && runtimeInvocationOutcome(run), 'cancelled');
});
test('node timers AbortError is cancellation only when its cause is this execution stop', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const buildStarted = makeGate();
backends.register('ai-sdk', async (ctx) => {
if (!ctx.abortSignal) throw new Error('backend factory did not receive an abort signal');
buildStarted.release();
await timerDelay(60_000, undefined, { signal: ctx.abortSignal });
return new TestBackend(ctx);
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_845),
});
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
const turn = manager
.sendMessage(session.id, {
turnId: 'native-abort-wrapper-stop',
text: 'native AbortError should retain exact cancellation cause',
})
[Symbol.asyncIterator]();
const firstEvent = turn.next();
await buildStarted.promise;
await manager.stopSession(session.id, { source: 'stop_button' });
assert.strictEqual((await firstEvent).done, true);
const [run] = await runStore.listSessionInvocations(session.id);
assert.strictEqual(run && runtimeInvocationOutcome(run), 'cancelled');
assert.strictEqual(run && runtimeInvocationFailureClass(run), undefined);
});
test('late ignored-signal backend is disposed once and never cached or dispatched', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const buildStarted = makeGate();
const releaseBuild = makeGate();
let builds = 0;
let firstDisposeCalls = 0;
let firstDispatches = 0;
backends.register('ai-sdk', async (ctx) => {
builds += 1;
if (builds === 1) {
buildStarted.release();
await releaseBuild.promise;
return {
kind: 'fake' as const,
sessionId: ctx.sessionId,
async *send(): AsyncIterable<SessionEvent> {
firstDispatches += 1;
},
async stop(): Promise<void> {},
async respondToSandboxBoundary(): Promise<void> {},
async dispose(): Promise<void> {
firstDisposeCalls += 1;
},
};
}
return new TestBackend(ctx);
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_845),
});
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
const turn = manager
.sendMessage(session.id, {
turnId: 'ignored-factory-stop',
text: 'late backend must be rejected',
})
[Symbol.asyncIterator]();
const firstEvent = turn.next();
await buildStarted.promise;
const stopping = manager.stopSession(session.id, { source: 'stop_button' });
releaseBuild.release();
await stopping;
assert.strictEqual((await firstEvent).done, true);
assert.strictEqual(firstDisposeCalls, 1);
assert.strictEqual(firstDispatches, 0);
const [stoppedRun] = await runStore.listSessionInvocations(session.id);
assert.strictEqual(stoppedRun && runtimeInvocationOutcome(stoppedRun), 'cancelled');
await drain(
manager.sendMessage(session.id, {
turnId: 'post-ignored-factory-stop',
text: 'must build a fresh backend',
}),
);
assert.strictEqual(builds, 2);
assert.strictEqual(firstDisposeCalls, 1);
});
test('late cancelled backend disposal failure propagates as an AggregateError', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const buildStarted = makeGate();
const releaseBuild = makeGate();
let builds = 0;
let disposeCalls = 0;
backends.register('ai-sdk', async (ctx) => {
builds += 1;
buildStarted.release();
await releaseBuild.promise;
return {
kind: 'fake' as const,
sessionId: ctx.sessionId,
async *send(): AsyncIterable<SessionEvent> {
throw new Error('cancelled backend must not dispatch');
},
async stop(): Promise<void> {},
async respondToSandboxBoundary(): Promise<void> {},
async dispose(): Promise<void> {
disposeCalls += 1;
throw new Error('late backend disposal failed');
},
};
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_845),
});
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
const turn = manager
.sendMessage(session.id, {
turnId: 'late-disposal-failure',
text: 'cleanup failure must remain observable',
})
[Symbol.asyncIterator]();
const firstEvent = turn.next();
await buildStarted.promise;
const streamRejection = assert.rejects(firstEvent, (error: unknown) => {
assert.ok(error instanceof AggregateError);
assert.match(error.message, /Cancelled backend activation disposal failed/);
assert.ok(
error.errors.some(
(entry) => entry instanceof Error && entry.message === 'late backend disposal failed',
),
);
return true;
});
const stopping = manager.stopSession(session.id, { source: 'stop_button' });
releaseBuild.release();
await Promise.all([streamRejection, stopping]);
assert.strictEqual(disposeCalls, 1);
const [run] = await runStore.listSessionInvocations(session.id);
assert.strictEqual(run && runtimeInvocationOutcome(run), 'cancelled');
await expectRejects(
drain(
manager.sendMessage(session.id, {
turnId: 'late-disposal-next-turn',
text: 'must not create a second backend after quarantine failure',
}),
),
/permanently quarantined/,
);
assert.strictEqual(builds, 1);
assert.strictEqual(disposeCalls, 1);
});
test('concurrent stopSession calls share one stop attempt', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const stopStarted = makeGate();
const releaseStop = makeGate();
let backend: ConcurrentStopBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new ConcurrentStopBackend(ctx, stopStarted, releaseStop);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_847),
});
const session = await manager.createSession(makeInput());
const turn = manager
.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })
[Symbol.asyncIterator]();
await turn.next();
const firstStop = manager.stopSession(session.id, { source: 'stop_button' });
await stopStarted.promise;
let secondStopSettled = false;
const secondStop = manager.stopSession(session.id, { source: 'stop_button' }).finally(() => {
secondStopSettled = true;
});
await Promise.resolve();
await Promise.resolve();
assert.strictEqual(secondStopSettled, false);
releaseStop.release();
await Promise.all([firstStop, secondStop]);
while (!(await turn.next()).done) {}
assert.strictEqual(backend?.stopCalls, 1);
const messages = await manager.getMessages(session.id);
assert.strictEqual(
messages.filter(
(message) =>
message.type === 'turn_state' &&
message.turnId === 'turn-1' &&
message.status === 'aborted',
).length,
1,
);
});
test('stopSession waits for a registering turn and fences it before backend send', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backend: TestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new TestBackend(ctx);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_847),
});
const session = await manager.createSession(makeInput());
await drain(manager.sendMessage(session.id, { turnId: 'turn-warm-cache', text: 'warm cache' }));
const readStarted = makeGate();
const releaseRead = makeGate();
store.nextReadHeaderGate = { started: readStarted, release: releaseRead };
const turn = manager
.sendMessage(session.id, { turnId: 'turn-registering', text: 'hello' })
[Symbol.asyncIterator]();
const firstEvent = turn.next();
await readStarted.promise;
let stopSettled = false;
const stop = manager
.stopSession(session.id, {
source: 'graph_supervisor',
mode: 'after_step',
})
.finally(() => {
stopSettled = true;
});
await Promise.resolve();
assert.strictEqual(stopSettled, false);
releaseRead.release();
await stop;
assert.strictEqual((await firstEvent).done, true);
assert.strictEqual(backend?.stopCalls, 1);
assert.deepStrictEqual(backend?.stopModes, ['after_step']);
assert.deepStrictEqual(
backend?.sendInputs.map((input) => input.turnId),
['turn-warm-cache'],
);
const registeringRun = (await runStore.listSessionInvocations(session.id)).find(
(run) => run.turnId === 'turn-registering',
);
assert.strictEqual(registeringRun && runtimeInvocationOutcome(registeringRun), 'cancelled');
assert.strictEqual(
(await runStore.readRuntimeEvents(session.id, registeringRun!.runId)).filter(
isTerminalRuntimeEvent,
).length,
1,
);
});
test('stopSession fences dispatch after the public onRunStarted hook', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backend: TestBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new TestBackend(ctx);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_847),
});
const session = await manager.createSession(makeInput());
const hookStarted = makeGate();
const releaseHook = makeGate();
const turn = manager
.sendMessage(
session.id,
{ turnId: 'turn-post-start-hook-stop', text: 'must not dispatch' },
{
onRunStarted: async () => {
hookStarted.release();
await releaseHook.promise;
},
},
)
[Symbol.asyncIterator]();
const firstEvent = turn.next();
await hookStarted.promise;
await manager.stopSession(session.id, { source: 'stop_button' });
releaseHook.release();
assert.strictEqual((await firstEvent).done, true);
assert.deepStrictEqual(backend?.sendInputs, []);
const stoppedRun = (await runStore.listSessionInvocations(session.id)).find(
(run) => run.turnId === 'turn-post-start-hook-stop',
);
assert.strictEqual(stoppedRun && runtimeInvocationOutcome(stoppedRun), 'cancelled');
});
test('concurrent cold turns share one backend generation without accepting an ownerless response', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const buildStarted = makeGate();
const releaseBuild = makeGate();
const releaseSend = makeGate();
let builds = 0;
let backend: PermissionBroadcastBackend | undefined;
backends.register('ai-sdk', async (ctx) => {
builds += 1;
buildStarted.release();
await releaseBuild.promise;
backend = new PermissionBroadcastBackend(ctx, releaseSend);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_847),
});
const session = await manager.createSession(makeInput());
const first = manager
.sendMessage(session.id, { turnId: 'cold-turn-1', text: 'first' })
[Symbol.asyncIterator]();
const second = manager
.sendMessage(session.id, { turnId: 'cold-turn-2', text: 'second' })
[Symbol.asyncIterator]();
const firstEvent = first.next();
await buildStarted.promise;
const secondEvent = second.next();
await Promise.resolve();
await Promise.resolve();
assert.strictEqual(builds, 1);
releaseBuild.release();
assert.strictEqual((await firstEvent).value?.type, 'text_delta');
assert.strictEqual((await secondEvent).value?.type, 'text_delta');
await expectRejects(
manager.respondToSandboxBoundary(session.id, {
requestId: 'control-broadcast',
decision: 'deny',
}),
/No pending sandbox boundary request/,
);
assert.strictEqual(backend?.permissionResponses, 0);
assert.strictEqual(builds, 1);
releaseSend.release();
while (!(await first.next()).done) {}
while (!(await second.next()).done) {}
assert.strictEqual(backend?.sendInputs?.length, 2);
});
test('stopSession retries an unsettled abort without a second backend stop', async () => {
const store = new MemorySessionStore();
// The stop's own terminal fact fails once. Nothing else records the abort,
// so the retry has to settle the ledger — and must not reach the backend a
// second time to do it.
let failAbortAppend = false;
const runStore = new MemoryAgentRunStore({
beforeRuntimeEventAppend: (_sessionId, _runId, event) => {
if (!failAbortAppend || event.status !== 'aborted') return;
failAbortAppend = false;
throw new Error('append runtime event failed');
},
});
const backends = new BackendRegistry();
const sendGate = makeGate();
let backend: CountingStopBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new CountingStopBackend(ctx, sendGate);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_849),
});
const session = await manager.createSession(makeInput());
const turn = manager
.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })
[Symbol.asyncIterator]();
await turn.next();
failAbortAppend = true;
await expectRejects(
manager.stopSession(session.id, { source: 'stop_button' }),
/append runtime event failed/,
);
await manager.stopSession(session.id, { source: 'stop_button' });
assert.strictEqual(backend?.stopCalls, 1);
const messages = await manager.getMessages(session.id);
assert.strictEqual(
messages.filter(
(message) =>
message.type === 'turn_state' &&
message.turnId === 'turn-1' &&
message.status === 'aborted',
).length,
1,
);
sendGate.release();
while (!(await turn.next()).done) {}
});
test('stopSession retains a failed projection after its Run exits without reentering backend stop', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const sendGate = makeGate();
let backend: CountingStopBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new CountingStopBackend(ctx, sendGate);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_850),
});
const session = await manager.createSession(makeInput());
const turn = manager
.sendMessage(session.id, { turnId: 'turn-retained-stop', text: 'hello' })
[Symbol.asyncIterator]();
await turn.next();
store.failUpdateHeaderFor.add(session.id);
await expectRejects(
manager.stopSession(session.id, { source: 'stop_button' }),
/Cannot update header/,
);
sendGate.release();
while (!(await turn.next()).done) {}
store.failUpdateHeaderFor.delete(session.id);
await manager.stopSession(session.id, { source: 'stop_button' });
assert.strictEqual(backend?.stopCalls, 1);
const messages = await manager.getMessages(session.id);
assert.strictEqual(
messages.filter(
(message) =>
message.type === 'turn_state' &&
message.turnId === 'turn-retained-stop' &&
message.status === 'aborted',
).length,
1,
);
});
test('agent projections list catalog definitions separately from child runs and read output artifacts by child turn', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
listArtifactsForTurn: async (_sessionId, turnId) =>
turnId === 'child-turn'
? [
{
id: 'artifact-1',
sessionId: 'session-1',
turnId,
createdAt: 200,
name: 'notes.md',
kind: 'file',
relativePath: 'artifacts/notes.md',
sizeBytes: 12,
source: 'tool_result',
},
]
: [],
newId: nextId(),
now: nextNow(6_848),
});
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'parent-run',
turnId: 'parent-turn',
status: 'completed',
createdAt: 100,
updatedAt: 110,
completedAt: 110,
}),
[
runtimeEvent({
id: 'parent-user',
sessionId: session.id,
runId: 'parent-run',
turnId: 'parent-turn',
ts: 101,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'parent' },
}),
runtimeEvent({
id: 'parent-complete',
sessionId: session.id,
runId: 'parent-run',
turnId: 'parent-turn',
ts: 110,
role: 'system',
author: 'system',
status: 'completed',
actions: { endInvocation: true },
}),
],
);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
status: 'completed',
createdAt: 120,
updatedAt: 130,
completedAt: 130,
parentRunId: 'parent-run',
agentId: LOCAL_READ_AGENT_ID,
agentName: 'Researcher',
permissionMode: 'explore',
}),
[
runtimeEvent({
id: 'child-user',
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: 121,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'inspect' },
}),
runtimeEvent({
id: 'child-answer',
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: 125,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'child answer' },
}),
runtimeEvent({
id: 'child-complete',
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: 130,
role: 'system',
author: 'system',
status: 'completed',
actions: { endInvocation: true },
}),
],
);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'continuation-run',
turnId: 'continuation-turn',
status: 'completed',
createdAt: 140,
updatedAt: 150,
completedAt: 150,
parentRunId: 'parent-run',
parentTurnId: 'parent-turn',
continuationSource: {
sourceInvocationId: 'parent-invocation',
sourceRunId: 'parent-run',
sourceTurnId: 'parent-turn',
sourceRuntimeEventHighWater: 2,
},
}),
[
runtimeEvent({
id: 'continuation-answer',
sessionId: session.id,
runId: 'continuation-run',
turnId: 'continuation-turn',
ts: 145,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'continued answer' },
}),
runtimeEvent({
id: 'continuation-complete',
sessionId: session.id,
runId: 'continuation-run',
turnId: 'continuation-turn',
ts: 150,
role: 'system',
author: 'system',
status: 'completed',
actions: { endInvocation: true },
}),
],
);
const list = await manager.listChildAgents(session.id);
assert.deepStrictEqual(
list.definitions.map((agent) => agent.id),
[LOCAL_READ_AGENT_ID, WEB_RESEARCH_AGENT_ID, IMPLEMENTATION_AGENT_ID],
);
assert.deepStrictEqual(list.definitions[0]?.availability, { status: 'available' });
assert.strictEqual(list.definitions[0]?.contract.defaultWriteBack, 'summary');
assert.strictEqual(list.definitions[0]?.contract.workspace, 'same_workspace');
assert.deepStrictEqual(list.definitions[1]?.availability, {
status: 'unavailable',
reason: 'missing_tools',
missingTools: ['WebSearch'],
});
assert.deepStrictEqual(list.definitions[2]?.availability, {
status: 'unavailable',
reason: 'workspace_isolation_unavailable',
workspace: AGENT_WORKSPACE_WORKTREE,
requiredRuntime: 'worktree_child_executor',
});
assert.deepStrictEqual(
list.runs.map((agent) => agent.runId),
['child-run'],
);
assert.deepStrictEqual(
list.executions.map((agent) => agent.execution),
[
{
kind: 'legacy_child_run',
sessionId: session.id,
runId: 'child-run',
},
],
);
assert.strictEqual(list.runs[0]?.agentId, LOCAL_READ_AGENT_ID);
assert.strictEqual(list.runs[0]?.agentName, 'Researcher');
assert.strictEqual(list.runs[0]?.durationMs, 10);
const output = await manager.readChildAgentOutput(session.id, { runId: 'child-run' });
assert.strictEqual(output.invocation.runId, 'child-run');
assert.deepStrictEqual(
output.runtimeEvents.map((event) => event.id),
['child-run-invocation-opened', 'child-user', 'child-answer', 'child-complete'],
);
assert.deepStrictEqual(
output.artifacts.map((artifact) => artifact.id),
['artifact-1'],
);
});
test('child agent projections use the terminal RuntimeEvent fact when the child header is stale', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(6_900),
});
const session = await manager.createSession(makeInput({ permissionMode: 'ask' }));
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'parent-run',
turnId: 'parent-turn',
status: 'running',
createdAt: 100,
updatedAt: 120,
}),
[
runtimeEvent({
id: 'parent-complete',
sessionId: session.id,
runId: 'parent-run',
turnId: 'parent-turn',
ts: 120,
status: 'completed',
actions: { endInvocation: true },
}),
],
);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
status: 'running',
createdAt: 130,
updatedAt: 140,
parentRunId: 'parent-run',
agentId: LOCAL_READ_AGENT_ID,
agentName: 'Researcher',
permissionMode: 'explore',
}),
[
runtimeEvent({
id: 'child-answer',
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: 135,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'child answer' },
}),
runtimeEvent({
id: 'child-complete',
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: 140,
role: 'system',
author: 'system',
status: 'completed',
actions: { endInvocation: true },
}),
],
);
const list = await manager.listChildAgents(session.id);
assert.strictEqual(list.runs[0]?.runId, 'child-run');
assert.strictEqual(list.runs[0]?.status, 'completed');
assert.strictEqual(list.runs[0]?.completedAt, 140);
assert.strictEqual(list.runs[0]?.durationMs, 10);
const output = await manager.readChildAgentOutput(session.id, { runId: 'child-run' });
assert.strictEqual(runtimeInvocationOutcome(output.invocation), 'completed');
assert.strictEqual(output.invocation.terminalEvent?.ts, 140);
});
test('agent output returns a bounded child inspection instead of full replay internals', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_849),
});
const session = await manager.createSession(makeInput());
const header = makeRunHeader({
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
status: 'completed',
createdAt: 120,
updatedAt: 200,
completedAt: 200,
parentRunId: 'parent-run',
agentId: LOCAL_READ_AGENT_ID,
agentName: 'Researcher',
permissionMode: 'explore',
});
await seedInvocationFromHeader(runStore, header);
for (let index = 0; index < 25; index += 1) {
await runStore.appendEvent(
session.id,
'child-run',
makeRunEvent({
id: `op-${index}`,
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
type: 'model_stream_started',
ts: 120 + index,
}),
);
runStore.seedRuntimeEvent(
session.id,
'child-run',
runtimeEvent({
id: `rt-${index}`,
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: 120 + index,
role: 'model',
author: 'agent',
content: { kind: 'text', text: `line ${index}` },
}),
);
}
const output = await manager.readChildAgentOutput(session.id, {
runId: 'child-run',
maxEvents: 5,
view: 'all',
});
assert.strictEqual(output.invocation.runId, 'child-run');
assert.deepStrictEqual(
output.events.map((event) => event.id),
['op-20', 'op-21', 'op-22', 'op-23', 'op-24'],
);
assert.deepStrictEqual(
output.runtimeEvents.map((event) => event.id),
['rt-20', 'rt-21', 'rt-22', 'rt-23', 'rt-24'],
);
assert.strictEqual(output.truncated.events, true);
assert.strictEqual(output.truncated.runtimeEvents, true);
assert.strictEqual(output.budget.view, 'all');
assert.strictEqual(output.budget.projectedBytes <= output.budget.maxBytes, true);
assert.strictEqual('modelReplay' in output, false);
assert.strictEqual('projection' in output, false);
});
test('agent output bounds oversized runtime events by serialized bytes', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_849),
});
const session = await manager.createSession(makeInput());
await seedInvocationFromHeader(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
status: 'completed',
createdAt: 120,
updatedAt: 200,
completedAt: 200,
parentRunId: 'parent-run',
agentId: LOCAL_READ_AGENT_ID,
agentName: 'Researcher',
permissionMode: 'explore',
}),
);
runStore.seedRuntimeEvent(
session.id,
'child-run',
runtimeEvent({
id: 'oversized',
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: 130,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'x'.repeat(64 * 1024) },
}),
);
runStore.seedRuntimeEvent(
session.id,
'child-run',
runtimeEvent({
id: 'final',
sessionId: session.id,
runId: 'child-run',
turnId: 'child-turn',
ts: 140,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'bounded final result' },
}),
);
const output = await manager.readChildAgentOutput(session.id, {
runId: 'child-run',
maxEvents: 20,
maxBytes: 1024,
});
assert.deepStrictEqual(output.events, []);
assert.deepStrictEqual(
output.runtimeEvents.map((event) => event.id),
['final'],
);
assert.strictEqual(output.truncated.events, true);
assert.strictEqual(output.truncated.runtimeEvents, true);
assert.strictEqual(output.truncated.bytes, true);
assert.partialDeepStrictEqual(output.budget, {
view: 'runtime_events',
maxBytes: 1024,
});
assert.strictEqual(output.budget.projectedBytes <= output.budget.maxBytes, true);
});
test('backend build failure after user append writes a failed terminal run fact', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', () => {
throw new Error('backend init failed');
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(7_500),
});
const session = await manager.createSession(makeInput());
await expectRejects(
drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })),
/backend init failed/,
);
const header = await store.readHeader(session.id);
assert.strictEqual(header.status, 'blocked');
assert.strictEqual(header.blockedReason, 'unknown');
const messages = await manager.getMessages(session.id);
assert.strictEqual(
messages.some((message) => message.type === 'user' && message.turnId === 'turn-1'),
true,
);
const turn = (await manager.listTurns(session.id)).find(
(candidate) => candidate.turnId === 'turn-1',
);
assert.strictEqual(turn?.status, 'failed');
const [run] = await runStore.listSessionInvocations(session.id);
if (!run) throw new Error('AgentRunStore run was not created');
assert.strictEqual(runtimeInvocationOutcome(run), 'failed');
assert.strictEqual(runtimeInvocationFailureClass(run), 'missing_terminal_event');
const terminalEvents = (await runStore.readRuntimeEvents(session.id, run.runId)).filter(
isTerminalRuntimeEvent,
);
assert.strictEqual(terminalEvents.length, 1);
assert.strictEqual(terminalEvents[0]?.status, 'failed');
assert.strictEqual(
terminalEvents[0]?.actions?.stateDelta?.failureClass,
'missing_terminal_event',
);
assert.strictEqual(
(await manager.getMessages(session.id)).some((message) => message.type === 'user'),
true,
);
});
test('marks a sandbox boundary request waiting and blocks boundary mode changes', async () => {
const store = new VersionedConfigurationMemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backend: SandboxBoundaryWaitBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new SandboxBoundaryWaitBackend(ctx);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(9_000),
});
const session = await manager.createSession(makeInput());
const iterator = manager
.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })
[Symbol.asyncIterator]();
assert.strictEqual((await iterator.next()).value?.type, 'sandbox_boundary_request');
const [activeBoundaryRequest] = await manager.listActiveInteractions(session.id);
assert.partialDeepStrictEqual(activeBoundaryRequest, {
type: 'sandbox_boundary_request',
requestId: 'boundary-1',
toolUseId: 'tool-1',
turnId: 'turn-1',
});
assert.strictEqual((await store.readHeader(session.id)).status, 'waiting_for_user');
const [run] = await runStore.listSessionInvocations(session.id);
assert.strictEqual(run?.terminalEvent, undefined);
await expectRejects(manager.setPermissionMode(session.id, 'bypass'), /pending Interaction/);
assert.strictEqual((await store.readHeader(session.id)).permissionMode, 'ask');
await manager.respondToSandboxBoundary(session.id, {
requestId: 'boundary-1',
decision: 'deny',
});
assert.deepStrictEqual(backend?.responses, [{ requestId: 'boundary-1', decision: 'deny' }]);
assert.strictEqual((await iterator.next()).value?.type, 'sandbox_boundary_decision_ack');
assert.deepStrictEqual(await manager.listActiveInteractions(session.id), []);
while (!(await iterator.next()).done) {}
assert.strictEqual((await store.readHeader(session.id)).status, 'active');
});
test('lists an unanswered user question until its answer ack lands', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new FakeBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(9_000),
});
const session = await manager.createSession(makeInput());
const iterator = manager
.sendMessage(session.id, { turnId: 'turn-1', text: FAKE_ASK_USER_QUESTION_PROMPT })
[Symbol.asyncIterator]();
let request: SessionEvent | undefined;
while (request?.type !== 'user_question_request') {
const next = await iterator.next();
if (next.done) break;
request = next.value;
}
assert.strictEqual(request?.type, 'user_question_request');
// The surface that raised this request may never have been mounted, so the
// request has to be readable back from the kernel (#2072).
const active = await manager.listActiveInteractions(session.id);
assert.deepStrictEqual(active, [request]);
const requestId = (request as Extract<SessionEvent, { type: 'user_question_request' }>)
.requestId;
// One registry now holds both request kinds, so answering a question as if
// it were a boundary must settle nothing and leave the entry intact.
await expectRejects(
manager.respondToSandboxBoundary(session.id, { requestId, decision: 'deny' }),
/No pending sandbox boundary request/,
);
assert.deepStrictEqual(await manager.listActiveInteractions(session.id), [request]);
await manager.respondToUserQuestion(session.id, {
requestId,
answers: ['邀请制', '本周', '是'],
});
let ack: SessionEvent | undefined;
while (ack?.type !== 'user_question_answer_ack') {
const next = await iterator.next();
if (next.done) break;
ack = next.value;
}
assert.strictEqual(ack?.type, 'user_question_answer_ack');
assert.deepStrictEqual(await manager.listActiveInteractions(session.id), []);
while (!(await iterator.next()).done) {}
});
test('releases a question abandoned by a stopped turn', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new FakeBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(9_000),
});
const session = await manager.createSession(makeInput());
const iterator = manager
.sendMessage(session.id, { turnId: 'turn-1', text: FAKE_ASK_USER_QUESTION_PROMPT })
[Symbol.asyncIterator]();
let request: SessionEvent | undefined;
while (request?.type !== 'user_question_request') {
const next = await iterator.next();
if (next.done) break;
request = next.value;
}
assert.strictEqual(request?.type, 'user_question_request');
// A stop settles no answer, so this question never gets its ack. Only the
// turn-end release keeps it from being read back as a prompt for a dead
// run — one nothing could answer, since the backend generation is gone.
await manager.stopSession(session.id, { source: 'stop_button' });
while (!(await iterator.next()).done) {}
assert.deepStrictEqual(await manager.listActiveInteractions(session.id), []);
});
test('reads a completed session back after a sandbox boundary allow', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new SandboxBoundaryWaitBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(9_500),
});
const session = await manager.createSession(makeInput());
const iterator = manager
.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })
[Symbol.asyncIterator]();
assert.strictEqual((await iterator.next()).value?.type, 'sandbox_boundary_request');
await manager.respondToSandboxBoundary(session.id, {
requestId: 'boundary-1',
decision: 'allow',
});
while (!(await iterator.next()).done) {}
const messages = await manager.getMessages(session.id);
assert.strictEqual(
messages.some((message) => message.type === 'user'),
true,
);
const turns = await manager.listTurns(session.id);
assert.strictEqual(turns.find((turn) => turn.turnId === 'turn-1')?.status, 'completed');
});
// The next unclaimed control fact must not repeat #1607. A complete ledger
// with one event the projection was never taught still reads back — messages,
// turns and every turn-scoped action — and the unclaimed event is reported as
// a diagnostic rather than costing the session that contains it.
test('reads a completed session back with an unclaimed control fact in its ledger', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new UnmappedSessionEventBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(9_600),
});
const session = await manager.createSession(makeInput());
for await (const _event of manager.sendMessage(session.id, {
turnId: 'turn-1',
text: 'hello',
})) {
// drain
}
const messages = await manager.getMessages(session.id);
assert.strictEqual(messages.filter((message) => message.type === 'user').length, 1);
assert.strictEqual(
messages.some((message) => message.type === 'assistant' && message.text === 'hi back'),
true,
);
const turns = await manager.listTurns(session.id);
assert.strictEqual(turns.find((turn) => turn.turnId === 'turn-1')?.status, 'completed');
const view = await new RuntimeReadModel({
runtimeEventStore: runStore,
}).getSessionView(session.id);
assert.strictEqual(
view.diagnostics.filter((diagnostic) => diagnostic.code === 'unclaimed_control_fact').length,
1,
);
});
// The counterpart of the soft case, at the caller that enforces the policy:
// an unclaimed event that carries content may have cost a reader a row, so
// getSessionView must still refuse the view rather than serve a lossy one.
test('refuses a session view when an unclaimed event carries content', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new UnmappedSessionEventBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(9_700),
});
const session = await manager.createSession(makeInput());
for await (const _event of manager.sendMessage(session.id, {
turnId: 'turn-1',
text: 'hello',
})) {
// drain
}
const [run] = await runStore.listSessionInvocations(session.id);
runStore.seedRuntimeEvent(
session.id,
run!.runId,
runtimeEvent({
id: 'unclaimed-content',
sessionId: session.id,
runId: run!.runId,
turnId: 'turn-1',
ts: 4,
role: 'model',
author: 'agent',
content: { kind: 'not_yet_projected', text: 'a reader would have seen this' } as never,
}),
);
await assert.rejects(
new RuntimeReadModel({ runtimeEventStore: runStore }).getSessionView(session.id),
(error: unknown) =>
error instanceof RuntimeReadModelError &&
error.diagnostics.some((diagnostic) => diagnostic.code === 'unsupported_event'),
);
});
test('complete(stopReason=error) without a prior error event classifies as runtime_error not unknown', async () => {
// Reproduces the DeepSeek-reasoner smoke failure: the backend ended with
// stopReason='error' but never emitted a preceding error event, so the
// run ledger's failureClass was 'unknown'. It should be 'runtime_error'
// so benchmark scoring can distinguish runtime failures from max_tokens.
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'error' }]),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(10_000),
});
const session = await manager.createSession(makeInput());
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }));
const [turn] = await manager.listTurns(session.id);
assert.strictEqual(turn?.status, 'failed');
assert.strictEqual(turn?.errorClass, 'runtime_error');
const [run] = await runStore.listSessionInvocations(session.id);
assert.strictEqual(run && runtimeInvocationFailureClass(run), 'runtime_error');
});
test('marks an explicit step limit incomplete without blocking the session', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) => new EventBackend(ctx, [{ type: 'complete', stopReason: 'step_limit' }]),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(10_000),
});
const session = await manager.createSession(makeInput());
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }));
assert.strictEqual((await store.readHeader(session.id)).status, 'active');
const [turn] = await manager.listTurns(session.id);
assert.strictEqual(turn?.status, 'failed');
assert.strictEqual(turn?.errorClass, 'tool_step_cap_reached');
const [run] = await runStore.listSessionInvocations(session.id);
assert.strictEqual(run && runtimeInvocationOutcome(run), 'failed');
assert.strictEqual(run && runtimeInvocationFailureClass(run), 'tool_step_cap_reached');
const terminal = (await runStore.readRuntimeEvents(session.id, run!.runId)).find(
(event) => event.actions?.endInvocation,
);
assert.strictEqual(terminal?.status, 'failed');
assert.partialDeepStrictEqual(terminal?.actions?.stateDelta, {
stopReason: 'step_limit',
failureClass: 'tool_step_cap_reached',
});
});
test('does not let a late complete event overwrite a prior turn error', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) =>
new EventBackend(ctx, [
{ type: 'error', recoverable: false, reason: 'tool_failed', message: 'Tool failed' },
{ type: 'complete', stopReason: 'end_turn' },
]),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(10_500),
});
const session = await manager.createSession(makeInput());
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }));
const states = (await manager.getMessages(session.id)).filter(
(message) => message.type === 'turn_state' && message.turnId === 'turn-1',
);
assert.deepStrictEqual(
states.map((state) => (state.type === 'turn_state' ? state.status : '')),
['failed'],
);
const [turn] = await manager.listTurns(session.id);
assert.strictEqual(turn?.status, 'failed');
assert.strictEqual(turn?.errorClass, 'tool_failed');
});
test('stopSession records renderer abort source for diagnostics', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const gate = makeGate();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx, gate));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_500),
});
const session = await manager.createSession(makeInput());
const iterator = manager
.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })
[Symbol.asyncIterator]();
await iterator.next();
await manager.stopSession(session.id, { source: 'stop_button' });
const [turn] = await manager.listTurns(session.id);
assert.strictEqual(turn?.status, 'aborted');
assert.strictEqual(turn?.abortSource, 'renderer.stop_button');
});
test('stopSession persists abortSource on a terminal RuntimeEvent emitted during backend stop', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
let backend: StopControlledAbortBackend | undefined;
backends.register('ai-sdk', (ctx) => {
backend = new StopControlledAbortBackend(ctx);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_710),
});
const session = await manager.createSession(makeInput());
const iterator = manager
.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })
[Symbol.asyncIterator]();
await iterator.next();
const pendingAbort = iterator.next();
const stopPromise = manager.stopSession(session.id, { source: 'stop_button' });
const abort = await pendingAbort;
assert.strictEqual(abort.value?.type, 'abort');
backend?.allowStopReturn();
await stopPromise;
while (!(await iterator.next()).done) {}
const [run] = await runStore.listSessionInvocations(session.id);
const runtimeEvents = await runStore.readRuntimeEvents(session.id, run!.runId);
const terminalEvents = runtimeEvents.filter((event) => event.status === 'aborted');
assert.strictEqual(terminalEvents.length, 1);
assert.strictEqual(terminalEvents[0]?.actions?.stateDelta?.abortSource, 'renderer.stop_button');
const messages = await manager.getMessages(session.id);
assert.strictEqual(
messages.some((message) => message.type === 'user' && message.turnId === 'turn-1'),
true,
);
assert.deepStrictEqual(
messages.find((message) => message.type === 'turn_state'),
{
type: 'turn_state',
id: terminalEvents[0]?.id,
turnId: 'turn-1',
ts: 2,
status: 'aborted',
abortedAt: 2,
abortSource: 'renderer.stop_button',
},
);
});
test('sendMessage does not emit or persist backend events after the first terminal event', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) =>
new EventBackend(ctx, [
{ type: 'text_delta', messageId: 'm1', text: 'before' },
{ type: 'abort', reason: 'user_stop' },
{ type: 'text_delta', messageId: 'm1', text: 'after-terminal' },
{ type: 'complete', stopReason: 'user_stop' },
]),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_715),
});
const session = await manager.createSession(makeInput());
const emitted = await collectSessionEvents(
manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }),
);
const [run] = await runStore.listSessionInvocations(session.id);
const runtimeEvents = await runStore.readRuntimeEvents(session.id, run!.runId);
const turnStates = (await manager.getMessages(session.id)).filter(
(message) =>
message.type === 'turn_state' &&
message.turnId === 'turn-1' &&
message.status !== 'running',
);
assert.deepStrictEqual(
emitted.map((event) => event.type),
['text_delta', 'abort'],
);
assert.deepStrictEqual(
runtimeEvents
.filter((event) => event.role === 'model' && event.content?.kind === 'text')
.map((event) => (event.content?.kind === 'text' ? event.content.text : '')),
['before'],
);
const abortedEvents = runtimeEvents.filter((event) => event.status === 'aborted');
assert.strictEqual(abortedEvents.length, 1);
assert.strictEqual(abortedEvents[0]?.actions?.stateDelta?.abortSource, 'user_stop');
assert.strictEqual(run && runtimeInvocationOutcome(run), 'cancelled');
assert.strictEqual(run?.terminalEvent?.actions?.stateDelta?.abortSource, 'user_stop');
assert.strictEqual(turnStates.length, 1);
assert.strictEqual(
turnStates[0]?.type === 'turn_state' ? turnStates[0].status : undefined,
'aborted',
);
assert.strictEqual(
turnStates[0]?.type === 'turn_state' ? turnStates[0].abortSource : undefined,
'user_stop',
);
});
test('sendMessage ignores backend errors thrown after a completed terminal event is recorded', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new ThrowAfterTerminalBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_716),
});
const session = await manager.createSession(makeInput());
const emitted = await collectSessionEvents(
manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }),
);
const [run] = await runStore.listSessionInvocations(session.id);
const runtimeEvents = await runStore.readRuntimeEvents(session.id, run!.runId);
const turnStates = (await manager.getMessages(session.id)).filter(
(message) =>
message.type === 'turn_state' &&
message.turnId === 'turn-1' &&
message.status !== 'running',
);
assert.deepStrictEqual(
emitted.map((event) => event.type),
['text_delta', 'complete'],
);
assert.strictEqual(run && runtimeInvocationOutcome(run), 'completed');
assert.deepStrictEqual(
runtimeEvents
.filter((event) => event.role === 'model' && event.content?.kind === 'text')
.map((event) => (event.content?.kind === 'text' ? event.content.text : '')),
['before'],
);
assert.strictEqual(runtimeEvents.filter((event) => event.status === 'completed').length, 1);
assert.strictEqual(runtimeEvents.filter((event) => event.status === 'failed').length, 0);
assert.strictEqual(turnStates.length, 1);
assert.strictEqual(
turnStates[0]?.type === 'turn_state' ? turnStates[0].status : undefined,
'completed',
);
});
test('stopSession keeps aborted state even if the backend emits a late error', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const gate = makeGate();
backends.register('ai-sdk', (ctx) => new LateErrorBackend(ctx, gate));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_720),
});
const session = await manager.createSession(makeInput());
const iterator = manager
.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' })
[Symbol.asyncIterator]();
await iterator.next();
await manager.stopSession(session.id, { source: 'stop_button' });
gate.release();
await iterator.next();
await iterator.next();
await iterator.next();
assert.strictEqual((await store.readHeader(session.id)).status, 'aborted');
const [turn] = await manager.listTurns(session.id);
assert.strictEqual(turn?.status, 'aborted');
assert.strictEqual(turn?.abortSource, 'renderer.stop_button');
const [run] = await runStore.listSessionInvocations(session.id);
assert.strictEqual(run && runtimeInvocationOutcome(run), 'cancelled');
assert.strictEqual(run && runtimeInvocationFailureClass(run), undefined);
});
test('durable run ledger records lifecycle trace events and redacts obvious secrets', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TraceBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_750),
});
const session = await manager.createSession(makeInput());
await drain(manager.sendMessage(session.id, { turnId: 'turn-1', text: 'hello' }));
const [run] = await runStore.listSessionInvocations(session.id);
assert.partialDeepStrictEqual(run?.opening.route, {
provenance: 'unknown',
backendKind: 'ai-sdk',
llmConnectionSlug: 'fake',
modelId: 'fake-model',
});
assert.strictEqual(run?.opening.configuration.permissionMode, 'ask');
assert.strictEqual(run && runtimeInvocationOutcome(run), 'completed');
const events = await runStore.readEvents(session.id, run!.runId);
assert.ok(events.map((event) => event.type).includes('model_stream_started'));
assert.ok(events.map((event) => event.type).includes('model_stream_completed'));
assert.strictEqual(JSON.stringify(events).includes('sk-live-secret-token-value'), false);
});
test('history compact cleanup includes continuation events without including child agent events', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const cleanupCalled = makeGate();
let observedEventIds: string[] = [];
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new HistoryCompactCheckpointBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
cleanupHistoryCompactArtifacts: async (input) => {
observedEventIds = input.runtimeEvents.map((event) => event.id);
cleanupCalled.release();
},
newId: nextId(),
now: nextNow(12_794),
});
const session = await manager.createSession(makeInput());
const sourceRunId = 'cleanup-source-run';
const sourceTurnId = 'cleanup-source-turn';
const sourceInvocationId = 'cleanup-source-invocation';
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: sourceRunId,
turnId: sourceTurnId,
status: 'completed',
createdAt: 100,
updatedAt: 101,
completedAt: 101,
}),
[
runtimeEvent({
id: 'cleanup-source-complete',
invocationId: sourceInvocationId,
sessionId: session.id,
runId: sourceRunId,
turnId: sourceTurnId,
ts: 101,
status: 'completed',
actions: { endInvocation: true },
}),
],
);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'cleanup-continuation-run',
turnId: 'cleanup-continuation-turn',
status: 'completed',
parentRunId: sourceRunId,
parentTurnId: sourceTurnId,
continuationSource: {
sourceInvocationId,
sourceRunId,
sourceTurnId,
sourceRuntimeEventHighWater: 1,
},
createdAt: 102,
updatedAt: 104,
completedAt: 104,
}),
[
runtimeEvent({
id: 'cleanup-continuation-text',
invocationId: 'cleanup-continuation-invocation',
sessionId: session.id,
runId: 'cleanup-continuation-run',
turnId: 'cleanup-continuation-turn',
ts: 103,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'continued output' },
}),
runtimeEvent({
id: 'cleanup-continuation-complete',
invocationId: 'cleanup-continuation-invocation',
sessionId: session.id,
runId: 'cleanup-continuation-run',
turnId: 'cleanup-continuation-turn',
ts: 104,
status: 'completed',
actions: { endInvocation: true },
}),
],
);
await seedRuntimeRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'cleanup-child-run',
turnId: 'cleanup-child-turn',
status: 'completed',
parentRunId: sourceRunId,
parentTurnId: sourceTurnId,
createdAt: 105,
updatedAt: 107,
completedAt: 107,
}),
[
runtimeEvent({
id: 'cleanup-child-text',
invocationId: 'cleanup-child-invocation',
sessionId: session.id,
runId: 'cleanup-child-run',
turnId: 'cleanup-child-turn',
ts: 106,
role: 'model',
author: 'agent',
content: { kind: 'text', text: 'private child output' },
}),
runtimeEvent({
id: 'cleanup-child-complete',
invocationId: 'cleanup-child-invocation',
sessionId: session.id,
runId: 'cleanup-child-run',
turnId: 'cleanup-child-turn',
ts: 107,
status: 'completed',
actions: { endInvocation: true },
}),
],
);
await drain(
manager.sendMessage(session.id, { turnId: 'cleanup-current-turn', text: 'continue' }),
);
await cleanupCalled.promise;
assert.ok(observedEventIds.includes('cleanup-continuation-text'));
assert.ok(!observedEventIds.includes('cleanup-child-text'));
});
test('persists an explicit same-coverage successor checkpoint', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const writeOutcomes: string[] = [];
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) => new SameCoverageCheckpointReplacementProbeBackend(ctx, writeOutcomes),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_796),
});
const session = await manager.createSession(makeInput());
await drain(
manager.sendMessage(session.id, { turnId: 'same-coverage-initial', text: 'hello' }),
);
await drain(
manager.sendMessage(session.id, { turnId: 'same-coverage-replacement', text: 'hello again' }),
);
assert.deepStrictEqual(writeOutcomes, [
'same-coverage-initial:fulfilled',
'same-coverage-replacement:fulfilled',
]);
const checkpoints: HistoryCompactCheckpoint[] = [];
for (const run of await runStore.listSessionInvocations(session.id)) {
for (const event of await runStore.readEvents(session.id, run.runId)) {
if (event.type === 'history_compact_checkpoint_recorded') {
checkpoints.push(event.data?.checkpoint as HistoryCompactCheckpoint);
}
}
}
assert.strictEqual(checkpoints.length, 2);
assert.strictEqual(checkpoints[1]?.previousCheckpointId, checkpoints[0]?.checkpointId);
assert.deepStrictEqual(checkpoints[1]?.coverage, checkpoints[0]?.coverage);
});
test('rejects checkpoint recording after the current AgentRun store becomes unavailable', async () => {
const store = new MemorySessionStore();
const writeOutcomes: string[] = [];
// The store goes unavailable partway through the run, right before the
// checkpoint write asks it for anything.
let runStoreUnavailable = false;
const runStore = new MemoryAgentRunStore({
beforeAgentRunEventAppend: async () => {
if (runStoreUnavailable) throw new Error('run ledger append failed');
},
});
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) =>
new CheckpointRecorderContractProbeBackend(
ctx,
async () => {
runStoreUnavailable = true;
},
writeOutcomes,
),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_800),
});
const session = await manager.createSession(makeInput());
await drain(manager.sendMessage(session.id, { turnId: 'store-unavailable', text: 'hello' }));
assert.deepStrictEqual(writeOutcomes, ['store-unavailable:rejected']);
});
test('recovers a missing projection before rejecting a shorter cold-start checkpoint', async () => {
const store = new MemorySessionStore();
const runStore = new MissingCheckpointProjectionAgentRunStore();
const writeOutcomes: string[] = [];
const backends = new BackendRegistry();
backends.register(
'ai-sdk',
(ctx) => new CheckpointRecorderContractProbeBackend(ctx, async () => {}, writeOutcomes),
);
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_803),
});
const session = await manager.createSession(makeInput());
const durableCheckpoint = buildHistoryCompactCheckpoint({
sessionId: session.id,
coveredRuntimeEvents: Array.from(
{ length: 10 },
(_, index): RuntimeEvent => ({
id: `cold-durable-event-${index}`,
sessionId: session.id,
runId: `cold-durable-run-${index}`,
turnId: `cold-durable-turn-${index}`,
invocationId: `cold-durable-invocation-${index}`,
ts: index + 1,
partial: false,
role: 'user',
author: 'user',
content: { kind: 'text', text: `source ${index}` },
}),
),
summary: sectionedSummary('durable checkpoint before projection loss'),
});
const durableEvent = makeRunEvent({
sessionId: session.id,
runId: 'cold-seed-run',
turnId: 'cold-seed-turn',
type: 'history_compact_checkpoint_recorded',
ts: 1,
data: { checkpoint: durableCheckpoint },
});
await seedRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'cold-seed-run',
turnId: 'cold-seed-turn',
status: 'completed',
}),
[durableEvent],
);
await drain(
manager.sendMessage(session.id, {
turnId: 'cold-stale-after-projection-loss',
text: 'hello',
}),
);
assert.deepStrictEqual(writeOutcomes, ['cold-stale-after-projection-loss:rejected']);
assert.strictEqual(runStore.repairedProjection?.id, durableEvent.id);
const checkpointCoverage: number[] = [];
for (const run of await runStore.listSessionInvocations(session.id)) {
for (const event of await runStore.readEvents(session.id, run.runId)) {
if (event.type === 'history_compact_checkpoint_recorded') {
checkpointCoverage.push(
(event.data!.checkpoint as HistoryCompactCheckpoint).coverage.eventCount,
);
}
}
}
assert.deepStrictEqual(checkpointCoverage, [10]);
});
test('startup recovery unsticks a legacy transcript Session and its import settles the turns', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_800),
});
const running = await manager.createSession(makeInput({ status: 'running' }));
const waiting = await manager.createSession(makeInput({ status: 'waiting_for_user' }));
const activeStuck = await manager.createSession(makeInput({ status: 'active' }));
const failedThenCompleted = await manager.createSession(makeInput({ status: 'active' }));
const activeDone = await manager.createSession(makeInput({ status: 'active' }));
await store.appendMessages(running.id, [
{ type: 'user', id: 'running-user', turnId: 'running-turn', ts: 10, text: 'still running' },
{
type: 'turn_state',
id: 'running-state',
turnId: 'running-turn',
ts: 11,
status: 'running',
},
]);
await store.appendMessages(waiting.id, [
{ type: 'user', id: 'waiting-user', turnId: 'waiting-turn', ts: 20, text: 'waiting' },
{
type: 'turn_state',
id: 'waiting-state',
turnId: 'waiting-turn',
ts: 21,
status: 'running',
},
]);
await store.appendMessages(activeStuck.id, [
{
type: 'user',
id: 'active-stuck-user',
turnId: 'active-stuck-turn',
ts: 30,
text: 'already active but stuck',
},
{
type: 'turn_state',
id: 'active-stuck-state',
turnId: 'active-stuck-turn',
ts: 31,
status: 'running',
},
]);
await store.appendMessages(failedThenCompleted.id, [
{
type: 'user',
id: 'failed-completed-user',
turnId: 'failed-completed-turn',
ts: 32,
text: 'failed then completed',
},
{
type: 'turn_state',
id: 'failed-completed-running',
turnId: 'failed-completed-turn',
ts: 33,
status: 'running',
},
{
type: 'turn_state',
id: 'failed-completed-failed',
turnId: 'failed-completed-turn',
ts: 34,
status: 'failed',
errorClass: 'tool_failed',
},
{
type: 'turn_state',
id: 'failed-completed-completed',
turnId: 'failed-completed-turn',
ts: 35,
status: 'completed',
},
]);
await store.appendMessages(activeDone.id, [
{ type: 'user', id: 'active-user', turnId: 'active-turn', ts: 30, text: 'done' },
{
type: 'turn_state',
id: 'active-state',
turnId: 'active-turn',
ts: 31,
status: 'completed',
},
]);
// These Sessions predate the ledger: their transcript is all they have.
for (const seeded of [running, waiting, activeStuck, failedThenCompleted, activeDone]) {
store.markPreLedgerSession(seeded.id);
}
// Recovery owns only what it can still decide without a ledger: a header
// left mid-turn by a crash. Nothing here re-reads the transcript to guess a
// turn's outcome — the import below is the one path that converts it.
const recovered = await manager.recoverInterruptedSessions();
assert.deepStrictEqual(recovered, [running.id, waiting.id]);
assert.strictEqual((await store.readHeader(running.id)).status, 'active');
assert.strictEqual((await store.readHeader(waiting.id)).status, 'active');
assert.strictEqual((await store.readHeader(activeStuck.id)).status, 'active');
assert.strictEqual((await store.readHeader(failedThenCompleted.id)).status, 'active');
assert.strictEqual((await store.readHeader(activeDone.id)).status, 'active');
const turnOf = async (sessionId: string, turnId: string) =>
(await manager.listTurns(sessionId)).find((turn) => turn.turnId === turnId);
// A turn the transcript never recorded an ending for converts to the
// failure it actually was, rather than to an inferred restart class.
for (const [sessionId, turnId] of [
[running.id, 'running-turn'],
[waiting.id, 'waiting-turn'],
[activeStuck.id, 'active-stuck-turn'],
] as const) {
const turn = await turnOf(sessionId, turnId);
assert.strictEqual(turn?.status, 'failed');
assert.strictEqual(turn?.errorClass, 'missing_terminal_event');
}
// A recorded ending is imported as recorded, last state wins.
assert.strictEqual(
(await turnOf(failedThenCompleted.id, 'failed-completed-turn'))?.status,
'completed',
);
assert.strictEqual((await turnOf(activeDone.id, 'active-turn'))?.status, 'completed');
});
test('startup recovery derives the interrupted outcome sink from the runtime store', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
let sessionId = '';
let outcomeCommitFailuresRemaining = 3;
let outcomeCommitAttempts = 0;
const runtimeCommitSink: RuntimeCommitSink = {
commitToolPrepared: async () => {
throw new Error('not used during recovery');
},
commitToolOutcome: async (input) => {
outcomeCommitAttempts += 1;
if (outcomeCommitFailuresRemaining > 0) {
outcomeCommitFailuresRemaining -= 1;
throw new Error('transient outcome commit failure');
}
await runStore.appendRuntimeEvent(sessionId, 'run-1', input.runtimeEvent);
return { created: true, runtimeEventSeq: 4 };
},
};
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: Object.assign(runStore, runtimeCommitSink),
backends,
newId: nextId(),
now: nextNow(12_825),
});
const session = await manager.createSession(makeInput({ status: 'running' }));
sessionId = session.id;
await seedRunningTurn(store, session.id, 'turn-1');
await seedRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
status: 'running',
toolMode: 'code_mode',
}),
[
makeRunEvent({
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
type: 'tool_started',
ts: 12,
}),
],
);
const code = { code: 'return await tools.Read({ path: "a.ts" })' };
await runStore.appendRuntimeEvent(
session.id,
'run-1',
runtimeEvent({
id: 'initial',
sessionId: session.id,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'inspect' },
actions: { runtimeProtocol: { toolBoundary: 't1_after_preflight_v1' } },
}),
);
await runStore.appendRuntimeEvent(
session.id,
'run-1',
runtimeEvent({
id: 'outer-call',
sessionId: session.id,
role: 'model',
author: 'agent',
origin: 'provider',
modelVisibility: 'visible',
content: { kind: 'function_call', id: 'exec-1', name: 'exec', args: code },
refs: { operationId: 'outer-op', toolCallId: 'exec-1' },
}),
);
await runStore.appendRuntimeEvent(
session.id,
'run-1',
runtimeEvent({
id: 'outer-dispatch',
sessionId: session.id,
actions: {
toolDispatch: {
protocol: 't1_after_preflight_v1',
operationId: 'outer-op',
providerToolCallId: 'exec-1',
toolName: 'exec',
canonicalArgsHash: canonicalToolArgsHash('exec', code),
recoveryMode: 'never_auto_retry',
},
},
refs: { operationId: 'outer-op', toolCallId: 'exec-1' },
}),
);
await manager.recoverInterruptedSessions();
assert.equal((await readInvocation(runStore, session.id, 'run-1')).terminalEvent, undefined);
assert.equal(outcomeCommitAttempts, 2);
assert.equal(
(await runStore.readRuntimeEvents(session.id, 'run-1')).some(
(event) => event.content?.kind === 'function_response',
),
false,
);
await manager.recoverInterruptedSessions();
assert.equal(outcomeCommitAttempts, 4);
const runtimeEvents = await runStore.readRuntimeEvents(session.id, 'run-1');
const response = runtimeEvents.find(
(event) => event.content?.kind === 'function_response' && event.content.id === 'exec-1',
);
assert.equal(response?.content?.kind, 'function_response');
assert.equal(response?.content?.kind === 'function_response' && response.content.isError, true);
assert.deepEqual(
response?.content?.kind === 'function_response' ? response.content.result : undefined,
{
kind: 'json',
value: {
kind: 'code_mode',
status: 'interrupted',
message: 'Code Mode execution was interrupted by runtime recovery.',
},
},
);
assert.equal(
runtimeInvocationOutcome(await readInvocation(runStore, session.id, 'run-1')),
'failed',
);
});
test('startup recovery does not leave stale permission waits stuck', async () => {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(12_830),
});
const session = await manager.createSession(makeInput({ status: 'waiting_for_user' }));
await store.createSandboxBoundaryRequest({
sessionId: session.id,
requestId: 'boundary-before-restart',
turnId: 'turn-1',
expansion: { network: { enabled: true } },
justification: 'Fetch a dependency.',
});
await seedRunningTurn(store, session.id, 'turn-1');
await seedRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
status: 'waiting_for_user',
}),
[
makeRunEvent({
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
type: 'permission_requested',
ts: 12,
}),
],
);
await manager.recoverInterruptedSessions();
assert.strictEqual((await store.readHeader(session.id)).status, 'active');
const [turn] = await manager.listTurns(session.id);
assert.strictEqual(turn?.status, 'failed');
// This turn owned the pending request, so its failure names the closure
// rather than the bare restart.
assert.strictEqual(turn?.errorClass, 'sandbox_boundary_closed_by_restart');
const [run] = await runStore.listSessionInvocations(session.id);
assert.strictEqual(run && runtimeInvocationOutcome(run), 'failed');
assert.strictEqual(
run && runtimeInvocationFailureClass(run),
'sandbox_boundary_closed_by_restart',
);
assert.deepStrictEqual(await store.listPendingSandboxBoundaryRequests(session.id), []);
});
test('startup recovery keeps the generic restart class for an answered boundary request', async () => {
const { store, runStore, manager, session } = await seedBoundaryRestartSession({
now: 12_890,
requestId: 'boundary-answered',
});
await store.settleSandboxBoundaryRequest({
sessionId: session.id,
requestId: 'boundary-answered',
decision: 'allow',
});
// A fully recorded request/decision pair in the ledger must not be mistaken
// for a closure: the user answered it before the host went away.
await runStore.appendRuntimeEvent(
session.id,
'run-1',
runtimeEvent({
id: 'rt-boundary-request',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 12,
actions: {
stateDelta: {
sandboxBoundaryRequest: {
requestId: 'boundary-answered',
toolUseId: 'tool-1',
justification: 'Fetch a dependency.',
expansion: { network: { enabled: true } },
},
},
},
refs: { toolCallId: 'tool-1' },
}),
);
await runStore.appendRuntimeEvent(
session.id,
'run-1',
runtimeEvent({
id: 'rt-boundary-decision',
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
ts: 13,
author: 'user',
actions: {
stateDelta: {
sandboxBoundaryDecision: {
requestId: 'boundary-answered',
decision: 'allow',
status: 'approved',
revision: 1,
},
},
},
refs: { toolCallId: 'tool-1' },
}),
);
await manager.recoverInterruptedSessions();
const [turn] = await manager.listTurns(session.id);
assert.strictEqual(turn?.errorClass, 'app_restarted');
});
});
async function drainAll(iterable: AsyncIterable<SessionEvent>): Promise<SessionEvent[]> {
const events: SessionEvent[] = [];
for await (const event of iterable) events.push(event);
return events;
}
/** Mock model: first request calls the Probe tool, second finishes with text. */
function steeringToolThenDoneModel(): MockLanguageModelV4 {
const usage = {
inputTokens: { total: 100, noCache: 100, cacheRead: 0, cacheWrite: 0 },
outputTokens: { total: 10, text: 10, reasoning: 0 },
};
const model: MockLanguageModelV4 = new MockLanguageModelV4({
doStream: async () => {
const call = model.doStreamCalls.length;
const chunks: LanguageModelV4StreamPart[] =
call === 1
? [
{ type: 'stream-start', warnings: [] },
{
type: 'tool-call',
toolCallId: 'tool-1',
toolName: 'Probe',
input: JSON.stringify({ q: 'x' }),
},
{ type: 'finish', finishReason: { unified: 'tool-calls', raw: 'tool_calls' }, usage },
]
: [
{ type: 'stream-start', warnings: [] },
{ type: 'text-start', id: 'text-1' },
{ type: 'text-delta', id: 'text-1', delta: 'done' },
{ type: 'text-end', id: 'text-1' },
{ type: 'finish', finishReason: { unified: 'stop', raw: 'stop' }, usage },
];
return {
stream: simulateReadableStream({ chunks, initialDelayInMs: null, chunkDelayInMs: null }),
};
},
});
return model;
}
/**
* A SessionManager wired to a REAL AiSdkBackend over a mock model, so the
* full steering delivery chain (kernel lease -> backend durability wait ->
* AgentRun fail-closed persist) is exercised. `duringTool` runs inside the
* first step's tool execution — the moment a real user steers.
*/
async function steeringDeliverySession(
runStore: MemoryAgentRunStore,
model: MockLanguageModelV4,
duringTool: (manager: SessionManager, sessionId: string) => Promise<void> | void,
) {
const store = new MemorySessionStore();
const backends = new BackendRegistry();
let manager!: SessionManager;
let sessionId = '';
backends.register('ai-sdk', (ctx) =>
createTestAiSdkBackend({
sessionId: ctx.sessionId,
header: ctx.header,
connection: {
slug: 'mock-main',
providerType: 'anthropic',
defaultModel: 'mock-model-id',
},
apiKey: 'sk-test',
modelId: 'mock-model-id',
modelFactory: () => model,
tools: [
{
name: 'Probe',
description: 'Probe description',
parameters: z.object({ q: z.string() }),
impl: async () => {
await duringTool(manager, sessionId);
return { ok: true };
},
},
],
loadTurnRuntimeEvents: ctx.loadTurnRuntimeEvents,
allowMidTurnHistoryCompaction: ctx.allowMidTurnHistoryCompaction,
newId: nextId(),
now: nextNow(1),
}),
);
const managerDeps = {
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(1_000),
};
manager = new SessionManager(managerDeps);
const session = await manager.createSession(makeInput({ permissionMode: 'bypass' }));
sessionId = session.id;
return { manager, session, store };
}
/**
* Parks each send behind a per-turn gate, pulls steering exactly once after
* release, then parks again behind a post-pull gate before finishing — a
* deterministic harness for the owner-identity rule and for enqueues that
* land after the final step boundary (stranded steering).
*/
class GatedSteeringBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
readonly gates = new Map<string, Gate>();
readonly pullDone = new Map<string, Gate>();
readonly pulls = new Map<string, string[][]>();
constructor(ctx: BackendFactoryContext) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
const gate = makeGate();
const afterPull = makeGate();
this.gates.set(input.turnId, gate);
this.pullDone.set(input.turnId, afterPull);
await gate.promise;
const leases = (await input.pullSteering?.()) ?? [];
const record = this.pulls.get(input.turnId) ?? [];
record.push(leases.map((lease) => lease.content.text));
this.pulls.set(input.turnId, record);
let seq = 0;
for (const lease of leases) {
seq += 1;
yield {
type: 'steering_message',
id: `${input.turnId}-steer-${seq}`,
turnId: input.turnId,
ts: seq,
messageId: lease.messageId,
content: lease.content,
};
}
// Delivery for this fake is the echo itself; ack the leases.
input.ackSteering?.(leases.map((lease) => lease.id));
await afterPull.promise;
yield {
type: 'text_complete',
id: `${input.turnId}-final`,
turnId: input.turnId,
ts: 10,
messageId: `${input.turnId}-m`,
text: 'ok',
};
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 11,
stopReason: 'end_turn',
};
}
/** Release both of a turn's gates (start + post-pull). */
release(turnId: string): void {
this.gates.get(turnId)?.release();
this.pullDone.get(turnId)?.release();
}
async stop(): Promise<void> {
for (const turnId of this.gates.keys()) this.release(turnId);
}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {}
}
/**
* A Session with Plan authority whose active execution carries the given step
* statuses, plus the two writes the terminal settlement can make. Operation
* receipts stay empty here: the store that records them is exercised on its
* production implementation below.
*/
async function mountPlanSettlement(
sessionId: string,
steps: ReadonlyArray<readonly [string, PlanStepStatus]>,
): Promise<{
manager: SessionManager;
sessionId: string;
updates: UpdatePlanExecutionInput[];
interrupts: Array<{ sessionId: string; reason: string; operationId: string | undefined }>;
runtimeKernel: DelegatingRuntimeKernel;
}> {
const store = new MemorySessionStore();
const runtimeKernel = new DelegatingRuntimeKernel();
const updates: UpdatePlanExecutionInput[] = [];
const interrupts: Array<{
sessionId: string;
reason: string;
operationId: string | undefined;
}> = [];
const planState: PlanSessionState = {
...emptyPlanSessionState(sessionId),
storeVersion: 2,
activeExecutionId: 'execution-1',
executions: [
{
executionId: 'execution-1',
planId: 'plan-1',
proposalId: 'proposal-1',
sessionId,
status: 'active',
steps: steps.map(([id, status], index) => ({
id,
title: `Step ${index + 1}`,
description: `Do step ${index + 1}.`,
status,
updatedAt: 2,
})),
startedAt: 1,
updatedAt: 2,
},
],
};
const manager = new SessionManager({
store,
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(1),
runtimeKernel,
planStore: {
readState: async () => structuredClone(planState),
readOperationReceipt: async () => undefined,
interruptActiveExecution: async (target: string, reason: string, operationId?: string) => {
interrupts.push({ sessionId: target, reason, operationId });
return {
event: {
id: operationId ?? 'interrupt-operation',
sessionId: target,
ts: 3,
storeVersion: 3,
type: 'plan_execution_interrupted',
executionId: 'execution-1',
reason,
} satisfies PlanEvent,
state: { ...structuredClone(planState), activeExecutionId: undefined, storeVersion: 3 },
};
},
updateExecution: async (input: UpdatePlanExecutionInput) => {
updates.push(input);
return {
event: {
id: input.operationId ?? 'update-operation',
sessionId: input.sessionId,
ts: 3,
storeVersion: 3,
type: 'plan_execution_completed',
executionId: input.executionId,
steps: structuredClone(planState.executions[0]!.steps),
} satisfies PlanEvent,
state: { ...structuredClone(planState), activeExecutionId: undefined, storeVersion: 3 },
};
},
} as unknown as PlanStore,
});
await manager.createSession(makeInput());
return { manager, sessionId, updates, interrupts, runtimeKernel };
}
/**
* A production Plan store on a temporary storage root holding one approved
* proposal and the execution it started, plus the Session Manager that settles
* it. The settlement retry is keyed by the store's own operation receipt, so the
* cases that assert it run against the real store rather than a stub.
*/
async function mountRealPlanSettlement(): Promise<{
manager: SessionManager;
store: InteractivePlanStoreWriter;
sessionId: string;
executionId: string;
runtimeKernel: DelegatingRuntimeKernel;
close(): Promise<void>;
}> {
const root = await mkdtemp(join(tmpdir(), 'maka-plan-settlement-'));
const capability = await resolveStorageRoot({ path: root, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
assert.ok(owner, 'Unable to acquire the interactive storage root');
if (!owner) throw new Error('Unable to acquire the interactive storage root');
const store = await openInteractivePlanStoreForWrite(owner.lease);
const runtimeKernel = new DelegatingRuntimeKernel();
const manager = new SessionManager({
store: new MemorySessionStore(),
backends: new BackendRegistry(),
newId: nextId(),
now: nextNow(1),
runtimeKernel,
planStore: store,
});
const session = await manager.createSession(makeInput());
const submitted = await store.submitProposal({
sessionId: session.id,
turnId: 'turn-plan-1',
title: 'Ship the plan request',
steps: [
{ id: 'step-1', title: 'Step 1', description: 'Do step 1.' },
{ id: 'step-2', title: 'Step 2', description: 'Do step 2.' },
],
});
assert.equal(submitted.event.type, 'plan_submitted');
const approved = await store.approveProposal({
sessionId: session.id,
proposalId: submitted.event.proposal.proposalId,
expectedRevision: submitted.event.proposal.revision,
expectedStoreVersion: submitted.state.storeVersion,
operationId: 'turn-plan-1',
});
assert.equal(approved.event.type, 'plan_approved');
return {
manager,
store,
sessionId: session.id,
executionId: approved.event.execution.executionId,
runtimeKernel,
close: async () => {
store.close();
await owner.close();
await rm(root, { recursive: true, force: true });
},
};
}
class DelegatingRuntimeKernel implements RuntimeKernelLike {
async *runCoordinationOperation(
_sessionId: string,
_input: Parameters<RuntimeKernelLike['startTurn']>[1],
_options: unknown,
execute: Parameters<RuntimeKernelLike['runCoordinationOperation']>[3],
): AsyncIterable<SessionEvent> {
await execute();
}
readonly starts: Array<{
sessionId: string;
input: Parameters<RuntimeKernelLike['startTurn']>[1];
}> = [];
readonly stopped: string[] = [];
readonly permissionResponses: string[] = [];
activeRuns = false;
failNextDispose = false;
disposed: string[] = [];
cachedHeaders: SessionHeader[] = [];
constructor(private readonly events: readonly SessionEvent[] = []) {}
claimExecution(sessionId: string): ReturnType<RuntimeKernelLike['claimExecution']> {
const stopController = new AbortController();
return {
sessionId,
stopSignal: stopController.signal,
isStopRequested: () => false,
release: () => {},
};
}
async runSessionAdmissionMutation<T>(
_sessionIds: readonly string[],
operation: () => Promise<T> | T,
): Promise<T> {
return operation();
}
async runSessionQuiescentMutation<T>(
_sessionIds: readonly string[],
operation: () => Promise<T> | T,
): Promise<T> {
return operation();
}
async *startTurn(
sessionId: string,
input: Parameters<RuntimeKernelLike['startTurn']>[1],
): AsyncIterable<SessionEvent> {
this.starts.push({ sessionId, input });
for (const event of this.events) {
yield event;
}
}
async *compactSession(
sessionId: string,
input: Parameters<RuntimeKernelLike['compactSession']>[1] = {},
): AsyncIterable<SessionEvent> {
this.starts.push({ sessionId, input: { turnId: input.turnId ?? 'compact-turn', text: '' } });
for (const event of this.events) {
yield event;
}
}
async preflightContextCompaction(): Promise<void> {}
async stopSession(sessionId: string): Promise<void> {
this.stopped.push(sessionId);
}
async respondToSandboxBoundary(
sessionId: string,
_response: Parameters<RuntimeKernelLike['respondToSandboxBoundary']>[1],
): Promise<void> {
this.permissionResponses.push(sessionId);
}
hasActiveRuns(): boolean {
return this.activeRuns;
}
updateCachedHeader(_sessionId: string, header: SessionHeader): void {
this.cachedHeaders.push(header);
}
async invalidateBackend(sessionId: string): Promise<void> {
if (!this.activeRuns) this.disposed.push(sessionId);
}
async invalidateCachedBackends(): Promise<void> {
// This test double does not retain backend instances between calls.
}
async disposeBackend(sessionId: string): Promise<void> {
if (this.failNextDispose) {
this.failNextDispose = false;
throw new Error('backend disposal failed');
}
this.disposed.push(sessionId);
}
}
class UnadmittedQuestionBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
constructor(ctx: BackendFactoryContext) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
yield {
type: 'user_question_request',
id: 'unadmitted-event',
turnId: input.turnId,
ts: 1,
requestId: 'unadmitted-request',
toolUseId: 'unadmitted-tool',
questions: [
{
question: 'Continue?',
options: [{ label: 'Yes' }, { label: 'No' }],
},
],
};
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(): Promise<void> {}
async dispose(): Promise<void> {}
}
class TestBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
readonly sendInputs: BackendSendInput[] = [];
stopCalls = 0;
readonly stopModes: BackendStopMode[] = [];
constructor(
private readonly ctx: BackendFactoryContext,
private readonly gate?: Gate,
) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
this.sendInputs.push(input);
yield {
type: 'text_delta',
id: `${input.turnId}-delta`,
turnId: input.turnId,
ts: 1,
messageId: `${input.turnId}-m`,
text: 'ok',
};
await this.gate?.promise;
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 2,
stopReason: 'end_turn',
};
}
async stop(
_reason: 'user_stop' | 'redirect',
mode: BackendStopMode = 'immediate',
): Promise<void> {
this.stopCalls += 1;
this.stopModes.push(mode);
}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {
if (this.ctx.store instanceof MemorySessionStore) {
this.ctx.store.disposeCount += 1;
}
}
}
class PermissionBroadcastBackend extends TestBackend {
permissionResponses = 0;
override async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {
this.permissionResponses += 1;
}
}
class ConcurrentStopBackend extends TestBackend {
constructor(
ctx: BackendFactoryContext,
private readonly stopStarted: Gate,
private readonly releaseStop: Gate,
) {
super(ctx, releaseStop);
}
override async stop(): Promise<void> {
this.stopCalls += 1;
this.stopStarted.release();
await this.releaseStop.promise;
}
}
class CountingStopBackend extends TestBackend {
override async stop(): Promise<void> {
this.stopCalls += 1;
}
}
class FinalTextTestBackend extends TestBackend {
override async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
this.sendInputs.push(input);
yield {
type: 'text_complete',
id: `${input.turnId}-final`,
turnId: input.turnId,
ts: 1,
messageId: `${input.turnId}-m`,
text: 'ok',
};
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 2,
stopReason: 'end_turn',
};
}
}
class CountingFinalTextBackend extends FinalTextTestBackend {
constructor(
ctx: BackendFactoryContext,
private readonly onSend: () => void,
) {
super(ctx);
}
override async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
this.onSend();
yield* super.send(input);
}
}
class CompactingTestBackend extends TestBackend {
constructor(
ctx: BackendFactoryContext,
private readonly compactCalls: Array<{
turnId: string;
runtimeContextCount: number;
sourceRoutes?: Array<{ runId: string; connectionId?: string; modelId: string }>;
}>,
) {
super(ctx);
}
async compactHistory(input: BackendCompactHistoryInput) {
this.compactCalls.push({
turnId: input.turnId,
runtimeContextCount: input.runtimeContext.length,
sourceRoutes: (input.runtimeContextInvocations ?? []).map((run) => ({
runId: run.runId,
...(run.opening.route.provenance === 'runtime' &&
run.opening.route.backendKind !== 'plugin-executor'
? { connectionId: run.opening.route.llmConnectionId }
: {}),
modelId: run.opening.route.modelId,
})),
});
return compactHistoryResult();
}
}
class LifecycleChildBackend extends CompactingTestBackend {
override async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
this.sendInputs.push(input);
yield {
type: 'tool_start',
id: `${input.turnId}-tool-start`,
turnId: input.turnId,
ts: 1,
toolUseId: `${input.turnId}-read`,
toolName: 'Read',
args: { path: 'README.md' },
};
yield {
type: 'tool_result',
id: `${input.turnId}-tool-result`,
turnId: input.turnId,
ts: 2,
toolUseId: `${input.turnId}-read`,
isError: false,
content: { kind: 'text', text: 'README body' },
};
yield {
type: 'token_usage',
id: `${input.turnId}-usage`,
turnId: input.turnId,
ts: 3,
input: 10,
output: 5,
total: 15,
};
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 4,
stopReason: 'end_turn',
};
}
}
class BlockingCompactBackend extends TestBackend {
stopCalls = 0;
constructor(
ctx: BackendFactoryContext,
private readonly options: {
compactGate: Gate;
onCompactStart: (backend: BlockingCompactBackend) => void;
},
) {
super(ctx);
}
async compactHistory(_input: { turnId: string; runtimeContext: readonly RuntimeEvent[] }) {
this.options.onCompactStart(this);
await this.options.compactGate.promise;
return compactHistoryResult();
}
override async stop(): Promise<void> {
this.stopCalls += 1;
}
}
class FailOpenCompactingBackend extends TestBackend {
async compactHistory(_input: { turnId: string; runtimeContext: readonly RuntimeEvent[] }) {
return compactHistoryFailOpenResult();
}
}
class ThrowingCompactingBackend extends TestBackend {
async compactHistory(_input: {
turnId: string;
runtimeContext: readonly RuntimeEvent[];
}): Promise<never> {
throw new Error('backend compaction exploded');
}
}
class ActiveTurnBackend extends TestBackend {
constructor(
ctx: BackendFactoryContext,
private readonly options: {
turnStarted: Gate;
sendGate: Gate;
compactCalls: Array<{ turnId: string; runtimeContextCount: number }>;
},
) {
super(ctx, options.sendGate);
}
override async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
this.options.turnStarted.release();
yield* super.send(input);
}
async compactHistory(input: { turnId: string; runtimeContext: readonly RuntimeEvent[] }) {
this.options.compactCalls.push({
turnId: input.turnId,
runtimeContextCount: input.runtimeContext.length,
});
return compactHistoryResult();
}
}
function compactHistoryResult() {
return {
outcome: { kind: 'compacted' as const, checkpointId: 'checkpoint-1' },
contextBudget: {
enabled: true,
policyName: 'unit-budget',
estimatedTokensBefore: 1000,
estimatedTokensAfter: 400,
keptTurns: 1,
droppedTurns: 1,
keptEvents: 1,
droppedEvents: 1,
compactionDecisions: [
{
stage: 'priorReplay' as const,
sourceKind: 'runtimeEvents' as const,
decision: 'replaced' as const,
boundaryKind: 'historyCompact',
estimatedTokensSaved: 600,
},
],
},
};
}
function compactHistoryFailOpenResult() {
return {
outcome: { kind: 'failed' as const, reason: 'write_failed' },
contextBudget: {
enabled: true,
policyName: 'unit-budget',
estimatedTokensBefore: 1000,
estimatedTokensAfter: 400,
keptTurns: 1,
droppedTurns: 1,
keptEvents: 1,
droppedEvents: 1,
compactionDecisions: [
{
stage: 'priorReplay' as const,
sourceKind: 'runtimeEvents' as const,
decision: 'failedOpen' as const,
boundaryKind: 'historyCompact',
failOpenReason: 'write_failed',
},
],
},
};
}
class LateErrorBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
constructor(
private readonly ctx: BackendFactoryContext,
private readonly gate: Gate,
) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
yield {
type: 'text_delta',
id: `${input.turnId}-delta`,
turnId: input.turnId,
ts: 1,
messageId: `${input.turnId}-m`,
text: 'ok',
};
await this.gate.promise;
yield {
type: 'error',
id: `${input.turnId}-error`,
turnId: input.turnId,
ts: 2,
recoverable: false,
reason: 'late_error',
message: 'late backend error',
};
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {
if (this.ctx.store instanceof MemorySessionStore) {
this.ctx.store.disposeCount += 1;
}
}
}
class StopControlledAbortBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
private releaseAbort: () => void = () => {};
private releaseStop: () => void = () => {};
private readonly abortGate = new Promise<void>((resolve) => {
this.releaseAbort = resolve;
});
private readonly stopGate = new Promise<void>((resolve) => {
this.releaseStop = resolve;
});
constructor(ctx: BackendFactoryContext) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
yield {
type: 'text_delta',
id: `${input.turnId}-delta`,
turnId: input.turnId,
ts: 1,
messageId: `${input.turnId}-m`,
text: 'ok',
};
await this.abortGate;
yield {
type: 'abort',
id: `${input.turnId}-abort`,
turnId: input.turnId,
ts: 2,
reason: 'user_stop',
};
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 3,
stopReason: 'user_stop',
};
}
async stop(): Promise<void> {
this.releaseAbort();
await this.stopGate;
}
allowStopReturn(): void {
this.releaseStop();
}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {}
}
type PartialEvent =
| Omit<Extract<SessionEvent, { type: 'text_delta' }>, 'id' | 'turnId' | 'ts'>
| Omit<Extract<SessionEvent, { type: 'permission_request' }>, 'id' | 'turnId' | 'ts'>
| Omit<Extract<SessionEvent, { type: 'complete' }>, 'id' | 'turnId' | 'ts'>
| Omit<Extract<SessionEvent, { type: 'error' }>, 'id' | 'turnId' | 'ts'>
| Omit<Extract<SessionEvent, { type: 'abort' }>, 'id' | 'turnId' | 'ts'>;
class TurnScriptBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
readonly sendInputs: BackendSendInput[] = [];
constructor(
private readonly ctx: BackendFactoryContext,
private readonly turns: PartialEvent[][],
) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
this.sendInputs.push(input);
const events = this.turns[this.sendInputs.length - 1] ?? [
{ type: 'text_delta', messageId: `${input.turnId}-m`, text: 'ok' },
{ type: 'complete', stopReason: 'end_turn' },
];
let index = 0;
for (const event of events) {
index += 1;
yield {
...event,
id: `${input.turnId}-${index}`,
turnId: input.turnId,
ts: index,
} as SessionEvent;
}
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {}
}
class EventBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
constructor(
private readonly ctx: BackendFactoryContext,
private readonly events: PartialEvent[],
) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
let index = 0;
for (const event of this.events) {
index += 1;
yield {
...event,
id: `${input.turnId}-${index}`,
turnId: input.turnId,
ts: index,
} as SessionEvent;
}
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {}
}
class SandboxBoundaryWaitBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
readonly responses: SandboxBoundaryResponse[] = [];
private readonly responseGate = makeGate();
constructor(ctx: BackendFactoryContext) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
yield {
type: 'sandbox_boundary_request',
id: `${input.turnId}-request`,
turnId: input.turnId,
ts: 1,
requestId: 'boundary-1',
toolUseId: 'tool-1',
justification: 'Write the requested export.',
expansion: {
filesystem: {
entries: [{ path: '/tmp/export.txt', access: 'write', scope: 'exact' }],
},
},
};
await this.responseGate.promise;
const response = this.responses[0]!;
yield {
type: 'sandbox_boundary_decision_ack',
id: `${input.turnId}-decision`,
turnId: input.turnId,
ts: 2,
requestId: response.requestId,
toolUseId: 'tool-1',
decision: response.decision,
status: response.decision === 'allow' ? 'approved' : 'denied',
revision: response.decision === 'allow' ? 1 : 0,
};
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 3,
stopReason: 'end_turn',
};
}
async stop(): Promise<void> {
this.responseGate.release();
}
async respondToSandboxBoundary(response: SandboxBoundaryResponse): Promise<void> {
this.responses.push(response);
this.responseGate.release();
}
async dispose(): Promise<void> {}
}
/**
* Emits a SessionEvent variant the mapping has never been taught, wrapped in a
* turn that produces a real assistant message. That is what a future variant
* looks like from the read model's side: the mapper's exhaustiveness guard turns
* it into a control-only RuntimeEvent, and the ledger keeps it.
*/
class UnmappedSessionEventBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
constructor(ctx: BackendFactoryContext) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
yield {
type: 'text_complete',
id: `${input.turnId}-text`,
turnId: input.turnId,
ts: 1,
messageId: `${input.turnId}-message`,
text: 'hi back',
};
yield {
type: 'not_yet_mapped',
id: `${input.turnId}-unmapped`,
turnId: input.turnId,
ts: 2,
} as unknown as SessionEvent;
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 3,
stopReason: 'end_turn',
};
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(): Promise<void> {}
async dispose(): Promise<void> {}
}
class ThrowAfterTerminalBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
constructor(ctx: BackendFactoryContext) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
yield {
type: 'text_delta',
id: `${input.turnId}-delta`,
turnId: input.turnId,
ts: 1,
messageId: `${input.turnId}-m`,
text: 'before',
};
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 2,
stopReason: 'end_turn',
};
throw new Error('cleanup after terminal failed');
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {}
}
class ThrowBeforeTerminalBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
constructor(ctx: BackendFactoryContext) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
yield {
type: 'text_delta',
id: `${input.turnId}-delta`,
turnId: input.turnId,
ts: 1,
messageId: `${input.turnId}-m`,
text: 'before',
};
throw new Error('backend failed before terminal');
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {}
}
class TraceBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
constructor(private readonly ctx: BackendFactoryContext) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
this.ctx.recordRunTrace?.({
id: `${input.turnId}-trace-start`,
sessionId: this.sessionId,
turnId: input.turnId,
ts: 1,
phase: 'model',
type: 'model_stream_started',
message: 'Model stream started with Bearer sk-live-secret-token-value',
data: {
activeTools: ['Read'],
credential: 'sk-live-secret-token-value',
},
});
yield {
type: 'text_delta',
id: `${input.turnId}-delta`,
turnId: input.turnId,
ts: 2,
messageId: `${input.turnId}-m`,
text: 'ok',
};
this.ctx.recordRunTrace?.({
id: `${input.turnId}-trace-usage`,
sessionId: this.sessionId,
turnId: input.turnId,
ts: 3,
phase: 'model',
type: 'model_stream_completed',
message: 'Model stream completed',
data: { finishReason: 'stop' },
});
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 4,
stopReason: 'end_turn',
};
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {}
}
class HistoryCompactCheckpointBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
constructor(private readonly ctx: BackendFactoryContext) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
const checkpoint = buildHistoryCompactCheckpoint({
sessionId: this.sessionId,
coveredRuntimeEvents: [
{
id: 'source-event',
sessionId: this.sessionId,
runId: 'source-run',
turnId: 'source-turn',
invocationId: 'source-invocation',
ts: 1,
partial: false,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'source' },
},
],
summary: sectionedSummary('persist the bounded checkpoint'),
});
this.ctx.recordHistoryCompactCheckpoint?.(
{ ...checkpoint, checkpointId: 'hcheckpoint-test' },
input.turnId,
);
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 4,
stopReason: 'end_turn',
};
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {}
}
class SameCoverageCheckpointReplacementProbeBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
constructor(
private readonly ctx: BackendFactoryContext,
private readonly writeOutcomes: string[],
) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
const current = await this.ctx.loadHistoryCompactCheckpoint?.();
const coveredRuntimeEvents: RuntimeEvent[] = [
{
id: 'same-coverage-source-event',
sessionId: this.sessionId,
runId: 'same-coverage-source-run',
turnId: 'same-coverage-source-turn',
invocationId: 'same-coverage-source-invocation',
ts: 1,
partial: false,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'same source' },
},
];
try {
await this.ctx.recordHistoryCompactCheckpoint?.(
buildHistoryCompactCheckpoint({
sessionId: this.sessionId,
coveredRuntimeEvents,
summary: sectionedSummary(`${input.turnId} summary`),
...(current ? { previousCheckpointId: current.checkpointId } : {}),
}),
input.turnId,
);
this.writeOutcomes.push(`${input.turnId}:fulfilled`);
} catch {
this.writeOutcomes.push(`${input.turnId}:rejected`);
}
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 4,
stopReason: 'end_turn',
};
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {}
}
class CheckpointRecorderContractProbeBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
constructor(
private readonly ctx: BackendFactoryContext,
private readonly beforeRecord: (turnId: string) => Promise<void>,
private readonly writeOutcomes: string[],
) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
await this.beforeRecord(input.turnId);
const coverage = input.turnId === 'parent-furthest-in-flight' ? 2 : 1;
const coveredRuntimeEvents = Array.from(
{ length: coverage },
(_, index): RuntimeEvent => ({
id: `contract-source-event-${index}`,
sessionId: this.sessionId,
runId: `contract-source-run-${index}`,
turnId: `contract-source-turn-${index}`,
invocationId: `contract-source-invocation-${index}`,
ts: index + 1,
partial: false,
role: 'user',
author: 'user',
content: { kind: 'text', text: `source ${index}` },
}),
);
try {
await this.ctx.recordHistoryCompactCheckpoint?.(
buildHistoryCompactCheckpoint({
sessionId: this.sessionId,
coveredRuntimeEvents,
summary: sectionedSummary(`${input.turnId} checkpoint`),
}),
input.turnId,
);
this.writeOutcomes.push(`${input.turnId}:fulfilled`);
} catch {
this.writeOutcomes.push(`${input.turnId}:rejected`);
}
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 4,
stopReason: 'end_turn',
};
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {}
}
class MemorySessionStore implements SessionStore {
private headers = new Map<string, SessionHeader>();
private messages = new Map<string, StoredMessage[]>();
private executionBoundaries = new Map<string, ExecutionBoundary>();
private sandboxBoundaryRequests = new Map<string, SandboxBoundaryRequest>();
readonly failReadMessagesFor = new Set<string>();
readonly failNextReadMessagesFor = new Map<string, number>();
readonly failListTurnsFor = new Set<string>();
readonly failUpdateHeaderFor = new Set<string>();
disposeCount = 0;
nextReadHeaderGate: { started: Gate; release: Gate } | undefined;
nextGraphOperatorProvisionGate: { started: Gate; release: Gate } | undefined;
/** A Session written before the header carried a transcript ledger version. */
markPreLedgerSession(sessionId: string): void {
const header = this.headers.get(sessionId);
if (!header) throw new Error(`Unknown session ${sessionId}`);
const { transcriptLedgerVersion: _version, ...legacy } = header;
void _version;
this.headers.set(sessionId, legacy);
}
async createSubagent(
input: CreateSessionInput,
initialBoundary?: ExecutionBoundary,
): Promise<{ header: SessionHeader; created: boolean }> {
const parent = input.subagentParent;
const spawn = input.subagentSpawn;
if (!parent || !input.subagentRuntime || !spawn) {
throw new Error('Missing child-session metadata');
}
const existing = Array.from(this.headers.values()).find((header) => {
const candidate = header.subagentParent;
return (
candidate?.parentSessionId === parent.parentSessionId &&
candidate.spawnedBy.parentRunId === parent.spawnedBy.parentRunId &&
candidate.spawnedBy.toolCallId === parent.spawnedBy.toolCallId &&
candidate.swarm?.swarmId === parent.swarm?.swarmId &&
candidate.swarm?.itemId === parent.swarm?.itemId
);
});
if (existing) {
if (
existing.subagentSpawn?.requestFingerprint !== spawn.requestFingerprint ||
existing.subagentParent?.spawnedBy.parentTurnId !== parent.spawnedBy.parentTurnId
) {
throw new Error('Child-session spawn identity was reused for different work');
}
return { header: existing, created: false };
}
return { header: await this.create(input, initialBoundary), created: true };
}
async createAgentGraphOperator(
input: CreateSessionInput,
request: AgentGraphOperatorProvisionRequest,
_expectedRevision: number,
initialBoundary?: ExecutionBoundary,
): Promise<{ header: SessionHeader } & AgentGraphOperatorProvisionResult> {
const gate = this.nextGraphOperatorProvisionGate;
if (gate) {
this.nextGraphOperatorProvisionGate = undefined;
gate.started.release();
await gate.release.promise;
}
const header = await this.create(input, initialBoundary);
return {
header,
created: true,
provision: {
...request,
edges: request.edges.map((edge) => ({ ...edge })),
targetSessionId: header.id,
provisionedAt: 1,
},
};
}
async create(
input: CreateSessionInput,
initialBoundary?: ExecutionBoundary,
): Promise<SessionHeader> {
const header: SessionHeader = {
id: `session-${this.headers.size + 1}`,
workspaceRoot: '/tmp/workspace',
cwd: input.cwd,
...(input.projectId !== undefined ? { projectId: input.projectId } : {}),
createdAt: 1,
name: input.name ?? 'New Chat',
titleIsManual: false,
isFlagged: false,
labels: input.labels ?? [],
isArchived: false,
status: input.status ?? 'active',
...(input.blockedReason ? { blockedReason: input.blockedReason } : {}),
statusUpdatedAt: 1,
...(input.parentSessionId ? { parentSessionId: input.parentSessionId } : {}),
...(input.branchOfTurnId ? { branchOfTurnId: input.branchOfTurnId } : {}),
...(input.subagentParent ? { subagentParent: input.subagentParent } : {}),
...(input.subagentRuntime ? { subagentRuntime: input.subagentRuntime } : {}),
...(input.subagentSpawn ? { subagentSpawn: input.subagentSpawn } : {}),
...(input.subagentWorkspace ? { subagentWorkspace: input.subagentWorkspace } : {}),
...(input.revisionRootSessionId
? { revisionRootSessionId: input.revisionRootSessionId }
: {}),
...(input.revisionParentSessionId
? { revisionParentSessionId: input.revisionParentSessionId }
: {}),
...(input.revisionOfTurnId ? { revisionOfTurnId: input.revisionOfTurnId } : {}),
...(input.revisionIndex !== undefined ? { revisionIndex: input.revisionIndex } : {}),
...(input.revisionState ? { revisionState: input.revisionState } : {}),
hasUnread: false,
backend: input.executorId ? 'plugin-executor' : 'ai-sdk',
...(input.executorId ? { executorId: input.executorId } : {}),
...(input.llmConnectionId === undefined ? {} : { llmConnectionId: input.llmConnectionId }),
llmConnectionSlug: input.llmConnectionSlug,
connectionLocked: input.subagentParent !== undefined,
model: input.model ?? 'fake-model',
...(input.thinkingLevel !== undefined ? { thinkingLevel: input.thinkingLevel } : {}),
permissionMode: input.permissionMode,
...(input.toolMode !== undefined ? { toolMode: input.toolMode } : {}),
collaborationMode: input.collaborationMode ?? 'agent',
orchestrationMode: input.orchestrationMode ?? 'default',
transcriptLedgerVersion: 1,
schemaVersion: 1,
};
this.headers.set(header.id, header);
this.messages.set(header.id, []);
this.executionBoundaries.set(
header.id,
initialBoundary
? { ...initialBoundary, revision: 0 }
: createGenesisExecutionBoundary(header.permissionMode),
);
return header;
}
async setExecutionBoundaryKind(
sessionId: string,
kind: 'managed' | 'bypass',
projection?: {
permissionMode: SessionHeader['permissionMode'];
labels?: readonly string[];
},
) {
const current = await this.readHeader(sessionId);
const permissionMode =
projection?.permissionMode ??
(kind === 'bypass'
? 'bypass'
: current.permissionMode === 'bypass'
? 'ask'
: current.permissionMode);
await this.updateHeader(sessionId, {
permissionMode,
...(projection?.labels ? { labels: [...projection.labels] } : {}),
});
const boundary = createGenesisExecutionBoundary(permissionMode);
this.executionBoundaries.set(sessionId, boundary);
return boundary;
}
async readExecutionBoundary(sessionId: string): Promise<ExecutionBoundary> {
const boundary = this.executionBoundaries.get(sessionId);
if (!boundary) throw new Error(`Unknown session ${sessionId}`);
return boundary;
}
async createSandboxBoundaryRequest(
input: CreateSandboxBoundaryRequest,
): Promise<SandboxBoundaryRequest> {
const boundary = await this.readExecutionBoundary(input.sessionId);
const request: SandboxBoundaryRequest = {
...input,
status: 'pending',
baseRevision: boundary.revision,
createdAt: 1,
};
this.sandboxBoundaryRequests.set(`${input.sessionId}:${input.requestId}`, request);
return request;
}
async listPendingSandboxBoundaryRequests(sessionId: string): Promise<SandboxBoundaryRequest[]> {
return [...this.sandboxBoundaryRequests.values()].filter(
(request) => request.sessionId === sessionId && request.status === 'pending',
);
}
async listSandboxBoundaryRestartClosures(sessionId: string): Promise<SandboxBoundaryRequest[]> {
return [...this.sandboxBoundaryRequests.values()].filter(
(request) => request.sessionId === sessionId && isSandboxBoundaryRestartClosure(request),
);
}
async hasExplicitSandboxBoundaryDenial(
identities: readonly { sessionId: string; runId: string; turnId: string }[],
): Promise<boolean> {
return [...this.sandboxBoundaryRequests.values()].some(
(request) =>
request.status === 'denied' &&
request.outcomeReason === 'client_denied' &&
identities.some(
(identity) =>
identity.sessionId === request.sessionId &&
identity.runId === request.runId &&
identity.turnId === request.turnId,
),
);
}
async settleSandboxBoundaryRequest(
input: SettleSandboxBoundaryRequest,
): Promise<SandboxBoundarySettlement> {
const key = `${input.sessionId}:${input.requestId}`;
const request = this.sandboxBoundaryRequests.get(key);
if (!request) throw new Error(`Unknown sandbox boundary request ${input.requestId}`);
const settled =
request.status === 'pending'
? {
...request,
status: input.decision === 'allow' ? ('approved' as const) : ('denied' as const),
settledAt: 2,
...(input.decision === 'deny'
? { outcomeReason: input.closureReason ?? 'client_denied' }
: {}),
}
: request;
this.sandboxBoundaryRequests.set(key, settled);
return {
request: settled,
boundary: await this.readExecutionBoundary(input.sessionId),
changed: false,
};
}
async list(_filter?: SessionListFilter): Promise<SessionSummary[]> {
return Array.from(this.headers.values()).map(headerToSummary);
}
async readHeader(sessionId: string): Promise<SessionHeader> {
const gate = this.nextReadHeaderGate;
if (gate) {
this.nextReadHeaderGate = undefined;
gate.started.release();
await gate.release.promise;
}
const header = this.headers.get(sessionId);
if (!header) {
const error = new Error(`Unknown session ${sessionId}`) as NodeJS.ErrnoException;
error.code = 'ENOENT';
throw error;
}
return header;
}
async readMessages(sessionId: string): Promise<StoredMessage[]> {
const remainingFailures = this.failNextReadMessagesFor.get(sessionId) ?? 0;
if (remainingFailures > 0) {
if (remainingFailures === 1) this.failNextReadMessagesFor.delete(sessionId);
else this.failNextReadMessagesFor.set(sessionId, remainingFailures - 1);
throw new Error(`Cannot read messages for ${sessionId}`);
}
if (this.failReadMessagesFor.has(sessionId))
throw new Error(`Cannot read messages for ${sessionId}`);
return [...(this.messages.get(sessionId) ?? [])];
}
async readMessagesAfter(
sessionId: string,
request: { afterSequence?: number; maxMessages: number },
): Promise<{
records: readonly { sequence: number; message: StoredMessage }[];
highWaterSequence: number | null;
}> {
const all = await this.readMessages(sessionId);
return {
records: all
.map((message, sequence) => ({ sequence, message }))
.filter(({ sequence }) => sequence > (request.afterSequence ?? -1))
.slice(0, request.maxMessages),
highWaterSequence: all.length > 0 ? all.length - 1 : null,
};
}
async listTurns(sessionId: string): Promise<TurnRecord[]> {
if (this.failListTurnsFor.has(sessionId)) throw new Error(`Cannot list turns for ${sessionId}`);
return deriveTurnRecords(await this.readMessages(sessionId));
}
async appendMessage(sessionId: string, message: StoredMessage): Promise<void> {
await this.appendMessages(sessionId, [message]);
}
async appendMessages(sessionId: string, messages: StoredMessage[]): Promise<void> {
this.messages.set(sessionId, [...(this.messages.get(sessionId) ?? []), ...messages]);
}
async updateHeader(sessionId: string, patch: Partial<SessionHeader>): Promise<SessionHeader> {
if (this.failUpdateHeaderFor.has(sessionId))
throw new Error(`Cannot update header for ${sessionId}`);
const current = await this.readHeader(sessionId);
const next = { ...current, ...patch };
this.headers.set(sessionId, next);
return next;
}
async setFlagged(sessionId: string, isFlagged: boolean): Promise<void> {
await this.updateHeader(sessionId, { isFlagged });
}
async rename(sessionId: string, name: string): Promise<void> {
await this.updateHeader(sessionId, { name, titleIsManual: true });
}
async remove(sessionId: string): Promise<void> {
this.headers.delete(sessionId);
this.messages.delete(sessionId);
this.executionBoundaries.delete(sessionId);
}
}
class VersionedConfigurationMemorySessionStore extends MemorySessionStore {
private readonly revisions = new Map<string, number>();
private readonly forcedBoundaries = new Map<string, ExecutionBoundary>();
nextConfigurationUpdateGate: { started: Gate; release: Gate } | undefined;
forceBoundary(sessionId: string, boundary: ExecutionBoundary): void {
this.forcedBoundaries.set(sessionId, boundary);
}
override async readExecutionBoundary(sessionId: string): Promise<ExecutionBoundary> {
return this.forcedBoundaries.get(sessionId) ?? super.readExecutionBoundary(sessionId);
}
override async create(
input: CreateSessionInput,
initialBoundary?: ExecutionBoundary,
): Promise<SessionHeader> {
const header = await super.create(input, initialBoundary);
this.revisions.set(header.id, 1);
return header;
}
async readHeaderRecordSnapshot(sessionId: string): Promise<VersionedSessionHeader> {
return {
header: await this.readHeader(sessionId),
revision: this.revisions.get(sessionId) ?? 1,
committedAt: 1,
};
}
async updateSessionConfiguration(
sessionId: string,
input: SessionConfigurationStoreUpdate,
): Promise<VersionedSessionHeader> {
const gate = this.nextConfigurationUpdateGate;
if (gate) {
this.nextConfigurationUpdateGate = undefined;
gate.started.release();
await gate.release.promise;
}
const revision = this.revisions.get(sessionId) ?? 1;
if (revision !== input.expectedVersion) {
throw new Error('injected configuration revision conflict');
}
await super.setExecutionBoundaryKind(
sessionId,
input.configuration.permissionMode === 'bypass' ? 'bypass' : 'managed',
{
permissionMode: input.configuration.permissionMode,
labels: input.configuration.labels,
},
);
const header = await super.updateHeader(sessionId, {
...input.configuration,
labels: [...input.configuration.labels],
...(input.lifecycle.kind === 'clear_connection_block'
? {
status: 'active',
blockedReason: undefined,
statusUpdatedAt: input.lifecycle.statusUpdatedAt,
}
: {}),
});
this.forcedBoundaries.delete(sessionId);
this.revisions.set(sessionId, revision + 1);
return { header, revision: revision + 1, committedAt: revision + 1 };
}
async updateHeaderVersioned(
sessionId: string,
patch: Partial<SessionHeader>,
expectedRevision: number,
): Promise<VersionedSessionHeader> {
const revision = this.revisions.get(sessionId) ?? 1;
if (revision !== expectedRevision) {
throw new SessionConfigurationRevisionConflictError(expectedRevision, revision);
}
const header = await super.updateHeader(sessionId, patch);
this.revisions.set(sessionId, revision + 1);
return { header, revision: revision + 1, committedAt: revision + 1 };
}
}
class AtomicBoundaryMemorySessionStore extends MemorySessionStore {
failAppends = false;
readonly boundaryCalls: Array<{
sessionId: string;
kind: 'managed' | 'bypass';
projection:
| {
permissionMode: SessionHeader['permissionMode'];
labels?: readonly string[];
}
| undefined;
}> = [];
private readonly boundaries = new Map<string, ExecutionBoundary>();
private projectingBoundary = false;
forceBoundary(sessionId: string, boundary: ExecutionBoundary): void {
this.boundaries.set(sessionId, boundary);
}
override async readExecutionBoundary(sessionId: string): Promise<ExecutionBoundary> {
return this.boundaries.get(sessionId) ?? super.readExecutionBoundary(sessionId);
}
async setExecutionBoundaryKind(
sessionId: string,
kind: 'managed' | 'bypass',
projection?: {
permissionMode: SessionHeader['permissionMode'];
labels?: readonly string[];
},
) {
this.boundaryCalls.push({ sessionId, kind, projection });
const current = await this.readHeader(sessionId);
const permissionMode =
projection?.permissionMode ??
(kind === 'bypass'
? 'bypass'
: current.permissionMode === 'bypass'
? 'ask'
: current.permissionMode);
this.projectingBoundary = true;
try {
await super.updateHeader(sessionId, {
permissionMode,
...(projection?.labels ? { labels: [...projection.labels] } : {}),
});
} finally {
this.projectingBoundary = false;
}
const boundary = {
...createGenesisExecutionBoundary(permissionMode),
revision: 1,
};
this.boundaries.set(sessionId, boundary);
return boundary;
}
override async updateHeader(
sessionId: string,
patch: Partial<SessionHeader>,
): Promise<SessionHeader> {
if (!this.projectingBoundary && Object.hasOwn(patch, 'permissionMode')) {
throw new Error('permissionMode must be projected by the boundary transition');
}
return super.updateHeader(sessionId, patch);
}
override async appendMessage(sessionId: string, message: StoredMessage): Promise<void> {
if (this.failAppends) throw new Error('audit append failed');
return super.appendMessage(sessionId, message);
}
}
class MemoryAgentRunStore
implements AgentRunStore, RuntimeEventStore, RuntimeContinuationAuthorityStore
{
readonly continuationAuthorityCapability = RUNTIME_CONTINUATION_AUTHORITY_V1;
listSessionRunsCalls = 0;
readEventsCalls = 0;
private events = new Map<string, AgentRunEvent[]>();
private runtimeEvents = new Map<string, RuntimeEvent[]>();
private runtimeEventEntries: RuntimeEvent[] = [];
private continuationClaims = new Map<string, ContinuationClaimV1>();
private continuationStartKinds = new Map<string, 'runtime_admission' | 'claim_repair'>();
private runtimeEventAppendCount = 0;
private rootTurnAdmissions = new Map<
string,
{ runId: string; userMessageId: string | null; execution: RootExecutionDescriptor }
>();
constructor(
private readonly options: {
failRuntimeEventAppends?: boolean;
failRuntimeEventAppendAfter?: number;
failRuntimeEventReads?: boolean;
failContinuationClaimReads?: boolean;
failContinuationCreate?: boolean;
beforeListSessionRuns?: (sessionId: string) => Promise<void> | void;
beforeRuntimeEventRead?: (sessionId: string, runId: string) => Promise<void> | void;
beforeRuntimeEventAppend?: (
sessionId: string,
runId: string,
event: RuntimeEvent,
options?: { durable?: boolean },
) => Promise<void> | void;
beforeAgentRunEventAppend?: (
sessionId: string,
runId: string,
event: AgentRunEvent,
) => Promise<void> | void;
beforeAgentRunEventRead?: (sessionId: string, runId: string) => Promise<void> | void;
} = {},
) {}
seedRootTurnAdmission(
sessionId: string,
turnId: string,
admission: { runId: string; userMessageId: string | null; execution: RootExecutionDescriptor },
): void {
this.rootTurnAdmissions.set(key(sessionId, turnId), admission);
}
async readRootTurnAdmission(
sessionId: string,
turnId: string,
): Promise<
{ runId: string; userMessageId: string | null; execution: RootExecutionDescriptor } | undefined
> {
return this.rootTurnAdmissions.get(key(sessionId, turnId));
}
async appendEvent(sessionId: string, runId: string, event: AgentRunEvent): Promise<void> {
await this.options.beforeAgentRunEventAppend?.(sessionId, runId, event);
const eventKey = key(sessionId, runId);
this.events.set(eventKey, [...(this.events.get(eventKey) ?? []), copyEvent(event)]);
}
async readEvents(sessionId: string, runId: string): Promise<AgentRunEvent[]> {
this.readEventsCalls += 1;
await this.options.beforeAgentRunEventRead?.(sessionId, runId);
return (this.events.get(key(sessionId, runId)) ?? []).map(copyEvent);
}
async appendRuntimeEvent(
sessionId: string,
runId: string,
event: RuntimeEvent,
options?: { durable?: boolean },
): Promise<void> {
await this.options.beforeRuntimeEventAppend?.(sessionId, runId, event, options);
if (this.options.failRuntimeEventAppends) throw new Error('runtime event append failed');
this.runtimeEventAppendCount += 1;
if (
this.options.failRuntimeEventAppendAfter !== undefined &&
this.runtimeEventAppendCount > this.options.failRuntimeEventAppendAfter
) {
this.options.failRuntimeEventAppendAfter = undefined;
throw new Error('runtime event append failed');
}
const existing = this.runtimeEvents.get(key(sessionId, runId)) ?? [];
// Same identity, same event: the store writes an id once, so a retry of an
// interrupted append lands on what is already there instead of a copy.
if (event.partial !== true && existing.some((candidate) => candidate.id === event.id)) return;
assertDoubleRunNotSealed(existing, event);
this.seedRuntimeEvent(sessionId, runId, event);
}
/**
* Put an event into the ledger underneath the seal.
*
* A test that needs a ledger shape the store would refuse to write has to
* assemble it below the store, not through the API whose contract forbids it.
*/
seedRuntimeEvent(sessionId: string, runId: string, event: RuntimeEvent): void {
const eventKey = key(sessionId, runId);
this.runtimeEvents.set(eventKey, [
...(this.runtimeEvents.get(eventKey) ?? []),
copyRuntimeEvent(event),
]);
if (event.partial !== true && !this.runtimeEventEntries.some(({ id }) => id === event.id)) {
this.runtimeEventEntries.push(copyRuntimeEvent(event));
}
}
async ensureTerminalRuntimeEventDurable(
sessionId: string,
runId: string,
event: RuntimeEvent,
): Promise<void> {
const existing = (this.runtimeEvents.get(key(sessionId, runId)) ?? []).find(
(candidate) => candidate.id === event.id,
);
if (!existing) {
await this.appendRuntimeEvent(sessionId, runId, event);
return;
}
if (JSON.stringify(existing) !== JSON.stringify(event)) {
throw new Error(`RuntimeEvent ${event.id} does not match the durable ledger record`);
}
}
async readRuntimeEvents(sessionId: string, runId: string): Promise<RuntimeEvent[]> {
if (this.options.failRuntimeEventReads) throw new Error('runtime event read failed');
await this.options.beforeRuntimeEventRead?.(sessionId, runId);
return (this.runtimeEvents.get(key(sessionId, runId)) ?? []).map(copyRuntimeEvent);
}
async readImmutableRuntimeEvents(sessionId: string, runId: string): Promise<RuntimeEvent[]> {
if (this.options.failRuntimeEventReads) throw new Error('runtime event read failed');
await this.options.beforeRuntimeEventRead?.(sessionId, runId);
return (this.runtimeEvents.get(key(sessionId, runId)) ?? [])
.filter((event) => event.partial !== true)
.map(copyRuntimeEvent);
}
async readSessionRuntimeEventEntries(sessionId: string) {
return this.runtimeEventEntries
.filter((event) => event.sessionId === sessionId)
.map((event, index) => ({ ordinal: index + 1, event: copyRuntimeEvent(event) }));
}
// Ordinals here are positions in the append log, read off it every time, so
// there is nothing stored for a resequence to move.
async resequenceSessionEventOrdinals(_sessionId: string): Promise<void> {}
replaceRuntimeEvent(
sessionId: string,
runId: string,
eventId: string,
replacement: RuntimeEvent,
): void {
const eventKey = key(sessionId, runId);
const events = this.runtimeEvents.get(eventKey) ?? [];
const index = events.findIndex((event) => event.id === eventId);
if (index < 0) throw new Error(`Unknown RuntimeEvent ${eventId}`);
const next = [...events];
next[index] = copyRuntimeEvent(replacement);
this.runtimeEvents.set(eventKey, next);
}
async readImmutableRuntimePrefix(input: {
sessionId: string;
runId: string;
upToEventSeq?: number;
}) {
const events = await this.readImmutableRuntimeEvents(input.sessionId, input.runId);
const selected =
input.upToEventSeq === undefined ? events : events.slice(0, input.upToEventSeq);
const first = selected[0];
if (!first || selected.length !== (input.upToEventSeq ?? selected.length)) {
throw new Error('immutable RuntimeEvent prefix is unavailable');
}
return buildImmutableRuntimePrefix(
{
sessionId: first.sessionId,
invocationId: first.invocationId,
runId: first.runId,
turnId: first.turnId,
},
selected.map((event, index) => ({ eventSeq: index + 1, event })),
);
}
async claimContinuation(input: { claim: ContinuationClaimV1 }) {
if (this.options.failContinuationCreate) {
throw new Error('continuation claim create failed');
}
const claim = decodeContinuationClaim(input.claim);
const existing = this.continuationClaims.get(claim.boundaryDigest);
if (existing) return { kind: 'existing' as const, claim: existing };
const conflict = [...this.continuationClaims.values()].find(
(candidate) =>
candidate.target.invocationId === claim.target.invocationId ||
candidate.target.runId === claim.target.runId ||
(candidate.target.sessionId === claim.target.sessionId &&
candidate.target.turnId === claim.target.turnId),
);
if (conflict) return { kind: 'conflict' as const, claim: conflict };
this.continuationClaims.set(claim.boundaryDigest, claim);
return { kind: 'acquired' as const, claim };
}
async readContinuationClaimByBoundary(
boundaryDigest: RuntimeBoundaryDigest,
): Promise<ContinuationClaimV1 | undefined> {
return this.continuationClaims.get(boundaryDigest);
}
async readContinuationClaimStateByBoundary(boundaryDigest: RuntimeBoundaryDigest) {
if (this.options.failContinuationClaimReads) {
throw new Error('continuation authority read failed');
}
const claim = this.continuationClaims.get(boundaryDigest);
if (!claim) return undefined;
const startEventId = this.runtimeEvents
.get(key(claim.target.sessionId, claim.target.runId))
?.find((event) => event.actions?.continuationStart?.claimId === claim.claimId)?.id;
const startKind = this.continuationStartKinds.get(boundaryDigest);
return {
claim,
...(startEventId && startKind ? { startEventId, startKind } : {}),
};
}
async listContinuationClaimsForRecovery(sessionId: string) {
if (this.options.failContinuationClaimReads) {
throw new Error('continuation authority read failed');
}
const states = [];
for (const claim of this.continuationClaims.values()) {
if (claim.target.sessionId !== sessionId) continue;
states.push((await this.readContinuationClaimStateByBoundary(claim.boundaryDigest))!);
}
return states;
}
async commitContinuationStart(input: { claim: ContinuationClaimV1; event: RuntimeEvent }) {
return this.commitContinuationStartOfKind(input, 'runtime_admission');
}
async commitContinuationRepairStart(input: { claim: ContinuationClaimV1; event: RuntimeEvent }) {
return this.commitContinuationStartOfKind(input, 'claim_repair');
}
private async commitContinuationStartOfKind(
input: { claim: ContinuationClaimV1; event: RuntimeEvent },
startKind: 'runtime_admission' | 'claim_repair',
) {
if (input.event.actions?.continuationStart?.provenance !== startKind) {
throw new Error('continuation start kind mismatch');
}
const existing = await this.readContinuationClaimStateByBoundary(input.claim.boundaryDigest);
if (existing?.startEventId) {
if (existing.startEventId !== input.event.id || existing.startKind !== startKind) {
throw new Error('continuation claim already has a different start');
}
return { created: false, runtimeEventSeq: 1 };
}
await this.appendRuntimeEvent(
input.claim.target.sessionId,
input.claim.target.runId,
input.event,
);
this.continuationStartKinds.set(input.claim.boundaryDigest, startKind);
return { created: true, runtimeEventSeq: 1 };
}
async readSessionRuntimeEvents(sessionId: string): Promise<RuntimeEvent[]> {
const ordered: Array<{ event: RuntimeEvent; runId: string; eventIndex: number }> = [];
for (const [eventKey, events] of this.runtimeEvents.entries()) {
const [eventSessionId, runId] = eventKey.split(':');
if (eventSessionId !== sessionId || !runId) continue;
events.forEach((event, eventIndex) =>
ordered.push({ event: copyRuntimeEvent(event), runId, eventIndex }),
);
}
ordered.sort(
(a, b) =>
a.event.ts - b.event.ts ||
a.runId.localeCompare(b.runId) ||
a.eventIndex - b.eventIndex ||
a.event.id.localeCompare(b.event.id),
);
return ordered.map((item) => item.event);
}
async listSessionInvocations(sessionId: string): Promise<RuntimeInvocationRecord[]> {
this.listSessionRunsCalls += 1;
await this.options.beforeListSessionRuns?.(sessionId);
return runtimeInvocationsFromSessionEvents(
sessionId,
await this.readSessionRuntimeEvents(sessionId),
);
}
}
class ContinuationClaimBarrierRunStore extends MemoryAgentRunStore {
private continuationClaimBarrierArmed = false;
private markContinuationClaimRead: (() => void) | undefined;
private releaseContinuationClaimReadWaiter: (() => void) | undefined;
private readonly continuationClaimRead = new Promise<void>((resolve) => {
this.markContinuationClaimRead = resolve;
});
private readonly continuationClaimRelease = new Promise<void>((resolve) => {
this.releaseContinuationClaimReadWaiter = resolve;
});
armContinuationClaimBarrier(): void {
this.continuationClaimBarrierArmed = true;
}
async waitForContinuationClaimRead(): Promise<void> {
await this.continuationClaimRead;
}
releaseContinuationClaimRead(): void {
this.releaseContinuationClaimReadWaiter?.();
}
override async listSessionInvocations(sessionId: string): Promise<RuntimeInvocationRecord[]> {
const snapshot = await super.listSessionInvocations(sessionId);
if (!this.continuationClaimBarrierArmed) return snapshot;
this.continuationClaimBarrierArmed = false;
this.markContinuationClaimRead?.();
await this.continuationClaimRelease;
return snapshot;
}
}
/** Yields a forged queue_update before completing — round-6 R3's attacker. */
class ForgingQueueBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
constructor(ctx: BackendFactoryContext) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
yield {
type: 'queue_update',
id: 'forged-queue-update',
turnId: input.turnId,
ts: 1,
steering: ['forged pending message'],
followup: [],
};
yield {
type: 'text_complete',
id: `${input.turnId}-final`,
turnId: input.turnId,
ts: 2,
messageId: `${input.turnId}-m`,
text: 'ok',
};
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 3,
stopReason: 'end_turn',
};
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {}
}
class ProviderRetryProgressBackend implements AgentBackend {
readonly kind = 'ai-sdk' as const;
readonly sessionId: string;
constructor(ctx: BackendFactoryContext) {
this.sessionId = ctx.sessionId;
}
async *send(input: BackendSendInput): AsyncIterable<SessionEvent> {
yield {
type: 'provider_retry',
id: 'retry-scheduled',
turnId: input.turnId,
ts: 1,
phase: 'scheduled',
attempt: 2,
maxAttempts: 10,
delayMs: 1_000,
reason: 'rate_limit',
};
yield {
type: 'provider_retry',
id: 'retry-started',
turnId: input.turnId,
ts: 2,
phase: 'started',
attempt: 2,
maxAttempts: 10,
reason: 'rate_limit',
};
yield {
type: 'text_complete',
id: `${input.turnId}-final`,
turnId: input.turnId,
ts: 3,
messageId: `${input.turnId}-m`,
text: 'ok',
};
yield {
type: 'complete',
id: `${input.turnId}-complete`,
turnId: input.turnId,
ts: 4,
stopReason: 'end_turn',
};
}
async stop(): Promise<void> {}
async respondToSandboxBoundary(_decision: SandboxBoundaryResponse): Promise<void> {}
async dispose(): Promise<void> {}
}
class ReverseOrderedAgentRunStore extends MemoryAgentRunStore {
override async listSessionInvocations(sessionId: string): Promise<RuntimeInvocationRecord[]> {
return (await super.listSessionInvocations(sessionId)).reverse();
}
}
class OrderingAgentRunStore extends MemoryAgentRunStore {
operations: string[] = [];
override async appendRuntimeEvent(
sessionId: string,
runId: string,
event: RuntimeEvent,
): Promise<void> {
await super.appendRuntimeEvent(sessionId, runId, event);
if (isTerminalRuntimeEvent(event)) this.operations.push('terminalRuntimeEvent');
}
}
class MissingCheckpointProjectionAgentRunStore extends MemoryAgentRunStore {
repairedProjection: AgentRunEvent | null | undefined;
async readEventProjection(): Promise<undefined> {
return undefined;
}
async readEventLedgerRevision(): Promise<string> {
return 'missing-checkpoint-projection-test-revision';
}
async repairEventProjection(
_sessionId: string,
_type: AgentRunEvent['type'],
event: AgentRunEvent | null,
_options: { ifLedgerRevision: string; replaceEventId?: string },
): Promise<void> {
this.repairedProjection = event;
}
}
class MemoryRuntimeEventStore implements RuntimeEventStore {
private runtimeEvents = new Map<string, RuntimeEvent[]>();
private runtimeEventEntries: RuntimeEvent[] = [];
constructor(
private readonly options: {
failRuntimeEventAppends?: boolean;
failRuntimeEventReads?: boolean;
} = {},
) {}
async appendRuntimeEvent(sessionId: string, runId: string, event: RuntimeEvent): Promise<void> {
if (this.options.failRuntimeEventAppends) throw new Error('runtime event append failed');
assertDoubleRunNotSealed(this.runtimeEvents.get(key(sessionId, runId)) ?? [], event);
this.seedRuntimeEvent(sessionId, runId, event);
}
/**
* Put an event into the ledger underneath the seal.
*
* A test that needs a ledger shape the store would refuse to write has to
* assemble it below the store, not through the API whose contract forbids it.
*/
seedRuntimeEvent(sessionId: string, runId: string, event: RuntimeEvent): void {
const eventKey = key(sessionId, runId);
this.runtimeEvents.set(eventKey, [
...(this.runtimeEvents.get(eventKey) ?? []),
copyRuntimeEvent(event),
]);
if (event.partial !== true && !this.runtimeEventEntries.some(({ id }) => id === event.id)) {
this.runtimeEventEntries.push(copyRuntimeEvent(event));
}
}
async ensureTerminalRuntimeEventDurable(
sessionId: string,
runId: string,
event: RuntimeEvent,
): Promise<void> {
const existing = (this.runtimeEvents.get(key(sessionId, runId)) ?? []).find(
(candidate) => candidate.id === event.id,
);
if (!existing) {
await this.appendRuntimeEvent(sessionId, runId, event);
return;
}
if (JSON.stringify(existing) !== JSON.stringify(event)) {
throw new Error(`RuntimeEvent ${event.id} does not match the durable ledger record`);
}
}
async readRuntimeEvents(sessionId: string, runId: string): Promise<RuntimeEvent[]> {
if (this.options.failRuntimeEventReads) throw new Error('runtime event read failed');
return (this.runtimeEvents.get(key(sessionId, runId)) ?? []).map(copyRuntimeEvent);
}
async readSessionRuntimeEventEntries(sessionId: string) {
return this.runtimeEventEntries
.filter((event) => event.sessionId === sessionId)
.map((event, index) => ({ ordinal: index + 1, event: copyRuntimeEvent(event) }));
}
// Ordinals here are positions in the append log, read off it every time, so
// there is nothing stored for a resequence to move.
async resequenceSessionEventOrdinals(_sessionId: string): Promise<void> {}
async readSessionRuntimeEvents(sessionId: string): Promise<RuntimeEvent[]> {
const ordered: Array<{ event: RuntimeEvent; runId: string; eventIndex: number }> = [];
for (const [eventKey, events] of this.runtimeEvents.entries()) {
const [eventSessionId, runId] = eventKey.split(':');
if (eventSessionId !== sessionId || !runId) continue;
events.forEach((event, eventIndex) =>
ordered.push({ event: copyRuntimeEvent(event), runId, eventIndex }),
);
}
ordered.sort(
(a, b) =>
a.event.ts - b.event.ts ||
a.runId.localeCompare(b.runId) ||
a.eventIndex - b.eventIndex ||
a.event.id.localeCompare(b.event.id),
);
return ordered.map((item) => item.event);
}
async listSessionInvocations(sessionId: string): Promise<RuntimeInvocationRecord[]> {
return runtimeInvocationsFromSessionEvents(
sessionId,
await this.readSessionRuntimeEvents(sessionId),
);
}
}
interface Gate {
promise: Promise<void>;
release(): void;
}
function makeGate(): Gate {
let release: () => void = () => {};
const promise = new Promise<void>((resolve) => {
release = resolve;
});
return { promise, release };
}
function makeTestRuntimePolicyGate(): {
runBackendActivation<T>(operation: () => Promise<T> | T): Promise<T>;
runMutation<T>(operation: () => Promise<T>): Promise<T>;
} {
const activeActivations = new Set<Promise<void>>();
return {
async runBackendActivation<T>(operation: () => Promise<T> | T): Promise<T> {
const completion = makeGate();
activeActivations.add(completion.promise);
try {
return await operation();
} finally {
completion.release();
activeActivations.delete(completion.promise);
}
},
async runMutation<T>(operation: () => Promise<T>): Promise<T> {
await Promise.all([...activeActivations]);
return operation();
},
};
}
function hostedRootAuthority(): RuntimeHostedRootAuthority {
return {
bindRun: (identity) => ({
...identity,
pull: async () => [],
ack: () => {},
nack: () => {},
release: () => {},
}),
executeRoot: async (input) => {
for await (const event of input.start({
runId: input.runId,
userMessageId: input.userMessageId,
onRunStarted: () => input.onReady?.(),
})) {
input.onEvent?.(event);
}
},
stopRoot: async () => {},
stopSession: async () => {},
};
}
function makeInput(overrides: Partial<CreateSessionInput> = {}): CreateSessionInput {
return {
cwd: '/tmp/cwd',
llmConnectionSlug: 'fake',
model: 'fake-model',
permissionMode: 'ask',
name: 'Session',
labels: [],
...overrides,
};
}
function configurationForHeader(
header: SessionHeader,
overrides: Partial<SessionConfigurationTransitionRequest['configuration']> = {},
): SessionConfigurationTransitionRequest['configuration'] {
return {
backend: header.backend,
...(header.llmConnectionId === undefined ? {} : { llmConnectionId: header.llmConnectionId }),
llmConnectionSlug: header.llmConnectionSlug,
connectionLocked: header.connectionLocked,
model: header.model,
thinkingLevel: header.thinkingLevel,
permissionMode: header.permissionMode,
collaborationMode: header.collaborationMode ?? 'agent',
orchestrationMode: header.orchestrationMode ?? 'default',
...overrides,
};
}
function createGraphOperatorSession(
store: MemorySessionStore,
parentSessionId: string,
): Promise<SessionHeader> {
return store.create(
makeInput({
name: 'Graph operator',
permissionMode: 'explore',
collaborationMode: 'agent',
orchestrationMode: 'default',
subagentParent: {
kind: 'subagent',
parentSessionId,
spawnedBy: {
parentRunId: 'supervisor-run',
parentTurnId: 'supervisor-turn',
toolCallId: 'graph-operator-create',
},
lifecycle: 'foreground',
},
subagentRuntime: {
schemaVersion: 1,
definitionVersion: LOCAL_READ_AGENT_DEFINITION.definitionVersion,
agentId: LOCAL_READ_AGENT_ID,
agentName: LOCAL_READ_AGENT_DEFINITION.name,
profile: LOCAL_READ_AGENT_PROFILE,
systemPrompt: LOCAL_READ_AGENT_DEFINITION.systemPrompt,
toolNames: [...LOCAL_READ_AGENT_DEFINITION.tools],
categoryPolicy: {},
},
}),
);
}
function graphIntentClaim(
overrides: Partial<AgentGraphIntentClaim> = {},
prompt = 'test graph prompt',
): AgentGraphIntentClaim {
const claim: AgentGraphIntentClaim = {
schemaVersion: 1,
claimId: `graph_claim_${'a'.repeat(32)}`,
graphId: 'graph-1',
intentId: `graph_intent_${'b'.repeat(32)}`,
intentFingerprint: '',
readinessContextFingerprint: `sha256:${'d'.repeat(64)}`,
targetOperatorId: 'operator-1',
targetSessionId: 'session-child',
targetTurnId: 'graph-turn',
targetRunId: 'graph-run',
claimedAt: 10,
...overrides,
};
const intent = graphRunnableIntentForClaim(claim);
return Object.freeze({
...claim,
intentFingerprint:
overrides.intentFingerprint ??
fingerprintAgentGraphRunnableIntent({
intent,
executionInput: { prompt },
}),
});
}
function graphExecutionInput(claim: AgentGraphIntentClaim, prompt: string) {
const intent = graphRunnableIntentForClaim(claim);
const storedClaim = Object.freeze({ ...claim });
const claimStore: AgentGraphIntentClaimStore = {
async claimAgentGraphIntent() {
throw new Error('test claim store is read-only');
},
async readAgentGraphIntentClaim(graphId, intentId) {
return graphId === storedClaim.graphId && intentId === storedClaim.intentId
? storedClaim
: undefined;
},
async listAgentGraphIntentClaims(graphId) {
return !graphId || graphId === storedClaim.graphId ? [storedClaim] : [];
},
};
return {
claimStore,
intent,
graphId: claim.graphId,
intentId: claim.intentId,
prompt,
};
}
function graphRunnableIntentForClaim(claim: AgentGraphIntentClaim): AgentGraphRunnableIntent {
return {
schemaVersion: 1,
intentId: claim.intentId,
graphId: claim.graphId,
readinessContextFingerprint: claim.readinessContextFingerprint,
policyFingerprint: `sha256:${'e'.repeat(64)}`,
readinessId: 'readiness-1',
operatorId: claim.targetOperatorId,
targetSessionId: claim.targetSessionId,
policyKind: 'map',
triggerRouteIds: ['route-1'],
triggerRecordIds: ['record-1'],
};
}
async function createQueuedGraphScenario(firstAbortSignal?: AbortSignal) {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
const activeGate = makeGate();
const firstReady = makeGate();
const stopStarted = makeGate();
let backend!: TestBackend;
backends.register('ai-sdk', (ctx) => {
backend = new (class extends TestBackend {
override async stop(
reason: 'user_stop' | 'redirect',
mode: BackendStopMode = 'immediate',
): Promise<void> {
await super.stop(reason, mode);
stopStarted.release();
activeGate.release();
}
})(ctx, activeGate);
return backend;
});
const manager = new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
newId: nextId(),
now: nextNow(70),
});
const parent = await manager.createSession(makeInput());
const child = await createGraphOperatorSession(store, parent.id);
const firstClaim = graphIntentClaim({ targetSessionId: child.id }, 'first activation');
const secondClaim = graphIntentClaim(
{
claimId: `graph_claim_${'e'.repeat(32)}`,
intentId: `graph_intent_${'f'.repeat(32)}`,
targetSessionId: child.id,
targetTurnId: 'graph-turn-2',
targetRunId: 'graph-run-2',
},
'queued activation',
);
const thirdClaim = graphIntentClaim(
{
claimId: `graph_claim_${'1'.repeat(32)}`,
intentId: `graph_intent_${'2'.repeat(32)}`,
targetSessionId: child.id,
targetTurnId: 'graph-turn-3',
targetRunId: 'graph-run-3',
},
'third activation',
);
const first = manager.runClaimedAgentGraphIntent({
...graphExecutionInput(firstClaim, 'first activation'),
...(firstAbortSignal ? { abortSignal: firstAbortSignal } : {}),
onReady: () => firstReady.release(),
});
await firstReady.promise;
return {
store,
runStore,
manager,
child,
backend,
activeGate,
stopStarted,
first,
claims: [firstClaim, secondClaim, thirdClaim] as const,
};
}
function hostedGraphExecutionCapability(
claims: AgentGraphIntentClaimStore,
runs: MemoryAgentRunStore,
) {
return {
readAgentGraphIntentClaim: (graphId: string, intentId: string) =>
claims.readAgentGraphIntentClaim(graphId, intentId),
async readRootTurnAdmissionIdentity(sessionId: string, turnId: string) {
const admission = await runs.readRootTurnAdmission(sessionId, turnId);
return admission
? { runId: admission.runId, userMessageId: admission.userMessageId }
: undefined;
},
};
}
function testTool(name: string): MakaTool {
return {
name,
description: `${name} test tool`,
parameters: {},
impl: async () => ({ ok: true }),
};
}
function makeRunHeader(overrides: Partial<TestRunHeader> = {}): TestRunHeader {
return {
runId: 'run-1',
sessionId: 'session-1',
turnId: 'turn-1',
status: 'running',
backendKind: 'ai-sdk',
llmConnectionSlug: 'fake',
modelId: 'fake-model',
cwd: '/tmp/cwd',
permissionMode: 'ask',
createdAt: 10,
updatedAt: 10,
...overrides,
};
}
/**
* The facts a test states about a run it is seeding.
*
* Deliberately not a stored record: `seedInvocationFromHeader` turns it into
* the opening fact and, when the test says the run ended, the terminal event
* that ends it. Nothing keeps this shape after the seed.
*/
interface TestRunHeader {
runId: string;
sessionId: string;
turnId: string;
invocationId?: string;
status: 'created' | 'running' | 'waiting_for_user' | 'completed' | 'failed' | 'cancelled';
backendKind: PersistedBackendKind;
llmConnectionId?: string;
llmConnectionSlug: string;
modelId: string;
providerStateIdentity?: `sha256:${string}`;
cwd: string;
workspaceIdentity?: string;
permissionMode: PermissionMode;
collaborationMode?: 'agent' | 'plan';
orchestrationMode?: 'default' | 'graph' | 'swarm';
orchestrationSource?: 'session' | 'turn_override';
agentSwarmAuthorization?: 'none' | 'session_mode' | 'turn_override';
toolMode?: ToolMode;
createdAt: number;
updatedAt: number;
completedAt?: number;
failureClass?: string;
failureMessage?: string;
abortSource?: 'stop_button' | 'graph_supervisor';
goalId?: string;
scheduledTaskId?: string;
legacyAutomationId?: string;
agentGraphWakeId?: string;
agentGraphWakeAttemptId?: string;
parentRunId?: string;
parentTurnId?: string;
parentSessionId?: string;
resumedFromRunId?: string;
retriedFromRunId?: string;
retriedFromTurnId?: string;
regeneratedFromTurnId?: string;
branchOfTurnId?: string;
agentId?: string;
agentName?: string;
continuationSource?: {
sourceInvocationId: string;
sourceRunId: string;
sourceTurnId: string;
sourceRuntimeEventHighWater: number;
claimId?: string;
boundaryDigest?: `sha256:${string}`;
};
}
/** The root authority the seeded header names, defaulting to the user. */
function testInvocationRoot(header: TestRunHeader): RuntimeInvocationRootAuthority {
if (header.goalId) return { kind: 'goal', goalId: header.goalId };
if (header.scheduledTaskId) {
return { kind: 'scheduled_task', scheduledTaskId: header.scheduledTaskId };
}
if (header.legacyAutomationId) {
return { kind: 'legacy_automation', legacyAutomationId: header.legacyAutomationId };
}
if (header.agentGraphWakeId && header.agentGraphWakeAttemptId) {
return {
kind: 'agent_graph_supervisor_wake',
wakeId: header.agentGraphWakeId,
attemptId: header.agentGraphWakeAttemptId,
};
}
return { kind: 'user' };
}
/** Everything the header says about lineage, with the absent edges left out. */
function testInvocationLineage(header: TestRunHeader): RuntimeInvocationLineage {
return {
...(header.parentRunId ? { parentRunId: header.parentRunId } : {}),
...(header.parentTurnId ? { parentTurnId: header.parentTurnId } : {}),
...(header.parentSessionId ? { parentSessionId: header.parentSessionId } : {}),
...(header.resumedFromRunId ? { resumedFromRunId: header.resumedFromRunId } : {}),
...(header.retriedFromRunId ? { retriedFromRunId: header.retriedFromRunId } : {}),
...(header.retriedFromTurnId ? { retriedFromTurnId: header.retriedFromTurnId } : {}),
...(header.regeneratedFromTurnId
? { regeneratedFromTurnId: header.regeneratedFromTurnId }
: {}),
...(header.branchOfTurnId ? { branchOfTurnId: header.branchOfTurnId } : {}),
...(header.agentId ? { agentId: header.agentId } : {}),
...(header.agentName ? { agentName: header.agentName } : {}),
};
}
function testInvocationOpening(header: TestRunHeader): RuntimeEventInvocationOpenedContent {
const lineage = testInvocationLineage(header);
return {
kind: 'invocation_opened',
protocol: 'invocation_opened_v1',
route:
header.llmConnectionId === undefined || header.backendKind === 'plugin-executor'
? {
provenance: 'unknown',
backendKind: header.backendKind,
llmConnectionSlug: header.llmConnectionSlug,
modelId: header.modelId,
}
: {
provenance: 'runtime',
backendKind: header.backendKind,
llmConnectionId: header.llmConnectionId,
llmConnectionSlug: header.llmConnectionSlug,
modelId: header.modelId,
...(header.providerStateIdentity
? { providerStateIdentity: header.providerStateIdentity }
: {}),
},
configuration: {
cwd: header.cwd,
permissionMode: header.permissionMode,
collaborationMode: header.collaborationMode ?? 'agent',
orchestrationMode: header.orchestrationMode ?? 'default',
orchestrationSource: header.orchestrationSource ?? 'session',
toolMode: header.toolMode ?? DEFAULT_TOOL_MODE,
...(header.agentSwarmAuthorization
? { agentSwarmAuthorization: header.agentSwarmAuthorization }
: {}),
...(header.workspaceIdentity ? { workspaceIdentity: header.workspaceIdentity } : {}),
},
root: testInvocationRoot(header),
source: header.continuationSource
? { kind: 'continuation', ...header.continuationSource }
: { kind: 'fresh' },
...(Object.keys(lineage).length > 0 ? { lineage } : {}),
};
}
/**
* Open the invocation the header describes, and close it when the header says
* the run ended. Seeding writes events because events are all there is.
*/
async function seedInvocationOpening(
store: Pick<RuntimeEventStore, 'appendRuntimeEvent'>,
header: TestRunHeader,
): Promise<void> {
await store.appendRuntimeEvent(
header.sessionId,
header.runId,
buildInvocationOpenedEvent({
id: `${header.runId}-invocation-opened`,
run: runIdentityOf(header),
openedAt: header.createdAt,
opening: testInvocationOpening(header),
}),
);
}
/** The one event that ends the run, when the header says the run ended. */
async function seedInvocationTerminal(
store: Pick<RuntimeEventStore, 'appendRuntimeEvent'>,
header: TestRunHeader,
): Promise<void> {
if (header.status !== 'completed' && header.status !== 'failed' && header.status !== 'cancelled')
return;
await store.appendRuntimeEvent(header.sessionId, header.runId, {
id: `${header.runId}-terminal`,
...runIdentityOf(header),
ts: header.completedAt ?? header.updatedAt,
partial: false,
role: 'system',
author: 'system',
status: header.status === 'cancelled' ? 'aborted' : header.status,
// The failure class and abort source live where every reader looks for
// them: on the terminal event's own state delta.
actions: {
endInvocation: true,
...(header.failureClass || header.abortSource
? {
stateDelta: {
...(header.failureClass ? { failureClass: header.failureClass } : {}),
...(header.abortSource ? { abortSource: header.abortSource } : {}),
},
}
: {}),
},
...(header.failureMessage
? { content: { kind: 'error' as const, message: header.failureMessage } }
: {}),
});
}
function runIdentityOf(header: TestRunHeader): {
sessionId: string;
invocationId: string;
runId: string;
turnId: string;
} {
return {
sessionId: header.sessionId,
invocationId: header.invocationId ?? header.runId,
runId: header.runId,
turnId: header.turnId,
};
}
async function seedInvocationFromHeader(
store: Pick<RuntimeEventStore, 'appendRuntimeEvent'>,
header: TestRunHeader,
): Promise<TestRunHeader> {
await seedInvocationOpening(store, header);
await seedInvocationTerminal(store, header);
return header;
}
/** The one invocation that opened this run. */
async function readInvocation(
store: Pick<RuntimeEventStore, 'listSessionInvocations'>,
sessionId: string,
runId: string,
): Promise<RuntimeInvocationRecord> {
const found = (await store.listSessionInvocations(sessionId)).find(
(candidate) => candidate.runId === runId,
);
if (!found) {
const error = new Error(`Unknown run ${runId}`) as NodeJS.ErrnoException;
error.code = 'ENOENT';
throw error;
}
return found;
}
function makeRunEvent(overrides: Partial<EmittedAgentRunEvent> = {}): EmittedAgentRunEvent {
return {
type: 'turn_started',
id: `${overrides.runId ?? 'run-1'}-${overrides.type ?? 'turn_started'}-${overrides.ts ?? 10}`,
runId: 'run-1',
sessionId: 'session-1',
turnId: 'turn-1',
ts: 10,
...overrides,
};
}
function makeManagerForReadCutover(
store: MemorySessionStore,
runStore: AgentRunStore & RuntimeEventStore,
): SessionManager {
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
return new SessionManager({
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(6_755),
});
}
async function seedRuntimeReadTurn(input: {
store: MemorySessionStore;
runStore: AgentRunStore & RuntimeEventStore;
sessionId: string;
turnId: string;
runId: string;
userText: string;
assistantText: string;
legacyIdPrefix: string;
/** Says something else in the transcript store, so a reader proves its source. */
legacyUserText?: string;
}): Promise<{ legacyMessages: StoredMessage[]; projectedMessages: StoredMessage[] }> {
const header = makeRunHeader({
sessionId: input.sessionId,
runId: input.runId,
turnId: input.turnId,
status: 'completed',
createdAt: 100,
updatedAt: 103,
completedAt: 103,
});
const events = [
runtimeEvent({
id: `${input.runId}-user-event`,
sessionId: input.sessionId,
runId: input.runId,
turnId: input.turnId,
ts: 101,
role: 'user',
author: 'user',
content: { kind: 'text', text: input.userText },
refs: { storedMessageId: `${input.runId}-projected-user` },
}),
runtimeEvent({
id: `${input.runId}-assistant-event`,
sessionId: input.sessionId,
runId: input.runId,
turnId: input.turnId,
ts: 102,
role: 'model',
author: 'agent',
content: { kind: 'text', text: input.assistantText },
refs: { storedMessageId: `${input.runId}-projected-assistant` },
}),
runtimeEvent({
id: `${input.runId}-complete-event`,
sessionId: input.sessionId,
runId: input.runId,
turnId: input.turnId,
ts: 103,
role: 'system',
author: 'system',
status: 'completed',
actions: { endInvocation: true },
}),
];
const legacyMessages: StoredMessage[] = [
{
type: 'user',
id: `${input.legacyIdPrefix}-user`,
turnId: input.turnId,
ts: 101,
text: input.legacyUserText ?? input.userText,
},
{
type: 'assistant',
id: `${input.legacyIdPrefix}-assistant`,
turnId: input.turnId,
ts: 102,
text: input.assistantText,
modelId: 'fake-model',
},
{
type: 'turn_state',
id: `${input.legacyIdPrefix}-state`,
turnId: input.turnId,
ts: 103,
status: 'completed',
},
];
const projectedMessages: StoredMessage[] = [
{
type: 'user',
id: `${input.runId}-projected-user`,
turnId: input.turnId,
ts: 101,
text: input.userText,
},
{
type: 'assistant',
id: `${input.runId}-projected-assistant`,
turnId: input.turnId,
ts: 102,
text: input.assistantText,
modelId: 'fake-model',
},
{
type: 'turn_state',
id: `${input.runId}-complete-event`,
turnId: input.turnId,
ts: 103,
status: 'completed',
},
];
await input.store.appendMessages(input.sessionId, legacyMessages);
await seedRuntimeRun(input.runStore, header, events);
return { legacyMessages, projectedMessages };
}
async function seedRuntimeReadTurnWithHeader(input: {
store: MemorySessionStore;
runStore: AgentRunStore & RuntimeEventStore;
sessionId: string;
turnId: string;
runId: string;
userText: string;
assistantText: string;
legacyIdPrefix: string;
header: Partial<TestRunHeader>;
tsBase: number;
}): Promise<void> {
const header = makeRunHeader({
sessionId: input.sessionId,
runId: input.runId,
turnId: input.turnId,
status: 'completed',
createdAt: input.tsBase,
updatedAt: input.tsBase + 3,
completedAt: input.tsBase + 3,
...input.header,
});
const events = [
runtimeEvent({
id: `${input.runId}-user-event`,
sessionId: input.sessionId,
runId: input.runId,
turnId: input.turnId,
ts: input.tsBase + 1,
role: 'user',
author: 'user',
content: { kind: 'text', text: input.userText },
refs: { storedMessageId: `${input.runId}-projected-user` },
}),
runtimeEvent({
id: `${input.runId}-assistant-event`,
sessionId: input.sessionId,
runId: input.runId,
turnId: input.turnId,
ts: input.tsBase + 2,
role: 'model',
author: 'agent',
content: { kind: 'text', text: input.assistantText },
refs: { storedMessageId: `${input.runId}-projected-assistant` },
}),
runtimeEvent({
id: `${input.runId}-complete-event`,
sessionId: input.sessionId,
runId: input.runId,
turnId: input.turnId,
ts: input.tsBase + 3,
role: 'system',
author: 'system',
status: 'completed',
actions: { endInvocation: true },
}),
];
await input.store.appendMessages(input.sessionId, [
{
type: 'user',
id: `${input.legacyIdPrefix}-user`,
turnId: input.turnId,
ts: input.tsBase + 1,
text: input.userText,
},
{
type: 'assistant',
id: `${input.legacyIdPrefix}-assistant`,
turnId: input.turnId,
ts: input.tsBase + 2,
text: input.assistantText,
modelId: 'fake-model',
},
{
type: 'turn_state',
id: `${input.legacyIdPrefix}-state`,
turnId: input.turnId,
ts: input.tsBase + 3,
status: 'completed',
...(input.header.parentTurnId ? { parentTurnId: input.header.parentTurnId } : {}),
...(input.header.retriedFromTurnId
? { retriedFromTurnId: input.header.retriedFromTurnId }
: {}),
...(input.header.regeneratedFromTurnId
? { regeneratedFromTurnId: input.header.regeneratedFromTurnId }
: {}),
...(input.header.branchOfTurnId ? { branchOfTurnId: input.header.branchOfTurnId } : {}),
...(input.header.parentSessionId ? { parentSessionId: input.header.parentSessionId } : {}),
},
]);
await seedRuntimeRun(input.runStore, header, events);
}
async function seedRun(
runStore: AgentRunStore & RuntimeEventStore,
header: TestRunHeader,
events: EmittedAgentRunEvent[],
): Promise<void> {
await seedInvocationFromHeader(runStore, header);
for (const event of events) {
await runStore.appendEvent(header.sessionId, header.runId, event);
}
}
/**
* Seed one invocation whose ledger the test writes itself.
*
* The opening always comes first, and it opens the invocation the test's own
* events name, so nothing the test writes lands outside the run it seeded. The
* terminal event comes from the header only when the test did not already state
* one, so a run never ends twice.
*/
async function seedRuntimeRun(
runStore: RuntimeEventStore,
header: TestRunHeader,
events: RuntimeEvent[],
): Promise<void> {
const seeded: TestRunHeader = {
...header,
invocationId:
header.invocationId ??
events.find((event) => event.runId === header.runId)?.invocationId ??
header.runId,
};
await seedInvocationOpening(runStore, seeded);
for (const event of events) {
await runStore.appendRuntimeEvent(seeded.sessionId, seeded.runId, event);
}
if (!events.some((event) => event.status !== undefined)) {
await seedInvocationTerminal(runStore, seeded);
}
}
function runtimeEvent(overrides: Partial<RuntimeEvent>): RuntimeEvent {
const runId = overrides.runId ?? 'run-1';
return {
id: 'rt-event',
// One invocation per run unless a test says otherwise. A shared default
// would put two runs' events on one invocation, which is two endings.
invocationId: runId,
runId,
sessionId: 'session-1',
turnId: 'turn-1',
ts: 100,
partial: false,
role: 'system',
author: 'system',
...overrides,
};
}
async function seedCanonicalPermissionRun(
runStore: MemoryAgentRunStore,
header: TestRunHeader,
includeLedgerRequest = true,
): Promise<void> {
const events = [
runtimeEvent({
id: 'permission-user-canonical',
sessionId: header.sessionId,
runId: header.runId,
turnId: header.turnId,
ts: 99,
role: 'user',
author: 'user',
content: { kind: 'text', text: 'write the file' },
}),
...(includeLedgerRequest
? [
runtimeEvent({
id: 'permission-request-canonical',
sessionId: header.sessionId,
runId: header.runId,
turnId: header.turnId,
ts: 100,
actions: {
permissionRequest: {
kind: 'tool_permission',
requestId: 'request-canonical',
toolUseId: 'tool-canonical',
toolName: 'Write',
category: 'file_write',
reason: 'file_write',
args: { path: '/tmp/file' },
rememberForTurnAllowed: true,
hint: 'write approval',
},
},
refs: { toolCallId: 'tool-canonical' },
}),
]
: []),
runtimeEvent({
id: 'permission-answer-canonical',
sessionId: header.sessionId,
runId: header.runId,
turnId: header.turnId,
ts: 126,
author: 'user',
actions: {
permissionAnswerAccepted: { requestId: 'request-canonical' },
},
refs: { toolCallId: 'tool-canonical' },
}),
runtimeEvent({
id: 'permission-terminal-canonical',
sessionId: header.sessionId,
runId: header.runId,
turnId: header.turnId,
ts: 130,
status: 'completed',
actions: { endInvocation: true },
}),
];
await seedRuntimeRun(runStore, header, events);
}
function canonicalPermissionRecord(
header: TestRunHeader,
overrides: Partial<CanonicalPermissionOutcomeRecord> = {},
): CanonicalPermissionOutcomeRecord {
return {
sessionId: header.sessionId,
runId: header.runId,
turnId: header.turnId,
requestId: 'request-canonical',
request: {
kind: 'permission',
toolUseId: 'tool-canonical',
prompt: {
kind: 'tool_permission',
toolName: 'Write',
category: 'file_write',
reason: 'file_write',
review: { kind: 'path', operation: 'write', path: '/tmp/file' },
rememberForTurnAllowed: true,
},
},
outcome: {
kind: 'permission_answer',
decision: 'deny',
rememberForTurn: false,
reviewer: 'user',
committedAt: 125,
},
...overrides,
};
}
const noCanonicalPermissionOutcomes: CanonicalPermissionOutcomeReader = {
readPermissionOutcome: async () => undefined,
};
function testInteractionAuthority(): RuntimeInteractionAuthority {
return {
bindRun: (identity) => ({
...identity,
acceptSandboxBoundaryRequest: async () => {},
acceptUserQuestionRequest: async () => {},
acceptFormRequest: async () => {},
withdrawFormRequest: async () => {},
close: async () => {},
release: () => {},
}),
};
}
/**
* A session interrupted while one turn held a sandbox boundary request: the
* request row exists with its turn/run provenance, the run is still
* non-terminal, and the ledger holds nothing about the boundary. Tests layer
* their own settlement or ledger events on top.
*/
async function seedBoundaryRestartSession(input: {
now: number;
requestId: string;
requestTurnId?: string;
requestRunId?: string;
interactionAuthority?: RuntimeInteractionAuthority;
}): Promise<{
store: MemorySessionStore;
runStore: MemoryAgentRunStore;
manager: SessionManager;
session: SessionSummary;
}> {
const store = new MemorySessionStore();
const runStore = new MemoryAgentRunStore();
const backends = new BackendRegistry();
backends.register('ai-sdk', (ctx) => new TestBackend(ctx));
const managerDeps = {
store,
runStore,
runtimeEventStore: runStore,
backends,
newId: nextId(),
now: nextNow(input.now),
};
const manager = input.interactionAuthority
? new SessionManager({
...managerDeps,
interactionAuthority: input.interactionAuthority,
canonicalPermissionOutcomes: noCanonicalPermissionOutcomes,
})
: new SessionManager(managerDeps);
const session = await manager.createSession(makeInput({ status: 'waiting_for_user' }));
await store.createSandboxBoundaryRequest({
sessionId: session.id,
requestId: input.requestId,
turnId: input.requestTurnId ?? 'turn-1',
runId: input.requestRunId ?? 'run-1',
expansion: { network: { enabled: true } },
justification: 'Fetch a dependency.',
});
await seedRunningTurn(store, session.id, 'turn-1');
await seedRun(
runStore,
makeRunHeader({
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
status: 'waiting_for_user',
}),
[
makeRunEvent({
sessionId: session.id,
runId: 'run-1',
turnId: 'turn-1',
type: 'turn_started',
ts: 11,
}),
],
);
return { store, runStore, manager, session };
}
async function seedRunningTurn(
store: MemorySessionStore,
sessionId: string,
turnId: string,
): Promise<void> {
await store.appendMessages(sessionId, [
{ type: 'user', id: `${turnId}-user`, turnId, ts: 9, text: 'interrupted turn' },
{
type: 'turn_state',
id: `${turnId}-state`,
turnId,
ts: 10,
status: 'running',
},
]);
}
function nextNow(start: number): () => number {
let ts = start;
return () => ++ts;
}
async function inspectStableContinuationSafety() {
return {
workspaceIdentity: 'workspace-1',
backgroundOperationsSettled: true,
availableToolNames: [] as string[],
};
}
async function drain(iterable: AsyncIterable<unknown>): Promise<void> {
for await (const _event of iterable) {
// consume
}
}
async function collectSessionEvents(
iterable: AsyncIterable<SessionEvent>,
): Promise<SessionEvent[]> {
const events: SessionEvent[] = [];
for await (const event of iterable) {
events.push(event);
}
return events;
}
async function expectRejects(promise: Promise<unknown>, pattern: RegExp): Promise<void> {
try {
await promise;
} catch (err) {
assert.match(String(err instanceof Error ? err.message : String(err)), pattern);
return;
}
throw new Error('Expected promise to reject');
}
function key(sessionId: string, runId: string): string {
return `${sessionId}:${runId}`;
}
function copyEvent(event: AgentRunEvent): AgentRunEvent {
return {
...event,
...(event.data ? { data: { ...event.data } } : {}),
};
}
function copyRuntimeEvent(event: RuntimeEvent): RuntimeEvent {
return JSON.parse(JSON.stringify(event)) as RuntimeEvent;
}