Squashed 'third_party/pi/' content from commit d981de122

git-subtree-dir: third_party/pi
git-subtree-split: d981de1229ef899957bbe968bc8dcda02a21f477
This commit is contained in:
2026-09-14 10:40:08 -04:00
commit 67b361f0e9
1669 changed files with 397627 additions and 0 deletions

View File

@@ -0,0 +1,677 @@
import { createModels, fauxAssistantMessage, fauxProvider } from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it, vi } from "vitest";
import { type HarnessEvent, HarnessFault, type OperationRequest } from "../../../src/harness/agent-harness.ts";
import { DEFAULT_COMPACTION_SETTINGS } from "../../../src/harness/compaction/compaction.ts";
import { BACKGROUND_CONTEXT, createContextKey, withContextValue } from "../../../src/harness/context.ts";
import type { Result } from "../../../src/harness/result.ts";
import { createAgentHarness, Harness } from "../../../src/harness/runtime/harness.ts";
import { Lane } from "../../../src/harness/runtime/lane.ts";
import { MemoryStorage } from "../../../src/harness/session/memory.ts";
import { StorageBackedSession } from "../../../src/harness/session/session.ts";
import { InstrumentedStorage } from "../../../src/harness/session/testing/index.ts";
import * as storedValues from "../../../src/harness/session/values.ts";
import type { AgentMessage, Session } from "../../../src/index.ts";
import { ControlledMemoryStorage, deferred, FailingMemoryStorage } from "./test-utils.ts";
const sessions: Session[] = [];
const configuration = {
model: { provider: "faux", modelId: "faux-1" },
thinkingLevel: "off" as const,
activeToolNames: [],
};
async function createSession(storage = new InstrumentedStorage(new MemoryStorage())): Promise<{
session: Session;
storage: InstrumentedStorage;
}> {
const session = new StorageBackedSession(
{ id: `accept-${sessions.length}`, createdAt: 1, storageVersion: 1 },
storage,
);
sessions.push(session);
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.branchTip("main"), null),
storedValues.setValue(storedValues.laneConfig("main"), configuration),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
return { session, storage };
}
function options(session: Session) {
const provider = fauxProvider();
const models = createModels();
models.setProvider(provider.provider);
return { session, models, model: provider.getModel() };
}
async function createHarness(
beforeCreate?: (session: Session) => Promise<void>,
resources: Parameters<typeof createAgentHarness>[0]["resources"] = {},
queueModes: { steeringMode?: "all" | "one-at-a-time"; followUpMode?: "all" | "one-at-a-time" } = {},
): Promise<{
harness: Harness<object | undefined>;
lane: Lane<object | undefined>;
session: Session;
storage: InstrumentedStorage;
models: ReturnType<typeof createModels>;
}> {
const created = await createSession();
await beforeCreate?.(created.session);
const harnessOptions = { ...options(created.session), resources, ...queueModes };
const { harness } = await createAgentHarness(harnessOptions, BACKGROUND_CONTEXT);
if (!(harness instanceof Harness)) throw new Error("Expected runtime Harness");
const lane = await harness.lane("main", BACKGROUND_CONTEXT);
if (!(lane instanceof Lane)) throw new Error("Expected runtime Lane");
created.storage.clearCommitAttempts();
return { harness, lane, ...created, models: harnessOptions.models };
}
function unwrap<T>(result: Result<T, unknown>): T {
if (!result.ok) throw result.error;
return result.value;
}
afterEach(async () => {
for (const session of sessions.splice(0)) await session.close(BACKGROUND_CONTEXT);
});
describe("runtime atomic run acceptance", () => {
it.each([
["text", { kind: "prompt", prompt: "hello" } satisfies OperationRequest, [{ type: "text", text: "hello" }]],
[
"images",
{
kind: "prompt",
prompt: "",
images: [{ type: "image", data: "aW1hZ2U=", mimeType: "image/png" }],
} satisfies OperationRequest,
[{ type: "image", data: "aW1hZ2U=", mimeType: "image/png" }],
],
[
"text and images",
{
kind: "prompt",
prompt: "hello",
images: [{ type: "image", data: "aW1hZ2U=", mimeType: "image/png" }],
} satisfies OperationRequest,
[
{ type: "text", text: "hello" },
{ type: "image", data: "aW1hZ2U=", mimeType: "image/png" },
],
],
] as const)("accepts normalized %s prompts into starting", async (_name, request, expectedContent) => {
const { lane, session } = await createHarness();
const admission = unwrap(await lane.accept(request, BACKGROUND_CONTEXT));
const operation = lane.state.operation;
if (operation?.state.at !== "starting") throw new Error("Expected accepted run");
const entryId = operation.meta.intent.kind === "run" ? operation.meta.intent.promptEntryIds[0] : undefined;
if (entryId === undefined) throw new Error("Expected prompt entry");
const entry = await session.getEntry(entryId, BACKGROUND_CONTEXT);
expect(admission).toMatchObject({ operationId: operation.meta.operationId, kind: "run" });
expect(entry).toMatchObject({ type: "message", message: { role: "user", content: expectedContent } });
expect(operation.state.settings).toEqual({
compaction: DEFAULT_COMPACTION_SETTINGS,
steeringMode: "all",
followUpMode: "all",
toolExecution: "parallel",
});
});
it("preserves supplied message arrays and commits the exact acceptance write families once", async () => {
const { harness, lane, storage, models } = await createHarness();
const messages: AgentMessage[] = [
{ role: "user", content: "one", timestamp: 10 },
{ role: "user", content: "two", timestamp: 11 },
];
const getModel = vi.spyOn(models, "getModel");
const contextKey = createContextKey<string>("accept.context");
const context = withContextValue(contextKey, "source", BACKGROUND_CONTEXT);
const seen: Array<{ event: HarnessEvent; sameContext: boolean }> = [];
for (const type of ["run_start", "message_start", "message_end", "entry_added"] as const) {
harness.events.on(type, (event, eventContext) => {
seen.push({ event, sameContext: eventContext === context });
});
}
const admission = unwrap(
await lane.accept({ kind: "prompt", operationId: "operation", prompt: messages }, context),
);
expect(admission).toEqual({ operationId: "operation", kind: "run", startedAt: expect.any(Number) });
expect(storage.getCommitAttempts()).toHaveLength(1);
expect(
storage
.getCommitAttempts()[0]
?.map((write) => (write.kind === "value" ? `${write.kind}:${write.op}` : write.kind)),
).toEqual(["entry", "entry", "value:set", "value:set", "value:set", "value:set"]);
const operation = lane.state.operation;
if (operation?.meta.intent.kind !== "run") throw new Error("Expected run metadata");
expect(operation.meta).toMatchObject({
operationId: "operation",
lane: "main",
sourceTipId: null,
intent: { promptEntryIds: expect.any(Array) },
});
expect(operation.meta.intent.promptEntryIds).toHaveLength(2);
expect(await lane.getTipId(BACKGROUND_CONTEXT)).toBe(operation.meta.intent.promptEntryIds[1]);
expect(seen.map(({ event }) => event.type)).toEqual([
"run_start",
"message_start",
"message_end",
"entry_added",
"message_start",
"message_end",
"entry_added",
]);
expect(seen.every(({ sameContext }) => sameContext)).toBe(true);
expect(getModel).not.toHaveBeenCalled();
});
it("captures next-run messages before request messages and accepts an otherwise empty request", async () => {
const first = "pending-first";
const second = "pending-second";
const firstMessage = { role: "user" as const, content: "first", timestamp: 1 };
const secondMessage = { role: "user" as const, content: "second", timestamp: 2 };
const { harness, lane, session, storage } = await createHarness(async (source) => {
await source.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.pendingEntry(first), {
type: "message",
payload: firstMessage,
}),
storedValues.setValue(storedValues.pendingEntry(second), {
type: "message",
payload: secondMessage,
}),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [
{ entryId: first, kind: "nextRun" },
{ entryId: second, kind: "nextRun" },
],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
});
const events: string[] = [];
harness.events.on("queue_update", (event) => {
events.push(event.type);
});
unwrap(await lane.accept({ kind: "prompt", prompt: "" }, BACKGROUND_CONTEXT));
const operation = lane.state.operation;
if (operation?.meta.intent.kind !== "run" || operation.state.at !== "starting") {
throw new Error("Expected accepted run");
}
expect(operation.meta.intent.promptEntryIds).toEqual([]);
expect(operation.meta.sourceTipId).toBeNull();
expect(lane.state.inbox).toEqual([]);
expect(
(await session.scanBranch({ start: second, order: "oldestFirst" }, BACKGROUND_CONTEXT)).map(({ id }) => id),
).toEqual([first, second]);
expect(await session.getValue(storedValues.pendingEntry(first), BACKGROUND_CONTEXT)).toBeUndefined();
expect(await session.getValue(storedValues.pendingEntry(second), BACKGROUND_CONTEXT)).toBeUndefined();
expect(
storage
.getCommitAttempts()[0]
?.map((write) => (write.kind === "value" ? `${write.kind}:${write.op}` : write.kind)),
).toEqual(["entry", "entry", "value:delete", "value:delete", "value:set", "value:set", "value:set", "value:set"]);
expect(events).toEqual(["queue_update"]);
});
it("captures every eligible tag by mode and preserves admission order before the request", async () => {
const ids = {
write: "queued-write",
next: "queued-next",
steer1: "queued-steer-1",
steer2: "queued-steer-2",
follow1: "queued-follow-1",
follow2: "queued-follow-2",
};
const { lane, session } = await createHarness(
async (source) => {
await source.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.pendingEntry(ids.write), {
type: "custom",
customType: "queued-write",
}),
...[ids.next, ids.steer1, ids.steer2, ids.follow1, ids.follow2].map((entryId) =>
storedValues.setValue(storedValues.pendingEntry(entryId), {
type: "message",
payload: { role: "user", content: entryId, timestamp: 1 },
}),
),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [
{ entryId: ids.steer1, kind: "steer" },
{ entryId: ids.write, kind: "write" },
{ entryId: ids.next, kind: "nextRun" },
{ entryId: ids.follow1, kind: "followUp" },
{ entryId: ids.steer2, kind: "steer" },
{ entryId: ids.follow2, kind: "followUp" },
],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
},
{},
{ steeringMode: "one-at-a-time", followUpMode: "one-at-a-time" },
);
unwrap(await lane.accept({ kind: "prompt", prompt: "request" }, BACKGROUND_CONTEXT));
const tipId = lane.state.tipId;
if (tipId === null) throw new Error("Expected accepted tip");
const entries = await session.scanBranch({ start: tipId, order: "oldestFirst" }, BACKGROUND_CONTEXT);
expect(entries.map((entry) => entry.id)).toEqual([ids.steer1, ids.write, ids.next, ids.follow1, tipId]);
expect(lane.state.inbox).toEqual([
{ entryId: ids.steer2, kind: "steer" },
{ entryId: ids.follow2, kind: "followUp" },
]);
expect((await session.getValue(storedValues.laneState("main"), BACKGROUND_CONTEXT))?.value.inbox).toEqual(
lane.state.inbox,
);
for (const entryId of [ids.write, ids.next, ids.steer1, ids.follow1]) {
expect(await session.getValue(storedValues.pendingEntry(entryId), BACKGROUND_CONTEXT)).toBeUndefined();
}
for (const entryId of [ids.steer2, ids.follow2]) {
expect(await session.getValue(storedValues.pendingEntry(entryId), BACKGROUND_CONTEXT)).toBeDefined();
}
});
it("does not let a lone queued write validate empty acceptance", async () => {
const entryId = "queued-write";
const { lane, session } = await createHarness(async (source) => {
await source.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.pendingEntry(entryId), {
type: "custom",
customType: "queued-write",
}),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [{ entryId, kind: "write" }],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
});
const result = await lane.accept({ kind: "prompt", prompt: "" }, BACKGROUND_CONTEXT);
expect(result).toMatchObject({ ok: false, error: { _tag: "InvalidMessage", reason: "empty" } });
expect(lane.state.inbox).toEqual([{ entryId, kind: "write" }]);
expect(await session.getValue(storedValues.pendingEntry(entryId), BACKGROUND_CONTEXT)).toBeDefined();
});
it("formats skills and templates before acceptance", async () => {
const resources = {
skills: [
{ name: "review", description: "Review", content: "Inspect it", filePath: "/skills/review/SKILL.md" },
],
promptTemplates: [{ name: "fix", content: "Fix $1 then $@" }],
};
const skill = await createHarness(undefined, resources);
unwrap(
await skill.lane.accept(
{ kind: "skill", name: "review", additionalInstructions: "Be strict" },
BACKGROUND_CONTEXT,
),
);
const skillEntry = await skill.session.getEntry(skill.lane.state.tipId!, BACKGROUND_CONTEXT);
expect(skillEntry).toMatchObject({
type: "message",
message: { content: [{ type: "text", text: expect.stringContaining('<skill name="review"') }] },
});
const template = await createHarness(undefined, resources);
unwrap(
await template.lane.accept({ kind: "prompt_template", name: "fix", args: ["A", "B"] }, BACKGROUND_CONTEXT),
);
const templateEntry = await template.session.getEntry(template.lane.state.tipId!, BACKGROUND_CONTEXT);
expect(templateEntry).toMatchObject({
type: "message",
message: { content: [{ type: "text", text: "Fix A then A B" }] },
});
});
it("accepts standalone compaction with durable preparation and no execution", async () => {
const { harness, lane, session, storage } = await createHarness();
await lane.appendMessage({ role: "user", content: "history", timestamp: 1 }, BACKGROUND_CONTEXT);
storage.clearCommitAttempts();
const starts = vi.fn();
harness.events.on("compaction_start", starts);
const admission = unwrap(
await lane.accept(
{ kind: "compaction", operationId: "compaction", customInstructions: "focus" },
BACKGROUND_CONTEXT,
),
);
expect(admission).toEqual({ operationId: "compaction", kind: "compaction", startedAt: expect.any(Number) });
const operation = lane.state.operation;
if (operation?.state.at !== "summary.deciding") throw new Error("Expected accepted compaction");
expect(operation.state.task).toMatchObject({
reason: "manual",
customInstructions: "focus",
boundary: { kind: "finish" },
});
expect(
await session.getValue(
storedValues.operationPreparation("compaction", operation.state.task.taskId),
BACKGROUND_CONTEXT,
),
).toMatchObject({ value: { kind: "compaction" } });
expect(storage.getCommitAttempts()).toHaveLength(1);
expect(starts).toHaveBeenCalledTimes(1);
expect(lane.activeDrive).toBeUndefined();
});
it("rejects empty standalone compaction without writing", async () => {
const { lane, storage } = await createHarness();
expect(await lane.accept({ kind: "compaction" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: false,
error: { _tag: "NothingToCompact" },
});
expect(storage.getCommitAttempts()).toEqual([]);
});
it.each([false, true])("accepts %s summarized navigation atomically", async (summarize) => {
const { harness, lane, session, storage } = await createHarness(async (source) => {
await source.mutate(
(mutator) =>
mutator.commit(
[
{
kind: "entry",
entry: {
id: "root",
parentId: null,
type: "message",
message: { role: "user", content: "root", timestamp: 1 },
},
},
{
kind: "entry",
entry: {
id: "source",
parentId: "root",
type: "message",
message: { role: "user", content: "source", timestamp: 2 },
},
},
{
kind: "entry",
entry: {
id: "target",
parentId: "root",
type: "message",
message: { role: "user", content: "target", timestamp: 3 },
},
},
storedValues.setValue(storedValues.branchTip("main"), "source"),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
});
storage.clearCommitAttempts();
const starts = vi.fn();
harness.events.on("navigation_start", starts);
unwrap(
await lane.accept(
{
kind: "navigation",
operationId: summarize ? "summarized" : "direct",
targetId: "target",
options: { summarize, label: "chosen", customInstructions: "focus" },
},
BACKGROUND_CONTEXT,
),
);
const operation = lane.state.operation;
if (operation === null) throw new Error("Expected accepted navigation");
expect(operation.state.at).toBe(summarize ? "summary.deciding" : "navigation.ready_to_commit");
expect(lane.state.tipId).toBe("source");
if (summarize) {
if (operation.state.at !== "summary.deciding") throw new Error("Expected summary decision");
expect(
await session.getValue(
storedValues.operationPreparation(operation.meta.operationId, operation.state.task.taskId),
BACKGROUND_CONTEXT,
),
).toMatchObject({ value: { kind: "branch_summary", messages: [{ content: "source" }] } });
}
expect(storage.getCommitAttempts()).toHaveLength(1);
expect(starts).toHaveBeenCalledTimes(1);
expect(lane.activeDrive).toBeUndefined();
});
it("validates navigation before acceptance", async () => {
const { lane, storage } = await createHarness(async (source) => {
await source.mutate(
(mutator) =>
mutator.commit(
[
{
kind: "entry",
entry: {
id: "source",
parentId: null,
type: "message",
message: { role: "user", content: "source", timestamp: 1 },
},
},
storedValues.setValue(storedValues.branchTip("main"), "source"),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
});
expect(await lane.accept({ kind: "navigation", targetId: "source" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: false,
error: { _tag: "InvalidNavigation", reason: "current_tip" },
});
expect(
await lane.accept({ kind: "navigation", targetId: null, options: { label: "bad" } }, BACKGROUND_CONTEXT),
).toMatchObject({
ok: false,
error: { _tag: "InvalidNavigation", reason: "root_label" },
});
expect(await lane.accept({ kind: "navigation", targetId: "missing" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: false,
error: { _tag: "UnknownTarget" },
});
expect(storage.getCommitAttempts()).toEqual([]);
});
it("returns expected pre-acceptance errors without writing", async () => {
const { lane, storage } = await createHarness();
const pending = fauxAssistantMessage([], { stopReason: "pending" });
expect(await lane.accept({ kind: "prompt", prompt: "" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: false,
error: { _tag: "InvalidMessage", reason: "empty" },
});
expect(await lane.accept({ kind: "prompt", prompt: pending }, BACKGROUND_CONTEXT)).toMatchObject({
ok: false,
error: { _tag: "InvalidMessage", reason: "pending_assistant" },
});
expect(await lane.accept({ kind: "skill", name: "missing" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: false,
error: { _tag: "UnknownSkill" },
});
expect(await lane.accept({ kind: "prompt_template", name: "missing" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: false,
error: { _tag: "UnknownTemplate" },
});
expect(storage.getCommitAttempts()).toEqual([]);
});
it("serializes concurrent accepts so exactly one wins", async () => {
const { lane, storage } = await createHarness();
const results = await Promise.all([
lane.accept({ kind: "prompt", prompt: "first" }, BACKGROUND_CONTEXT),
lane.accept({ kind: "prompt", prompt: "second" }, BACKGROUND_CONTEXT),
]);
expect(results.filter((result) => result.ok)).toHaveLength(1);
expect(results.filter((result) => !result.ok)).toMatchObject([{ error: { _tag: "LaneBusy" } }]);
expect(storage.getCommitAttempts()).toHaveLength(1);
});
it("faults on commit failure without publishing acceptance", async () => {
const storage = new FailingMemoryStorage();
const session = new StorageBackedSession({ id: "failing", createdAt: 1, storageVersion: 1 }, storage);
sessions.push(session);
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.branchTip("main"), null),
storedValues.setValue(storedValues.laneConfig("main"), configuration),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const { harness } = await createAgentHarness(options(session), BACKGROUND_CONTEXT);
if (!(harness instanceof Harness)) throw new Error("Expected runtime Harness");
const lane = await harness.lane("main", BACKGROUND_CONTEXT);
if (!(lane instanceof Lane)) throw new Error("Expected runtime Lane");
storage.failure = new Error("accept failed");
await expect(lane.accept({ kind: "prompt", prompt: "hello" }, BACKGROUND_CONTEXT)).rejects.toBeInstanceOf(
HarnessFault,
);
expect(lane.state.operation).toBeNull();
});
it("delivers acceptance listeners after publishing state and permits serialized reads", async () => {
const { harness, lane } = await createHarness();
let inspected = false;
harness.events.on("run_start", async () => {
inspected = (await lane.inspectExecution(BACKGROUND_CONTEXT)).current?.status === "open";
});
unwrap(await lane.accept({ kind: "prompt", prompt: "hello" }, BACKGROUND_CONTEXT));
expect(inspected).toBe(true);
});
it("does not resolve acceptance before its direct listeners settle", async () => {
const { harness, lane } = await createHarness();
const started = deferred();
const release = deferred();
harness.events.on("run_start", async () => {
started.resolve();
await release.promise;
});
const acceptance = lane.accept({ kind: "prompt", prompt: "hello" }, BACKGROUND_CONTEXT);
await started.promise;
let resolved = false;
void acceptance.then(() => {
resolved = true;
});
await new Promise((resolve) => setTimeout(resolve, 0));
expect(resolved).toBe(false);
release.resolve();
unwrap(await acceptance);
expect(resolved).toBe(true);
});
it("returns Closed when acceptance starts after close", async () => {
const { harness, lane, storage } = await createHarness();
await harness.close(BACKGROUND_CONTEXT);
expect(await lane.accept({ kind: "prompt", prompt: "late" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: false,
error: { _tag: "Closed" },
});
expect(storage.getCommitAttempts()).toEqual([]);
});
it("publishes an acceptance admitted before close", async () => {
const storage = new ControlledMemoryStorage();
const session = new StorageBackedSession({ id: "closing", createdAt: 1, storageVersion: 1 }, storage);
sessions.push(session);
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.branchTip("main"), null),
storedValues.setValue(storedValues.laneConfig("main"), configuration),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const { harness } = await createAgentHarness(options(session), BACKGROUND_CONTEXT);
const lane = await harness.lane("main", BACKGROUND_CONTEXT);
const started = deferred();
const release = deferred();
storage.beforeNextCommit = async () => {
started.resolve();
await release.promise;
};
const acceptance = lane.accept({ kind: "prompt", prompt: "hello" }, BACKGROUND_CONTEXT);
await started.promise;
const closing = harness.close(BACKGROUND_CONTEXT);
release.resolve();
expect((await acceptance).ok).toBe(true);
await closing;
});
});

View File

@@ -0,0 +1,790 @@
import {
type AssistantMessage,
createAssistantMessageEventStream,
createModels,
fauxAssistantMessage,
fauxProvider,
type MutableModels,
type Provider,
} from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { HarnessEvent, WatchHandle } from "../../../src/harness/agent-harness.ts";
import { DEFAULT_COMPACTION_SETTINGS } from "../../../src/harness/compaction/compaction.ts";
import { BACKGROUND_CONTEXT, type Context } from "../../../src/harness/context.ts";
import { HookRegistry } from "../../../src/harness/hooks.ts";
import { runCheckpoint, startRun } from "../../../src/harness/runtime/drive/checkpoint.ts";
import { runGeneration } from "../../../src/harness/runtime/drive/generation.ts";
import { recoverAssistantGeneration } from "../../../src/harness/runtime/drive/recovery.ts";
import { Lane } from "../../../src/harness/runtime/lane.ts";
import { restoreLane } from "../../../src/harness/runtime/restore.ts";
import { type Config, Drive } from "../../../src/harness/runtime/types.ts";
import { MemoryStorage } from "../../../src/harness/session/memory.ts";
import { StorageBackedSession } from "../../../src/harness/session/session.ts";
import { InstrumentedStorage } from "../../../src/harness/session/testing/instrumented-storage.ts";
import {
type AssistantEffectPendingOperation,
type AssistantReadyOperation,
type CheckpointOperation,
type LaneConfiguration,
type MessageEntry,
type OperationState,
operationScopeOf,
type Session,
type Write,
} from "../../../src/harness/session/types.ts";
import * as storedValues from "../../../src/harness/session/values.ts";
import { deferred } from "./test-utils.ts";
const sessions: Session[] = [];
const operationId = "01950000-0000-7000-8000-000000000001";
class FrameBlockingMemoryStorage extends MemoryStorage {
blockNextFrame = false;
readonly frameStarted = deferred();
readonly releaseFrame = deferred();
override async commit(writes: Write[], context: Context) {
if (
this.blockNextFrame &&
writes.some(
(write) =>
write.kind === "list" && write.op === "append" && write.namespace === "pi.pending.assistant_frame",
)
) {
this.blockNextFrame = false;
this.frameStarted.resolve();
await this.releaseFrame.promise;
}
return super.commit(writes, context);
}
}
interface Fixture {
lane: Lane<undefined>;
drive: Drive;
session: Session;
storage: InstrumentedStorage;
models: MutableModels;
faux: ReturnType<typeof fauxProvider>;
hooks: HookRegistry;
events: HarnessEvent[];
config: Config<undefined>;
}
function unusedWatch<T>(): WatchHandle<T> {
throw new Error("watch is not used by generation tests");
}
async function createFixture(backend: MemoryStorage = new MemoryStorage({ now: () => 100 })): Promise<Fixture> {
const storage = new InstrumentedStorage(backend);
const session = new StorageBackedSession(
{ id: `generation-${sessions.length}`, createdAt: 1, storageVersion: 1 },
storage,
);
sessions.push(session);
const faux = fauxProvider({ tokenSize: { min: 1, max: 1 } });
const model = faux.getModel();
const configuration: LaneConfiguration = {
model: { provider: model.provider, modelId: model.id },
thinkingLevel: "off",
activeToolNames: [],
};
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.branchTip("main"), null),
storedValues.setValue(storedValues.laneConfig("main"), configuration),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const models = createModels();
models.setProvider(faux.provider);
const hooks = new HookRegistry(() => {});
const events: HarnessEvent[] = [];
const config: Config<undefined> = {
tools: [],
resources: {},
streamOptions: {},
retryPolicy: { enabled: true, maxRetries: 3, baseDelayMs: 1 },
compaction: DEFAULT_COMPACTION_SETTINGS,
steeringMode: "all",
followUpMode: "all",
toolExecution: "parallel",
toolContext: undefined,
systemPrompt: undefined,
toProviderMessages: (messages) =>
messages.filter(
(message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult",
),
entryProjectors: {},
};
const lane = new Lane<undefined>(
"main",
session,
models,
hooks,
await restoreLane(session, "main", BACKGROUND_CONTEXT),
(cause) => (cause instanceof Error ? cause : new Error(String(cause))),
(batch) => {
events.push(...structuredClone(batch));
return Promise.resolve();
},
unusedWatch,
() => config,
);
const admission = await lane.accept({ kind: "prompt", operationId, prompt: "question" }, BACKGROUND_CONTEXT);
if (!admission.ok) throw admission.error;
const drive = new Drive({ operationId }, BACKGROUND_CONTEXT);
lane.activeDrive = drive;
storage.clearCommitAttempts();
return { lane, drive, session, storage, models, faux, hooks, events, config };
}
function currentRun(lane: Lane<undefined>): OperationState {
const operation = lane.state.operation;
if (operation === null) throw new Error("fixture has no operation");
return operation.state;
}
function readyGeneration(lane: Lane<undefined>): AssistantReadyOperation {
const run = currentRun(lane);
if (run.at !== "assistant.ready") throw new Error("fixture has no ready generation");
return run;
}
async function startFixtureRun(fixture: Fixture) {
const run = currentRun(fixture.lane);
if (run.at !== "starting") throw new Error("run did not start at its initial boundary");
return startRun(fixture.lane, fixture.drive, run);
}
async function advanceToReady(fixture: Fixture): Promise<AssistantReadyOperation> {
await startFixtureRun(fixture);
const run = currentRun(fixture.lane);
if (run.at !== "checkpoint") throw new Error("run did not reach checkpoint");
await runCheckpoint(fixture.lane, fixture.drive, run);
return readyGeneration(fixture.lane);
}
async function expectProjectionRestores(fixture: Fixture): Promise<void> {
expect(fixture.lane.state).toEqual(await restoreLane(fixture.session, "main", BACKGROUND_CONTEXT));
}
function writesOperationState(writes: readonly Write[], status: string): boolean {
return writes.some(
(write) =>
write.kind === "value" &&
write.op === "set" &&
write.namespace === "pi.op.state" &&
typeof write.value === "object" &&
write.value !== null &&
"at" in write.value &&
write.value.at === `assistant.${status}`,
);
}
afterEach(async () => {
vi.restoreAllMocks();
for (const session of sessions.splice(0)) await session.close(BACKGROUND_CONTEXT);
});
describe("runtime generation checkpoint", () => {
it("consumes before_run and snapshots a ready generation", async () => {
const fixture = await createFixture();
fixture.hooks.on("before_run", () => ({
messages: [{ role: "user", content: "injected", timestamp: 2 }],
}));
expect(await startFixtureRun(fixture)).toEqual({ kind: "continue" });
let run = currentRun(fixture.lane);
if (run.at !== "checkpoint") throw new Error("missing checkpoint");
expect(run.triggerEntryId).toBe(fixture.lane.state.tipId);
expect(fixture.events.map((event) => event.type)).toContain("entry_added");
await expectProjectionRestores(fixture);
expect(await runCheckpoint(fixture.lane, fixture.drive, run)).toEqual({ kind: "continue" });
run = currentRun(fixture.lane);
if (run.at !== "assistant.ready") {
throw new Error("missing ready generation");
}
expect(run.generationContext).toMatchObject({
configuration: fixture.lane.state.configuration,
streamOptions: {},
retryPolicy: { maxAttempts: 4, baseDelayMs: 1 },
overflowRecoveryUsed: false,
});
await expectProjectionRestores(fixture);
});
it("drains mixed pending writes while separating leaf and generation trigger", async () => {
const fixture = await createFixture();
await startFixtureRun(fixture);
const messageId = "01950000-0000-7000-8000-000000000010";
const customId = "01950000-0000-7000-8000-000000000011";
await fixture.lane.command((state) => {
const operation = state.operation;
if (operation === null) throw new Error("missing operation");
const inbox = [
...state.inbox,
{ entryId: messageId, kind: "write" as const },
{ entryId: customId, kind: "write" as const },
];
return {
kind: "commit",
writes: [
storedValues.setValue(storedValues.pendingEntry(messageId), {
type: "message",
payload: { role: "user", content: "projecting", timestamp: 3 },
}),
storedValues.setValue(storedValues.pendingEntry(customId), {
type: "custom",
customType: "display-only",
payload: { value: true },
}),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox,
}),
],
next: { ...state, inbox },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
const run = currentRun(fixture.lane);
if (run.at !== "checkpoint") throw new Error("missing checkpoint");
fixture.storage.clearCommitAttempts();
await runCheckpoint(fixture.lane, fixture.drive, run);
expect(fixture.storage.getCommitAttempts()).toHaveLength(1);
const routed = currentRun(fixture.lane);
if (routed.at !== "assistant.ready") throw new Error("pending writes did not route to generation");
expect(fixture.lane.state.tipId).toBe(customId);
expect(routed.generationContext).toMatchObject({
triggerEntryId: messageId,
overflowRecoveryUsed: false,
});
expect(fixture.lane.state.inbox).toEqual([]);
await expectProjectionRestores(fixture);
});
it("preserves checkpoint routing when a custom-only pending write does not project", async () => {
const fixture = await createFixture();
await startFixtureRun(fixture);
const current = currentRun(fixture.lane);
if (current.at !== "checkpoint") throw new Error("missing checkpoint");
const customId = "01950000-0000-7000-8000-000000000012";
const checkpoint: CheckpointOperation = {
...current,
continuation: { kind: "may_finish", includeFinalAssistant: false },
};
await fixture.lane.command((state) => {
const operation = state.operation;
if (operation === null) throw new Error("missing operation");
const inbox = [...state.inbox, { entryId: customId, kind: "write" as const }];
return {
kind: "commit",
writes: [
storedValues.setValue(storedValues.pendingEntry(customId), {
type: "custom",
customType: "display-only",
payload: { value: true },
}),
storedValues.setValue(storedValues.operationState(operationId), checkpoint),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox,
}),
],
next: { ...state, inbox, operation: { meta: operation.meta, state: checkpoint } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
fixture.hooks.on("before_run_end", () => ({ followUp: "continue after custom write" }));
expect(await runCheckpoint(fixture.lane, fixture.drive, checkpoint)).toEqual({ kind: "continue" });
const ready = currentRun(fixture.lane);
if (ready.at !== "assistant.ready") throw new Error("hook follow-up did not route to generation");
const followUpId = ready.generationContext.triggerEntryId;
expect(followUpId).toBe(fixture.lane.state.tipId);
expect(await fixture.session.getEntry(customId, BACKGROUND_CONTEXT)).toMatchObject({ type: "custom" });
expect(await fixture.session.getEntry(followUpId, BACKGROUND_CONTEXT)).toMatchObject({
parentId: customId,
type: "message",
message: { role: "user", content: "continue after custom write" },
});
expect(fixture.lane.state.inbox).toEqual([]);
expect(await fixture.session.getValue(storedValues.pendingEntry(customId), BACKGROUND_CONTEXT)).toBeUndefined();
await expectProjectionRestores(fixture);
});
it("drops a stale finish-hook follow-up when input arrives during the hook", async () => {
const fixture = await createFixture();
await startFixtureRun(fixture);
const current = currentRun(fixture.lane);
if (current.at !== "checkpoint") throw new Error("missing checkpoint");
const checkpoint: CheckpointOperation = {
...current,
continuation: { kind: "may_finish", includeFinalAssistant: false },
};
await fixture.lane.command((state) => {
const operation = state.operation;
if (operation === null) throw new Error("missing operation");
return {
kind: "commit",
writes: [storedValues.setValue(storedValues.operationState(operationId), checkpoint)],
next: { ...state, operation: { meta: operation.meta, state: checkpoint } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
const hookStarted = deferred();
const releaseHook = deferred();
fixture.hooks.on("before_run_end", async () => {
hookStarted.resolve();
await releaseHook.promise;
return { followUp: "stale hook follow-up" };
});
const running = runCheckpoint(fixture.lane, fixture.drive, checkpoint);
await hookStarted.promise;
await fixture.lane.command((state) => {
const inbox = [...state.inbox, { entryId: "steer-during-finish", kind: "steer" as const }];
return {
kind: "commit",
writes: [
storedValues.setValue(storedValues.pendingEntry("steer-during-finish"), {
type: "message",
payload: { role: "user", content: "new input", timestamp: 4 },
}),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox,
}),
],
next: { ...state, inbox },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
releaseHook.resolve();
expect(await running).toEqual({ kind: "continue" });
const ready = currentRun(fixture.lane);
if (ready.at !== "assistant.ready") throw new Error("new input did not replace stale finish decision");
expect(ready.generationContext.triggerEntryId).toBe("steer-during-finish");
expect(fixture.lane.state.tipId).toBe("steer-during-finish");
expect((await fixture.session.getEntry("steer-during-finish", BACKGROUND_CONTEXT))?.type).toBe("message");
});
});
describe("runtime assistant generation", () => {
it("commits intent before provider admission, preserves queued inbox state, and settles reserved ids", async () => {
const fixture = await createFixture();
const ready = await advanceToReady(fixture);
const hookStarted = deferred();
const releaseHook = deferred();
fixture.hooks.on("before_request", async () => {
hookStarted.resolve();
await releaseHook.promise;
return undefined;
});
let effectPendingAtProvider = false;
fixture.faux.setResponses([
async () => {
const state = await fixture.session.getValue(storedValues.operationState(operationId), BACKGROUND_CONTEXT);
effectPendingAtProvider = state?.value.at === "assistant.effect_pending";
return fauxAssistantMessage("answer", { timestamp: 5 });
},
]);
fixture.storage.clearCommitAttempts();
const generating = runGeneration(fixture.lane, fixture.drive, ready);
await hookStarted.promise;
const steerId = "01950000-0000-7000-8000-000000000020";
await fixture.lane.command((state) => {
const operation = state.operation;
if (operation === null) throw new Error("missing operation");
const inbox = [...state.inbox, { entryId: steerId, kind: "steer" as const }];
return {
kind: "commit",
writes: [
storedValues.setValue(storedValues.pendingEntry(steerId), {
type: "message",
payload: { role: "user", content: "late steer", timestamp: 4 },
}),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox,
}),
],
next: { ...state, inbox },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
releaseHook.resolve();
expect(await generating).toEqual({ kind: "continue" });
expect(effectPendingAtProvider).toBe(true);
const settled = currentRun(fixture.lane);
expect(fixture.lane.state.inbox).toEqual([{ entryId: steerId, kind: "steer" }]);
if (settled.at !== "checkpoint") throw new Error("response did not reach checkpoint");
const responseId = settled.latestAssistantEntryId;
if (responseId === null) throw new Error("missing response id");
const entry = await fixture.session.getEntry(responseId, BACKGROUND_CONTEXT);
expect(entry).toMatchObject({ id: responseId, type: "message", message: { content: [{ text: "answer" }] } });
expect(
await fixture.session.readList(
storedValues.pendingAssistantFrames(operationId, responseId),
undefined,
BACKGROUND_CONTEXT,
),
).toEqual([]);
const attempts = fixture.storage.getCommitAttempts();
expect(attempts.some((writes) => writesOperationState(writes, "effect_pending"))).toBe(true);
const settlement = attempts.find((writes) => writes.some((write) => write.kind === "entry"));
expect(
settlement?.map((write) =>
write.kind === "value" || write.kind === "list"
? `${write.kind}:${write.op}:${write.namespace}`
: write.kind,
),
).toEqual([
"entry",
"usage",
"value:set:pi.branch.tip",
"list:delete:pi.pending.assistant_frame",
"value:set:pi.op.state",
]);
expect(fixture.events.map((event) => event.type)).toEqual(
expect.arrayContaining([
"turn_start",
"message_start",
"message_update",
"message_end",
"entry_added",
"usage",
"turn_end",
]),
);
await expectProjectionRestores(fixture);
});
it("does not await frame storage inside the provider event loop and persists frames in order", async () => {
const backend = new FrameBlockingMemoryStorage({ now: () => 100 });
const fixture = await createFixture(backend);
const ready = await advanceToReady(fixture);
const base = fixture.faux.provider;
const provider: Provider = {
id: base.id,
name: base.name,
auth: base.auth,
getModels: () => base.getModels(),
stream: (model, context, options) => base.stream(model, context, options),
streamSimple: () => {
const stream = createAssistantMessageEventStream();
const partial: AssistantMessage = {
...fauxAssistantMessage([], { timestamp: 5 }),
stopReason: "pending",
};
const text = { type: "text" as const, text: "" };
queueMicrotask(() => {
stream.push({ type: "start", partial });
partial.content.push(text);
stream.push({ type: "text_start", contentIndex: 0, partial });
text.text = "ab";
stream.push({ type: "text_delta", contentIndex: 0, delta: "ab", partial });
stream.push({ type: "text_end", contentIndex: 0, content: "ab", partial });
stream.push({ type: "done", reason: "stop", message: fauxAssistantMessage("ab", { timestamp: 5 }) });
});
return stream;
},
};
fixture.models.setProvider(provider);
backend.blockNextFrame = true;
fixture.storage.clearCommitAttempts();
const generating = runGeneration(fixture.lane, fixture.drive, ready);
await backend.frameStarted.promise;
await expect
.poll(() => fixture.events.filter((event) => event.type === "message_update").length)
.toBeGreaterThan(1);
backend.releaseFrame.resolve();
await generating;
const frames = fixture.storage
.getCommitAttempts()
.flatMap((writes) =>
writes.flatMap((write) =>
write.kind === "list" && write.op === "append" && write.namespace === "pi.pending.assistant_frame"
? [write.value]
: [],
),
);
expect(
frames.map((frame) =>
typeof frame === "object" && frame !== null && "type" in frame ? frame.type : undefined,
),
).toEqual(["start", "text_start", "text_end"]);
expect(frames[0]).toMatchObject({ partial: { content: [] } });
expect(frames[1]).toMatchObject({ content: { text: "ab" } });
});
it("enters configuration failure without reserving response ids or calling the provider", async () => {
const fixture = await createFixture();
await fixture.lane.setActiveTools(["missing"], BACKGROUND_CONTEXT);
const ready = await advanceToReady(fixture);
const queuedId = await fixture.lane.appendCustomEntry("after-failure", { retained: true }, BACKGROUND_CONTEXT);
fixture.storage.clearCommitAttempts();
expect(await runGeneration(fixture.lane, fixture.drive, ready)).toMatchObject({
kind: "settled",
outcome: {
operationId,
kind: "run",
status: "failed",
error: { code: "configured_tools_unavailable", details: { tools: ["missing"] } },
},
});
expect(fixture.lane.state.operation).toBeNull();
expect(fixture.lane.state.inbox).toEqual([{ entryId: queuedId, kind: "write" }]);
expect(await fixture.session.getValue(storedValues.pendingEntry(queuedId), BACKGROUND_CONTEXT)).toBeDefined();
expect(fixture.faux.state.callCount).toBe(0);
expect(
fixture.storage
.getCommitAttempts()
.flat()
.some((write) => write.kind === "entry" || write.kind === "usage"),
).toBe(false);
await expectProjectionRestores(fixture);
});
it("fails a missing captured model before reserving ids", async () => {
const fixture = await createFixture();
const ready = await advanceToReady(fixture);
fixture.models.deleteProvider(fixture.faux.provider.id);
fixture.storage.clearCommitAttempts();
expect(await runGeneration(fixture.lane, fixture.drive, ready)).toMatchObject({
kind: "settled",
outcome: { operationId, kind: "run", status: "failed", error: { code: "model_unavailable" } },
});
expect(fixture.lane.state.operation).toBeNull();
expect(fixture.faux.state.callCount).toBe(0);
expect(
fixture.storage
.getCommitAttempts()
.flat()
.some((write) => write.kind === "entry" || write.kind === "usage"),
).toBe(false);
await expectProjectionRestores(fixture);
});
it("declines intent when cancellation wins preparation", async () => {
const cancelled = await createFixture();
const cancelledReady = await advanceToReady(cancelled);
const cancelHookStarted = deferred();
const releaseCancelHook = deferred();
cancelled.hooks.on("before_request", async () => {
cancelHookStarted.resolve();
await releaseCancelHook.promise;
return undefined;
});
cancelled.storage.clearCommitAttempts();
const cancelling = runGeneration(cancelled.lane, cancelled.drive, cancelledReady);
await cancelHookStarted.promise;
await cancelled.lane.command((state) => {
const operation = state.operation;
if (operation === null) throw new Error("missing operation");
const nextState: OperationState = {
...operation.state,
control: { status: "cancel_requested", requestedAt: 10 },
};
return {
kind: "commit",
writes: [storedValues.setValue(storedValues.operationState(operationId), nextState)],
next: { ...state, operation: { meta: operation.meta, state: nextState } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
releaseCancelHook.resolve();
expect(await cancelling).toEqual({ kind: "continue" });
expect(cancelled.faux.state.callCount).toBe(0);
expect(
cancelled.storage.getCommitAttempts().some((writes) => writesOperationState(writes, "effect_pending")),
).toBe(false);
});
it("recovers an orphan with no frames into a zero-usage retry wait", async () => {
const fixture = await createFixture();
const ready = await advanceToReady(fixture);
const responseEntryId = "01950000-0000-7000-8000-000000000028";
const usageId = "01950000-0000-7000-8000-000000000029";
const pending: AssistantEffectPendingOperation = {
...operationScopeOf(ready),
at: "assistant.effect_pending",
generationContext: { ...ready.generationContext, retryPolicy: { maxAttempts: 2, baseDelayMs: 1 } },
attempt: 1,
responseEntryId,
usageId,
intendedOutputLimit: 100,
contextWindow: 1_000,
};
await fixture.lane.command((state) => {
const operation = state.operation;
if (operation === null) throw new Error("missing operation");
return {
kind: "commit",
writes: [storedValues.setValue(storedValues.operationState(operationId), pending)],
next: { ...state, operation: { meta: operation.meta, state: pending } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
await recoverAssistantGeneration(fixture.lane, fixture.drive, pending);
const entry = (await fixture.session.getEntry(responseEntryId, BACKGROUND_CONTEXT)) as MessageEntry;
expect(entry.message).toMatchObject({
api: "unknown",
provider: ready.generationContext.configuration.model.provider,
model: ready.generationContext.configuration.model.modelId,
content: [],
stopReason: "error",
usage: { totalTokens: 0 },
});
expect(currentRun(fixture.lane)).toMatchObject({
at: "assistant.retry_wait",
nextAttempt: 2,
});
expect(fixture.faux.state.callCount).toBe(0);
await expectProjectionRestores(fixture);
});
it("recovers an orphan from committed frames without a provider or response hook", async () => {
const fixture = await createFixture();
const ready = await advanceToReady(fixture);
const responseEntryId = "01950000-0000-7000-8000-000000000030";
const usageId = "01950000-0000-7000-8000-000000000031";
const partial: AssistantMessage = {
...fauxAssistantMessage([], { timestamp: 6 }),
stopReason: "pending",
};
const pending: AssistantEffectPendingOperation = {
...operationScopeOf(ready),
at: "assistant.effect_pending",
generationContext: { ...ready.generationContext, retryPolicy: { maxAttempts: 1, baseDelayMs: 1 } },
attempt: 1,
responseEntryId,
usageId,
intendedOutputLimit: 100,
contextWindow: 1_000,
};
await fixture.lane.command((state) => {
const operation = state.operation;
if (operation === null) throw new Error("missing operation");
return {
kind: "commit",
writes: [
storedValues.setValue(storedValues.operationState(operationId), pending),
storedValues.appendList(storedValues.pendingAssistantFrames(operationId, responseEntryId), {
type: "start",
partial,
}),
storedValues.appendList(storedValues.pendingAssistantFrames(operationId, responseEntryId), {
type: "text_start",
contentIndex: 0,
content: { type: "text", text: "" },
}),
storedValues.appendList(storedValues.pendingAssistantFrames(operationId, responseEntryId), {
type: "text_delta",
contentIndex: 0,
delta: "draft",
}),
storedValues.appendList(storedValues.pendingAssistantFrames(operationId, responseEntryId), {
type: "text_end",
contentIndex: 0,
content: "corrected",
}),
],
next: { ...state, operation: { meta: operation.meta, state: pending } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
let afterResponseCalls = 0;
fixture.hooks.on("after_response", () => {
afterResponseCalls++;
return undefined;
});
expect(await recoverAssistantGeneration(fixture.lane, fixture.drive, pending)).toMatchObject({
kind: "settled",
outcome: { operationId, kind: "run", status: "failed", tipId: responseEntryId },
});
expect(fixture.faux.state.callCount).toBe(0);
expect(afterResponseCalls).toBe(0);
const entry = (await fixture.session.getEntry(responseEntryId, BACKGROUND_CONTEXT)) as MessageEntry;
expect(entry.message).toMatchObject({
role: "assistant",
content: [{ type: "text", text: "corrected" }],
stopReason: "error",
usage: { input: 0, output: 0 },
});
expect(fixture.lane.state.operation).toBeNull();
expect(
await fixture.session.readList(
storedValues.pendingAssistantFrames(operationId, responseEntryId),
undefined,
BACKGROUND_CONTEXT,
),
).toEqual([]);
expect(
fixture.events.filter((event) => "recovery" in event && event.recovery).map((event) => event.type),
).toEqual(["message_start", "message_end", "entry_added"]);
await expectProjectionRestores(fixture);
});
it("finishes the no-tool run with terminal cleanup and an immutable result record", async () => {
const fixture = await createFixture();
const ready = await advanceToReady(fixture);
fixture.faux.setResponses([fauxAssistantMessage("done", { timestamp: 7 })]);
await runGeneration(fixture.lane, fixture.drive, ready);
const run = currentRun(fixture.lane);
if (run.at !== "checkpoint") throw new Error("missing terminal checkpoint");
const result = await runCheckpoint(fixture.lane, fixture.drive, run);
expect(result).toMatchObject({
kind: "settled",
outcome: { operationId, kind: "run", status: "completed", tipId: fixture.lane.state.tipId },
});
expect(fixture.lane.state.operation).toBeNull();
expect(fixture.lane.state.lastOperationId).toBe(operationId);
const getEntries = vi.spyOn(fixture.storage, "getEntries");
expect(await fixture.lane.getResult(operationId, BACKGROUND_CONTEXT)).toMatchObject({
operationId,
kind: "run",
status: "completed",
tipId: fixture.lane.state.tipId,
});
expect(await fixture.lane.getResult("unknown", BACKGROUND_CONTEXT)).toBeUndefined();
expect(getEntries).not.toHaveBeenCalled();
expect(
await fixture.session.getValue(storedValues.operationMeta(operationId), BACKGROUND_CONTEXT),
).toBeUndefined();
expect(
await fixture.session.getValue(storedValues.operationState(operationId), BACKGROUND_CONTEXT),
).toBeUndefined();
expect(fixture.events.filter((event) => event.type === "run_end")).toHaveLength(1);
await expectProjectionRestores(fixture);
});
});

View File

@@ -0,0 +1,612 @@
import { createModels, fauxAssistantMessage, fauxProvider } from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it } from "vitest";
import {
AgentHarness,
type AgentLane,
HarnessClosed,
HarnessFault,
type OperationAdmission,
type Resources,
} from "../../../src/harness/agent-harness.ts";
import { BACKGROUND_CONTEXT, withCancel } from "../../../src/harness/context.ts";
import { Lane } from "../../../src/harness/runtime/lane.ts";
import { MemoryStorage } from "../../../src/harness/session/memory.ts";
import { StorageBackedSession } from "../../../src/harness/session/session.ts";
import type { Session } from "../../../src/harness/session/types.ts";
import { deferred } from "./test-utils.ts";
const sessions: Session[] = [];
async function createFixture(options: { deferred?: boolean; resources?: Resources } = {}): Promise<{
lane: AgentLane;
runtimeLane: Lane<object | undefined>;
harness: Awaited<ReturnType<typeof AgentHarness.create>>["harness"];
faux: ReturnType<typeof fauxProvider>;
session: Session;
}> {
const session = new StorageBackedSession(
{ id: `public-drive-${sessions.length}`, createdAt: 1, storageVersion: 1 },
new MemoryStorage(),
);
sessions.push(session);
const faux = fauxProvider();
const models = createModels();
models.setProvider(faux.provider);
const { harness } = await AgentHarness.create(
{
session,
models,
model: faux.getModel(),
...(options.deferred === true ? { streamOptions: { deferred: true } } : {}),
...(options.resources === undefined ? {} : { resources: options.resources }),
},
BACKGROUND_CONTEXT,
);
const lane = await harness.lane("main", BACKGROUND_CONTEXT);
if (!(lane instanceof Lane)) throw new Error("Expected runtime Lane");
return { lane, runtimeLane: lane, harness, faux, session };
}
async function acceptRun(lane: AgentLane, operationId: string): Promise<OperationAdmission> {
const result = await lane.accept({ kind: "prompt", operationId, prompt: operationId }, BACKGROUND_CONTEXT);
if (!result.ok) throw result.error;
return result.value;
}
afterEach(async () => {
for (const session of sessions.splice(0)) await session.close(BACKGROUND_CONTEXT);
});
describe("runtime public drive", () => {
it("persists model identity without requiring a local registration", async () => {
const { lane, faux } = await createFixture();
await lane.setModel({ provider: "missing", modelId: "missing-model" }, BACKGROUND_CONTEXT);
expect(await lane.getModel(BACKGROUND_CONTEXT)).toBeUndefined();
expect(await lane.prompt("prompt", undefined, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: {
kind: "run",
status: "failed",
error: { code: "model_unavailable" },
},
});
expect(faux.state.callCount).toBe(0);
});
it("composes prompt, skill, and template acceptance with drive", async () => {
const resources: Resources = {
skills: [
{ name: "review", description: "Review", content: "Inspect it", filePath: "/skills/review/SKILL.md" },
],
promptTemplates: [{ name: "fix", content: "Fix $1" }],
};
const { lane, faux } = await createFixture({ resources });
faux.setResponses([
fauxAssistantMessage("prompt answer"),
fauxAssistantMessage("skill answer"),
fauxAssistantMessage("template answer"),
]);
expect(await lane.prompt("prompt", undefined, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { kind: "run", status: "completed" },
});
expect(await lane.skill("review", "strict", BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { kind: "run", status: "completed" },
});
expect(await lane.promptFromTemplate("fix", ["it"], BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { kind: "run", status: "completed" },
});
expect(faux.state.callCount).toBe(3);
});
it("returns a convenience-only suspension observation", async () => {
const { lane, runtimeLane, faux } = await createFixture({ deferred: true });
faux.setResponses([fauxAssistantMessage("eventual answer")]);
expect(await lane.prompt("defer", undefined, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: {
operationId: expect.any(String),
status: "suspended",
deferred: { provider: "faux", modelId: "faux-1" },
},
});
expect(runtimeLane.state.operation?.state.at).toBe("deferred.suspended");
});
it("records caller usage as an adjustment and publishes committed totals", async () => {
const { lane, harness, session } = await createFixture();
const usage = fauxAssistantMessage("usage").usage;
const events: unknown[] = [];
harness.events.on("usage", (event) => {
events.push(event);
});
const recorded = await lane.recordUsage(
usage,
{ entryId: "external", details: { source: "test" } },
BACKGROUND_CONTEXT,
);
if (!recorded.ok) throw recorded.error;
expect(events).toEqual([
expect.objectContaining({
type: "usage",
lane: "main",
row: expect.objectContaining({ id: recorded.value.usageId, adjustment: true }),
totals: (await session.getStats(BACKGROUND_CONTEXT)).usage,
}),
]);
});
it.each(["steer", "followUp", "nextRun"] as const)(
"starts an ordinary continuation run from queued %s input",
async (kind) => {
const { lane, faux } = await createFixture();
await lane.appendMessage({ role: "user", content: "history", timestamp: 1 }, BACKGROUND_CONTEXT);
const summaryStarted = deferred();
const releaseSummary = deferred();
faux.setResponses([
async () => {
summaryStarted.resolve();
await releaseSummary.promise;
return fauxAssistantMessage("summary");
},
fauxAssistantMessage("continuation answer"),
]);
const compacting = lane.compact(undefined, BACKGROUND_CONTEXT);
await summaryStarted.promise;
const queued = await lane[kind]("continue", undefined, BACKGROUND_CONTEXT);
expect(queued.ok).toBe(true);
releaseSummary.resolve();
expect(await compacting).toMatchObject({
ok: true,
value: {
compaction: { kind: "compaction", status: "completed" },
run: { kind: "run", status: "completed" },
},
});
expect(faux.state.callCount).toBe(2);
},
);
it("lets a competing acceptance win the structural continuation window", async () => {
const { lane, harness, faux } = await createFixture();
await lane.appendMessage({ role: "user", content: "history", timestamp: 1 }, BACKGROUND_CONTEXT);
const summaryStarted = deferred();
const releaseSummary = deferred();
faux.setResponses([
async () => {
summaryStarted.resolve();
await releaseSummary.promise;
return fauxAssistantMessage("summary");
},
fauxAssistantMessage("competitor answer"),
]);
let competing: ReturnType<typeof lane.accept> | undefined;
harness.events.on("compaction_end", (event) => {
if (event.status === "completed") {
competing = lane.accept({ kind: "prompt", prompt: "competitor" }, BACKGROUND_CONTEXT);
}
});
const compacting = lane.compact(undefined, BACKGROUND_CONTEXT);
await summaryStarted.promise;
await lane.nextRun("queued", undefined, BACKGROUND_CONTEXT);
releaseSummary.resolve();
const compacted = await compacting;
expect(compacted).toMatchObject({ ok: true, value: { compaction: { status: "completed" } } });
if (!compacted.ok) throw compacted.error;
expect(compacted.value).not.toHaveProperty("run");
if (competing === undefined) throw new Error("Competing acceptance did not start");
const admission = await competing;
if (!admission.ok) throw admission.error;
expect(await lane.drive({ operationId: admission.value.operationId }, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { kind: "settled", outcome: { status: "completed" } },
});
});
it("cancels queued input and reports consumed or missing ids", async () => {
const { lane, faux } = await createFixture();
const cancelled = await lane.nextRun("cancel", undefined, BACKGROUND_CONTEXT);
if (!cancelled.ok) throw cancelled.error;
expect(await lane.cancelQueued(cancelled.value.entryId, BACKGROUND_CONTEXT)).toEqual({
ok: true,
value: { kind: "cancelled" },
});
expect(await lane.cancelQueued(cancelled.value.entryId, BACKGROUND_CONTEXT)).toEqual({
ok: true,
value: { kind: "not_found" },
});
const consumed = await lane.nextRun("consume", undefined, BACKGROUND_CONTEXT);
if (!consumed.ok) throw consumed.error;
const admission = await lane.accept({ kind: "prompt", prompt: "" }, BACKGROUND_CONTEXT);
if (!admission.ok) throw admission.error;
expect(await lane.cancelQueued(consumed.value.entryId, BACKGROUND_CONTEXT)).toEqual({
ok: true,
value: { kind: "already_consumed" },
});
faux.setResponses([fauxAssistantMessage("answer")]);
await lane.drive({ operationId: admission.value.operationId }, BACKGROUND_CONTEXT);
});
it("admits input after cancellation and preserves it through reconciliation", async () => {
const { lane, runtimeLane, faux } = await createFixture();
await acceptRun(lane, "cancelled");
await lane.requestAbort("cancelled", BACKGROUND_CONTEXT);
const late = await lane.steer("late", undefined, BACKGROUND_CONTEXT);
expect(late.ok).toBe(true);
expect(await lane.drive({ operationId: "cancelled" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { kind: "settled", outcome: { status: "aborted" } },
});
expect(runtimeLane.state.inbox).toHaveLength(1);
faux.setResponses([fauxAssistantMessage("late answer")]);
expect(await lane.prompt("", undefined, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { kind: "run", status: "completed" },
});
});
it("composes standalone compaction acceptance with drive", async () => {
const { lane, faux } = await createFixture();
await lane.appendMessage({ role: "user", content: "history", timestamp: 1 }, BACKGROUND_CONTEXT);
faux.setResponses([fauxAssistantMessage("summary")]);
expect(await lane.compact(undefined, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { compaction: { kind: "compaction", status: "completed" } },
});
expect(faux.state.callCount).toBe(1);
});
it.each([false, true])("composes %s summarized navigation acceptance with drive", async (summarize) => {
const { lane, session, faux } = await createFixture();
const rootId = await lane.appendMessage({ role: "user", content: "root", timestamp: 1 }, BACKGROUND_CONTEXT);
await lane.appendMessage({ role: "user", content: "source", timestamp: 2 }, BACKGROUND_CONTEXT);
await session.mutate(
(mutator) =>
mutator.commit(
[
{
kind: "entry",
entry: {
id: "target",
parentId: rootId,
type: "message",
message: { role: "user", content: "target", timestamp: 3 },
},
},
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
if (summarize) faux.setResponses([fauxAssistantMessage("branch summary")]);
expect(await lane.navigateTree("target", { summarize, label: "chosen" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { navigation: { kind: "navigation", status: "completed" } },
});
expect(faux.state.callCount).toBe(summarize ? 1 : 0);
expect(await lane.getTipId(BACKGROUND_CONTEXT)).not.toBeNull();
});
it("resumes any current operation after acceptance or reopen", async () => {
const { lane, faux } = await createFixture();
await acceptRun(lane, "run");
faux.setResponses([fauxAssistantMessage("answer")]);
expect(await lane.resume(BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { operationId: "run", kind: "run", status: "completed" },
});
});
it("polls one deferred permit through resume", async () => {
const { lane, runtimeLane, faux } = await createFixture({ deferred: true });
faux.setResponses([fauxAssistantMessage("eventual answer")]);
const suspended = await lane.prompt("defer", undefined, BACKGROUND_CONTEXT);
if (!suspended.ok || suspended.value.status !== "suspended") throw new Error("Expected suspended run");
expect(await lane.resume(BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { operationId: suspended.value.operationId, kind: "run", status: "completed" },
});
expect(runtimeLane.state.operation).toBeNull();
expect(faux.state.deferredFetchCount).toBe(1);
expect(await lane.resume(BACKGROUND_CONTEXT)).toMatchObject({
ok: false,
error: { _tag: "NothingToResume" },
});
});
it("aborts and reconciles the current operation", async () => {
const { lane, runtimeLane, faux } = await createFixture();
await acceptRun(lane, "run");
expect(await lane.abort(BACKGROUND_CONTEXT)).toEqual({
ok: true,
value: { operationId: "run", steer: [], followUp: [] },
});
expect(runtimeLane.state.operation).toBeNull();
expect(faux.state.callCount).toBe(0);
expect(await lane.abort(BACKGROUND_CONTEXT)).toMatchObject({
ok: false,
error: { _tag: "NoActiveOperation" },
});
});
it("waits for an operation that has no installed drive", async () => {
const { lane, faux } = await createFixture();
await acceptRun(lane, "run");
let idle = false;
const waiting = lane.waitForIdle(BACKGROUND_CONTEXT).then(() => {
idle = true;
});
await Promise.resolve();
expect(idle).toBe(false);
faux.setResponses([fauxAssistantMessage("answer")]);
await lane.drive({ operationId: "run" }, BACKGROUND_CONTEXT);
await waiting;
expect(idle).toBe(true);
});
it("serializes concurrent runWhenIdle callbacks", async () => {
const { lane } = await createFixture();
const firstStarted = deferred();
const releaseFirst = deferred();
const order: string[] = [];
const first = lane.runWhenIdle(async () => {
order.push("first:start");
firstStarted.resolve();
await releaseFirst.promise;
order.push("first:end");
}, BACKGROUND_CONTEXT);
await firstStarted.promise;
const second = lane.runWhenIdle(() => {
order.push("second");
}, BACKGROUND_CONTEXT);
await Promise.resolve();
expect(order).toEqual(["first:start"]);
releaseFirst.resolve();
await Promise.all([first, second]);
expect(order).toEqual(["first:start", "first:end", "second"]);
});
it("owns the idle window while runWhenIdle executes", async () => {
const { lane, faux } = await createFixture();
const callbackStarted = deferred();
const releaseCallback = deferred();
const callback = lane.runWhenIdle(async () => {
callbackStarted.resolve();
await releaseCallback.promise;
}, BACKGROUND_CONTEXT);
await callbackStarted.promise;
let accepted = false;
const acceptance = lane.accept({ kind: "prompt", prompt: "after" }, BACKGROUND_CONTEXT).then((result) => {
accepted = true;
return result;
});
await Promise.resolve();
expect(accepted).toBe(false);
releaseCallback.resolve();
await callback;
const admission = await acceptance;
if (!admission.ok) throw admission.error;
faux.setResponses([fauxAssistantMessage("answer")]);
await lane.drive({ operationId: admission.value.operationId }, BACKGROUND_CONTEXT);
});
it("allows coherent lane reads from an idle callback", async () => {
const { lane } = await createFixture();
await lane.runWhenIdle(async (context) => {
const execution = await lane.inspectExecution(context);
const watch = await lane.watch(context);
expect(execution.current).toBeNull();
expect(watch.snapshot.operation).toBeNull();
watch.unsubscribe();
}, BACKGROUND_CONTEXT);
});
it("releases idle ownership when the callback fails", async () => {
const { lane } = await createFixture();
const failure = new Error("callback failed");
await expect(
lane.runWhenIdle(() => {
throw failure;
}, BACKGROUND_CONTEXT),
).rejects.toBe(failure);
expect(await lane.accept({ kind: "prompt", prompt: "after" }, BACKGROUND_CONTEXT)).toMatchObject({ ok: true });
});
it("close waits for an already-running idle callback", async () => {
const { lane, harness } = await createFixture();
const callbackStarted = deferred();
const releaseCallback = deferred();
const callback = lane.runWhenIdle(async () => {
callbackStarted.resolve();
await releaseCallback.promise;
}, BACKGROUND_CONTEXT);
await callbackStarted.promise;
let closed = false;
const closing = harness.close(BACKGROUND_CONTEXT).then(() => {
closed = true;
});
await Promise.resolve();
expect(closed).toBe(false);
releaseCallback.resolve();
await Promise.all([callback, closing]);
expect(closed).toBe(true);
});
it("installs one pass and joins same-operation callers", async () => {
const { lane, runtimeLane, faux } = await createFixture();
const started = deferred();
const release = deferred();
faux.setResponses([
async () => {
started.resolve();
await release.promise;
return fauxAssistantMessage("answer");
},
]);
const admission = await acceptRun(lane, "run");
const first = lane.drive({ operationId: admission.operationId }, BACKGROUND_CONTEXT);
await started.promise;
const second = lane.drive({ operationId: admission.operationId }, BACKGROUND_CONTEXT);
expect(runtimeLane.activeDrive?.operationId).toBe(admission.operationId);
release.resolve();
const [firstResult, secondResult] = await Promise.all([first, second]);
expect(firstResult).toMatchObject({
ok: true,
value: { kind: "settled", outcome: { operationId: "run", status: "completed" } },
});
expect(secondResult).toEqual(firstResult);
expect(faux.state.callCount).toBe(1);
expect(runtimeLane.activeDrive).toBeUndefined();
});
it("returns old result records without disturbing the current operation", async () => {
const { lane, runtimeLane, faux } = await createFixture();
faux.setResponses([fauxAssistantMessage("first")]);
await acceptRun(lane, "first");
expect(await lane.drive({ operationId: "first" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { kind: "settled", outcome: { operationId: "first" } },
});
await acceptRun(lane, "second");
expect(await lane.drive({ operationId: "first" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { kind: "settled", outcome: { operationId: "first" } },
});
expect(runtimeLane.state.operation?.meta.operationId).toBe("second");
expect(runtimeLane.activeDrive).toBeUndefined();
faux.setResponses([fauxAssistantMessage("second")]);
expect(await lane.drive({ operationId: "second" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { kind: "settled", outcome: { operationId: "second" } },
});
});
it("isolates stale operation ids", async () => {
const { lane, runtimeLane, faux } = await createFixture();
await acceptRun(lane, "current");
expect(await lane.drive({ operationId: "stale" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: false,
error: { _tag: "OperationMismatch", expectedOperationId: "stale", currentOperationId: "current" },
});
expect(runtimeLane.activeDrive).toBeUndefined();
expect(faux.state.callCount).toBe(0);
});
it("does not install for a caller already cancelled", async () => {
const { lane, runtimeLane, faux } = await createFixture();
await acceptRun(lane, "run");
const caller = withCancel(BACKGROUND_CONTEXT);
const cancelled = new Error("caller cancelled");
caller.cancel(cancelled);
await expect(lane.drive({ operationId: "run" }, caller.context)).rejects.toBe(cancelled);
expect(runtimeLane.activeDrive).toBeUndefined();
expect(faux.state.callCount).toBe(0);
});
it("caller cancellation stops only that caller's observation", async () => {
const { lane, runtimeLane, faux } = await createFixture();
const started = deferred();
const release = deferred();
faux.setResponses([
async () => {
started.resolve();
await release.promise;
return fauxAssistantMessage("answer");
},
]);
await acceptRun(lane, "run");
const owner = lane.drive({ operationId: "run" }, BACKGROUND_CONTEXT);
await started.promise;
const caller = withCancel(BACKGROUND_CONTEXT);
const observer = lane.drive({ operationId: "run" }, caller.context);
const cancelled = new Error("observer cancelled");
caller.cancel(cancelled);
await expect(observer).rejects.toBe(cancelled);
expect(runtimeLane.activeDrive?.operationId).toBe("run");
release.resolve();
expect(await owner).toMatchObject({ ok: true, value: { kind: "settled" } });
expect(faux.state.callCount).toBe(1);
});
it("close rejects observation without waiting for a non-cooperative effect", async () => {
const { lane, runtimeLane, harness, faux } = await createFixture();
const started = deferred();
const release = deferred();
faux.setResponses([
async () => {
started.resolve();
await release.promise;
return fauxAssistantMessage("late");
},
]);
await acceptRun(lane, "run");
const observation = lane.drive({ operationId: "run" }, BACKGROUND_CONTEXT);
await started.promise;
await harness.close(BACKGROUND_CONTEXT);
await expect(observation).rejects.toBeInstanceOf(HarnessClosed);
release.resolve();
await expect.poll(() => runtimeLane.activeDrive).toBeUndefined();
});
it("exposes durable abort and reconciles through public drive", async () => {
const { lane, runtimeLane, faux } = await createFixture();
await acceptRun(lane, "run");
expect(await lane.requestAbort("run", BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { operationId: "run", newlyRequested: true, steer: [], followUp: [] },
});
expect(runtimeLane.activeDrive).toBeUndefined();
expect(await lane.drive({ operationId: "run" }, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { kind: "settled", outcome: { operationId: "run", status: "aborted" } },
});
expect(faux.state.callCount).toBe(0);
expect(await lane.requestAbort("run", BACKGROUND_CONTEXT)).toMatchObject({
ok: false,
error: { _tag: "OperationMismatch", expectedOperationId: "run", lastOperationId: "run" },
});
});
it("faults the harness when a detached pass fails", async () => {
const { lane, runtimeLane, harness } = await createFixture();
harness.hooks.on("before_drive", () => {
throw new Error("drive failed");
});
await acceptRun(lane, "run");
await expect(lane.drive({ operationId: "run" }, BACKGROUND_CONTEXT)).rejects.toBeInstanceOf(HarnessFault);
expect(runtimeLane.activeDrive).toBeUndefined();
await expect(lane.getTipId(BACKGROUND_CONTEXT)).rejects.toBeInstanceOf(HarnessFault);
});
});

View File

@@ -0,0 +1,747 @@
import {
createModels,
type DeferredHandle,
fauxAssistantMessage,
fauxProvider,
fauxToolCall,
type MutableModels,
type Provider,
} from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { HarnessEvent, WatchHandle } from "../../../src/harness/agent-harness.ts";
import { DEFAULT_COMPACTION_SETTINGS } from "../../../src/harness/compaction/compaction.ts";
import { BACKGROUND_CONTEXT } from "../../../src/harness/context.ts";
import { HookRegistry } from "../../../src/harness/hooks.ts";
import { reconcileOperation } from "../../../src/harness/runtime/drive/reconcile.ts";
import { runStructuralDecision } from "../../../src/harness/runtime/drive/structural.ts";
import { driveOperation } from "../../../src/harness/runtime/drive.ts";
import { Lane } from "../../../src/harness/runtime/lane.ts";
import { restoreLane } from "../../../src/harness/runtime/restore.ts";
import { type Config, Drive } from "../../../src/harness/runtime/types.ts";
import { insertEntry } from "../../../src/harness/session/commit.ts";
import { MemoryStorage } from "../../../src/harness/session/memory.ts";
import { StorageBackedSession } from "../../../src/harness/session/session.ts";
import { InstrumentedStorage } from "../../../src/harness/session/testing/instrumented-storage.ts";
import type {
Control,
DeferredEffectPendingOperation,
DeferredSuspendedOperation,
LaneConfiguration,
NewEntry,
OperationMeta,
OperationScope,
OperationState,
Session,
SummaryContext,
SummaryTask,
Write,
} from "../../../src/harness/session/types.ts";
import * as storedValues from "../../../src/harness/session/values.ts";
import type { AgentMessage } from "../../../src/types.ts";
const sessions: Session[] = [];
const operationId = "01950000-0000-7000-8000-000000000001";
interface Fixture {
lane: Lane<undefined>;
drive: Drive;
session: Session;
storage: InstrumentedStorage;
models: MutableModels;
faux: ReturnType<typeof fauxProvider>;
configuration: LaneConfiguration;
events: HarnessEvent[];
hooks: HookRegistry;
}
interface InstalledOperation {
state: OperationState;
intent: OperationMeta["intent"];
entries?: NewEntry[];
writes?: Write[];
terminalEvents: HarnessEvent["type"][];
}
function unusedWatch<T>(): WatchHandle<T> {
throw new Error("watch is not used by reconciliation tests");
}
function cancelledControl(): Control {
return { status: "cancel_requested", requestedAt: 10 };
}
function scope(control: Control = cancelledControl()): OperationScope {
return {
control,
settings: {
compaction: DEFAULT_COMPACTION_SETTINGS,
steeringMode: "all",
followUpMode: "all",
toolExecution: "parallel",
},
latestAssistantEntryId: null,
};
}
function user(content: string): AgentMessage {
return { role: "user", content, timestamp: 1 };
}
function summaryContext(configuration: LaneConfiguration): SummaryContext {
return {
resultEntryId: "summary-entry",
configuration,
streamOptions: {},
retryPolicy: { maxAttempts: 2, baseDelayMs: 10 },
};
}
async function createFixture(): Promise<Fixture> {
const storage = new InstrumentedStorage(new MemoryStorage({ now: () => 100 }));
const session = new StorageBackedSession(
{ id: `reconcile-${sessions.length}`, createdAt: 1, storageVersion: 1 },
storage,
);
sessions.push(session);
const faux = fauxProvider({ api: "faux", deferred: { pendingFetches: 0 } });
const model = faux.getModel();
const models = createModels();
models.setProvider(faux.provider);
const configuration: LaneConfiguration = {
model: { provider: model.provider, modelId: model.id },
thinkingLevel: "off",
activeToolNames: [],
};
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.branchTip("main"), null),
storedValues.setValue(storedValues.laneConfig("main"), configuration),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const hooks = new HookRegistry(() => {});
const events: HarnessEvent[] = [];
const config: Config<undefined> = {
tools: [],
resources: {},
streamOptions: {},
retryPolicy: { enabled: true, maxRetries: 1, baseDelayMs: 10 },
compaction: DEFAULT_COMPACTION_SETTINGS,
steeringMode: "all",
followUpMode: "all",
toolExecution: "parallel",
toolContext: undefined,
systemPrompt: undefined,
toProviderMessages: (messages) =>
messages.filter(
(message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult",
),
entryProjectors: {},
};
const lane = new Lane<undefined>(
"main",
session,
models,
hooks,
await restoreLane(session, "main", BACKGROUND_CONTEXT),
(cause) => (cause instanceof Error ? cause : new Error(String(cause))),
(batch) => {
events.push(...structuredClone(batch));
return Promise.resolve();
},
unusedWatch,
() => config,
);
const drive = new Drive({ operationId }, BACKGROUND_CONTEXT);
lane.activeDrive = drive;
storage.clearCommitAttempts();
return { lane, drive, session, storage, models, faux, configuration, events, hooks };
}
async function installOperation(fixture: Fixture, installed: InstalledOperation): Promise<void> {
const entries = installed.entries ?? [
{ id: "tip", parentId: null, type: "message" as const, message: user("history") },
];
const tipId = entries.at(-1)?.id ?? null;
const meta: OperationMeta = {
operationId,
lane: "main",
sourceTipId: tipId,
startedAt: 1,
intent: installed.intent,
};
await fixture.lane.command(
(projection) => ({
kind: "commit",
writes: [
...entries.map((entry) => insertEntry(entry)),
storedValues.setValue(storedValues.branchTip("main"), tipId),
...(installed.writes ?? []),
storedValues.setValue(storedValues.operationMeta(operationId), meta),
storedValues.setValue(storedValues.operationState(operationId), installed.state),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: projection.lastOperationId,
inbox: projection.inbox,
}),
],
next: { ...projection, tipId, operation: { meta, state: installed.state } },
materialize: () => undefined,
}),
BACKGROUND_CONTEXT,
);
fixture.storage.clearCommitAttempts();
}
function deferredOperation(
fixture: Fixture,
effectPending: boolean,
): { state: DeferredSuspendedOperation | DeferredEffectPendingOperation; entry: NewEntry; handle: DeferredHandle } {
const model = fixture.faux.getModel();
const handle = { provider: model.provider, modelId: model.id, api: model.api, id: "deferred-job" };
const entry: NewEntry = {
id: "deferred-source",
parentId: null,
type: "message",
message: fauxAssistantMessage([], { stopReason: "deferred", deferred: handle }),
};
const base = {
...scope(),
stepId: "step",
sourceEntryId: entry.id,
poll: effectPending ? 1 : 0,
configuration: fixture.configuration,
streamOptions: {},
};
return {
state: effectPending
? {
...base,
at: "deferred.effect_pending",
responseEntryId: "deferred-response",
usageId: "deferred-usage",
}
: { ...base, at: "deferred.suspended" },
entry,
handle,
};
}
function cases(fixture: Fixture): InstalledOperation[] {
const generation = {
stepId: "step",
triggerEntryId: "tip",
configuration: fixture.configuration,
streamOptions: {},
retryPolicy: { maxAttempts: 2, baseDelayMs: 10 },
overflowRecoveryUsed: false,
};
const runTask: SummaryTask = {
taskId: "run-summary",
reason: "threshold",
boundary: {
kind: "resume_checkpoint",
resumeAfter: {
continuation: { kind: "need_assistant", overflowRecoveryUsed: false },
triggerEntryId: "tip",
},
},
};
const compactionTask: SummaryTask = { taskId: "compaction", reason: "manual", boundary: { kind: "finish" } };
const navigationTask: SummaryTask = {
taskId: "navigation",
boundary: { kind: "commit_navigation", targetId: "target" },
};
const suspended = deferredOperation(fixture, false);
const deferredEffect = deferredOperation(fixture, true);
return [
{
state: { ...scope(), at: "starting" },
intent: { kind: "run", promptEntryIds: ["tip"] },
terminalEvents: ["run_end"],
},
{
state: {
...scope(),
at: "checkpoint",
continuation: { kind: "need_assistant", overflowRecoveryUsed: false },
triggerEntryId: "tip",
},
intent: { kind: "run", promptEntryIds: ["tip"] },
terminalEvents: ["run_end"],
},
{
state: { ...scope(), at: "assistant.ready", generationContext: generation, nextAttempt: 1 },
intent: { kind: "run", promptEntryIds: ["tip"] },
terminalEvents: ["run_end"],
},
{
state: {
...scope(),
at: "assistant.effect_pending",
generationContext: generation,
attempt: 1,
responseEntryId: "assistant-response",
usageId: "assistant-usage",
intendedOutputLimit: 100,
contextWindow: 1_000,
},
intent: { kind: "run", promptEntryIds: ["tip"] },
writes: [
storedValues.appendList(storedValues.pendingAssistantFrames(operationId, "assistant-response"), {
type: "text_delta",
contentIndex: 0,
delta: "partial",
}),
],
terminalEvents: ["run_end"],
},
{
state: {
...scope(),
at: "assistant.retry_wait",
generationContext: generation,
nextAttempt: 2,
notBefore: Date.now() + 100_000,
errorMessage: "retry",
},
intent: { kind: "run", promptEntryIds: ["tip"] },
terminalEvents: ["run_end"],
},
{
state: {
...scope(),
at: "tools",
batch: {
assistantEntryId: "assistant",
configuration: fixture.configuration,
turnId: "turn",
calls: [{ status: "planned", sourceIndex: 0, resultEntryId: "tool-result" }],
},
},
intent: { kind: "run", promptEntryIds: [] },
entries: [
{
id: "assistant",
parentId: null,
type: "message",
message: fauxAssistantMessage(fauxToolCall("tool", {}), { stopReason: "toolUse" }),
},
],
terminalEvents: ["run_end"],
},
{
state: suspended.state,
intent: { kind: "run", promptEntryIds: [] },
entries: [suspended.entry],
terminalEvents: ["run_end"],
},
{
state: deferredEffect.state,
intent: { kind: "run", promptEntryIds: [] },
entries: [deferredEffect.entry],
terminalEvents: ["run_end"],
},
{
state: { ...scope(), at: "summary.deciding", task: runTask },
intent: { kind: "run", promptEntryIds: ["tip"] },
terminalEvents: ["compaction_end", "run_end"],
},
{
state: {
...scope(),
at: "summary.ready",
task: compactionTask,
summaryContext: summaryContext(fixture.configuration),
nextAttempt: 1,
},
intent: { kind: "compaction" },
terminalEvents: ["compaction_end"],
},
{
state: {
...scope(),
at: "summary.effect_pending",
task: navigationTask,
summaryContext: summaryContext(fixture.configuration),
attempt: 1,
request: { index: 0, usageId: "usage" },
usageIds: [],
},
intent: { kind: "navigation", targetId: "target", summarize: true },
terminalEvents: ["navigation_end"],
},
{
state: {
...scope(),
at: "summary.retry_wait",
task: runTask,
summaryContext: summaryContext(fixture.configuration),
nextAttempt: 2,
notBefore: Date.now() + 100_000,
errorMessage: "retry",
},
intent: { kind: "run", promptEntryIds: ["tip"] },
terminalEvents: ["compaction_end", "run_end"],
},
{
state: { ...scope(), at: "navigation.ready_to_commit", targetId: "target" },
intent: { kind: "navigation", targetId: "target", summarize: false },
terminalEvents: ["navigation_end"],
},
];
}
afterEach(async () => {
vi.useRealTimers();
vi.restoreAllMocks();
for (const session of sessions.splice(0)) await session.close(BACKGROUND_CONTEXT);
});
describe("runtime total drive", () => {
it("drives an ordinary run through all direct procedures with one before_drive hook", async () => {
const fixture = await createFixture();
await installOperation(fixture, {
state: { ...scope({ status: "running" }), at: "starting" },
intent: { kind: "run", promptEntryIds: ["tip"] },
terminalEvents: ["run_end"],
});
const beforeDrive = vi.fn();
fixture.hooks.on("before_drive", beforeDrive);
fixture.faux.setResponses([fauxAssistantMessage("answer")]);
expect(await driveOperation(fixture.lane, fixture.drive)).toMatchObject({
kind: "settled",
outcome: { operationId, kind: "run", status: "completed" },
});
expect(beforeDrive).toHaveBeenCalledTimes(1);
expect(fixture.faux.state.callCount).toBe(1);
expect(fixture.lane.state.operation).toBeNull();
});
it("leaves durable state unchanged when before_drive fails closed", async () => {
const fixture = await createFixture();
const starting = { ...scope({ status: "running" }), at: "starting" } as const;
await installOperation(fixture, {
state: starting,
intent: { kind: "run", promptEntryIds: ["tip"] },
terminalEvents: ["run_end"],
});
fixture.hooks.on("before_drive", () => {
throw new Error("blocked drive");
});
await expect(driveOperation(fixture.lane, fixture.drive)).rejects.toThrow("blocked drive");
expect(fixture.lane.state.operation?.state).toEqual(starting);
expect(fixture.storage.getCommitAttempts()).toEqual([]);
});
});
describe("runtime cancellation reconciliation", () => {
it("reconciles every durable leaf without ordinary hook admission", async () => {
for (let index = 0; index < 13; index++) {
const fixture = await createFixture();
const installed = cases(fixture)[index]!;
await installOperation(fixture, installed);
const beforeDrive = vi.fn();
fixture.hooks.on("before_drive", beforeDrive);
const outcome = await driveOperation(fixture.lane, fixture.drive);
expect(outcome).toMatchObject({
kind: "settled",
outcome: { operationId, status: "aborted" },
});
expect(beforeDrive).not.toHaveBeenCalled();
expect(fixture.lane.state.operation).toBeNull();
expect(await fixture.lane.getResult(operationId, BACKGROUND_CONTEXT)).toMatchObject({ status: "aborted" });
expect(
await fixture.session.getValue(storedValues.operationMeta(operationId), BACKGROUND_CONTEXT),
).toBeUndefined();
expect(
await fixture.session.getValue(storedValues.operationState(operationId), BACKGROUND_CONTEXT),
).toBeUndefined();
expect(
await fixture.session.scanValues(storedValues.operationToolArgsPrefix(operationId), BACKGROUND_CONTEXT),
).toEqual([]);
expect(
await fixture.session.scanValues(storedValues.operationToolMemoPrefix(operationId), BACKGROUND_CONTEXT),
).toEqual([]);
expect(
await fixture.session.scanValues(storedValues.operationPreparationPrefix(operationId), BACKGROUND_CONTEXT),
).toEqual([]);
if (installed.state.at === "deferred.suspended" || installed.state.at === "deferred.effect_pending") {
expect(fixture.faux.state.cancelledDeferred).toHaveLength(1);
}
expect(
fixture.events
.filter((event) => installed.terminalEvents.includes(event.type))
.slice(-installed.terminalEvents.length)
.map((event) => event.type),
).toEqual(installed.terminalEvents);
}
});
it("durably drains abortable input once and preserves lane-owned input through terminal cleanup", async () => {
const fixture = await createFixture();
await installOperation(fixture, {
state: {
...scope({ status: "running" }),
at: "checkpoint",
continuation: { kind: "need_assistant", overflowRecoveryUsed: false },
triggerEntryId: "tip",
},
intent: { kind: "run", promptEntryIds: ["tip"] },
terminalEvents: ["run_end"],
});
const queued = [
{ entryId: "steer", kind: "steer" as const, message: user("steer") },
{ entryId: "write", kind: "write" as const, message: user("write") },
{ entryId: "follow", kind: "followUp" as const, message: user("follow") },
{ entryId: "next", kind: "nextRun" as const, message: user("next") },
];
await fixture.lane.command(
(state) => ({
kind: "commit",
writes: [
...queued.map((item) =>
storedValues.setValue(storedValues.pendingEntry(item.entryId), {
type: "message" as const,
payload: item.message,
}),
),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox: queued.map(({ entryId, kind }) => ({ entryId, kind })),
}),
],
next: { ...state, inbox: queued.map(({ entryId, kind }) => ({ entryId, kind })) },
materialize: () => undefined,
}),
BACKGROUND_CONTEXT,
);
fixture.storage.clearCommitAttempts();
const requested = await fixture.lane.requestOperationAbort(operationId, BACKGROUND_CONTEXT);
expect(requested).toMatchObject({
ok: true,
value: {
operationId,
newlyRequested: true,
steer: [{ content: "steer" }],
followUp: [{ content: "follow" }],
},
});
expect(fixture.drive.gate.signal.aborted).toBe(true);
expect(fixture.drive.closeSignal.aborted).toBe(false);
expect(fixture.lane.state.inbox).toEqual([
{ entryId: "write", kind: "write" },
{ entryId: "next", kind: "nextRun" },
]);
expect(await fixture.session.getValue(storedValues.pendingEntry("steer"), BACKGROUND_CONTEXT)).toBeUndefined();
expect(await fixture.session.getValue(storedValues.pendingEntry("follow"), BACKGROUND_CONTEXT)).toBeUndefined();
expect(fixture.events.filter((event) => event.type === "operation_abort")).toMatchObject([
{ operationId, steer: [{ content: "steer" }], followUp: [{ content: "follow" }] },
]);
const commitCount = fixture.storage.getCommitAttempts().length;
const eventCount = fixture.events.length;
expect(await fixture.lane.requestOperationAbort(operationId, BACKGROUND_CONTEXT)).toEqual({
ok: true,
value: { operationId, newlyRequested: false, steer: [], followUp: [] },
});
expect(fixture.storage.getCommitAttempts()).toHaveLength(commitCount);
expect(fixture.events).toHaveLength(eventCount);
expect(await driveOperation(fixture.lane, fixture.drive)).toMatchObject({
kind: "settled",
outcome: { status: "aborted" },
});
expect(fixture.lane.state.inbox).toEqual([
{ entryId: "write", kind: "write" },
{ entryId: "next", kind: "nextRun" },
]);
expect(await fixture.session.getValue(storedValues.pendingEntry("write"), BACKGROUND_CONTEXT)).toBeDefined();
expect(await fixture.session.getValue(storedValues.pendingEntry("next"), BACKGROUND_CONTEXT)).toBeDefined();
fixture.storage.clearCommitAttempts();
const stale = await fixture.lane.requestOperationAbort(operationId, BACKGROUND_CONTEXT);
expect(stale.ok).toBe(false);
if (stale.ok) throw new Error("settled abort unexpectedly succeeded");
expect(stale.error._tag).toBe("OperationMismatch");
expect(fixture.storage.getCommitAttempts()).toEqual([]);
});
it("marks cancellation without installing a Drive", async () => {
const fixture = await createFixture();
await installOperation(fixture, {
state: { ...scope({ status: "running" }), at: "starting" },
intent: { kind: "run", promptEntryIds: ["tip"] },
terminalEvents: ["run_end"],
});
fixture.lane.activeDrive = undefined;
expect(await fixture.lane.requestOperationAbort(operationId, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { newlyRequested: true },
});
expect(fixture.lane.activeDrive).toBeUndefined();
expect(fixture.lane.state.operation?.state.control.status).toBe("cancel_requested");
});
it("cancels an admitted retry timer only after the abort marker commits", async () => {
vi.useFakeTimers();
vi.setSystemTime(1_000);
const fixture = await createFixture();
const generation = {
stepId: "step",
triggerEntryId: "tip",
configuration: fixture.configuration,
streamOptions: {},
retryPolicy: { maxAttempts: 2, baseDelayMs: 10 },
overflowRecoveryUsed: false,
};
await installOperation(fixture, {
state: {
...scope({ status: "running" }),
at: "assistant.retry_wait",
generationContext: generation,
nextAttempt: 2,
notBefore: 2_000,
errorMessage: "retry",
},
intent: { kind: "run", promptEntryIds: ["tip"] },
terminalEvents: ["run_end"],
});
const drive = new Drive({ operationId, waitForRetry: true }, BACKGROUND_CONTEXT);
fixture.lane.activeDrive = drive;
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
const running = driveOperation(fixture.lane, drive);
await vi.waitFor(() => expect(setTimeoutSpy).toHaveBeenCalled());
expect(await fixture.lane.requestOperationAbort(operationId, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { newlyRequested: true },
});
expect(await running).toMatchObject({ kind: "settled", outcome: { status: "aborted" } });
expect(fixture.events.some((event) => event.type === "retry_start")).toBe(false);
});
it("drops a stale structural hook result when cancellation commits first", async () => {
const fixture = await createFixture();
const task: SummaryTask = { taskId: "task", reason: "manual", boundary: { kind: "finish" } };
const deciding = { ...scope({ status: "running" }), at: "summary.deciding", task } as const;
await installOperation(fixture, {
state: deciding,
intent: { kind: "compaction" },
writes: [
storedValues.setValue(storedValues.operationPreparation(operationId, task.taskId), {
kind: "compaction",
messagesToSummarize: [user("history")],
turnPrefixMessages: [],
retainedTail: [],
isSplitTurn: false,
tokensBefore: 100,
fileOps: { read: [], written: [], edited: [] },
settings: DEFAULT_COMPACTION_SETTINGS,
}),
],
terminalEvents: ["compaction_end"],
});
let releaseHook!: () => void;
const hookStarted = new Promise<void>((resolve) => {
fixture.hooks.on("before_compaction", async () => {
resolve();
await new Promise<void>((release) => {
releaseHook = release;
});
return {
compaction: { summary: "stale", tokensBefore: 100, retainedTail: [] },
};
});
});
const running = runStructuralDecision(fixture.lane, fixture.drive, deciding);
await hookStarted;
await fixture.lane.requestOperationAbort(operationId, BACKGROUND_CONTEXT);
releaseHook();
expect(await running).toEqual({ kind: "continue" });
expect(await fixture.session.findEntries({ type: "compaction" }, BACKGROUND_CONTEXT)).toEqual([]);
expect(await driveOperation(fixture.lane, fixture.drive)).toMatchObject({
kind: "settled",
outcome: { status: "aborted" },
});
expect(fixture.events.at(-1)).toMatchObject({ type: "compaction_end", status: "aborted" });
});
it("keeps the deferred cleanup signal separate from operation abort", () => {
const drive = new Drive({ operationId }, BACKGROUND_CONTEXT);
drive.beginAbort(Promise.resolve());
drive.signalAbort();
expect(drive.gate.signal.aborted).toBe(true);
expect(drive.closeSignal.aborted).toBe(false);
const closed = new Error("closed");
drive.closeGate(closed);
expect(drive.closeSignal.aborted).toBe(true);
expect(drive.closeSignal.reason).toBe(closed);
});
it("can crash between cancelled response settlement and terminal cleanup", async () => {
const fixture = await createFixture();
const installed = cases(fixture)[3]!;
await installOperation(fixture, installed);
expect(await reconcileOperation(fixture.lane, fixture.drive)).toEqual({ kind: "continue" });
expect(fixture.lane.state.operation?.state.at).toBe("checkpoint");
expect(await fixture.session.getEntry("assistant-response", BACKGROUND_CONTEXT)).toMatchObject({
type: "message",
message: { stopReason: "aborted" },
});
expect(await fixture.lane.getResult(operationId, BACKGROUND_CONTEXT)).toBeUndefined();
expect(
await fixture.session.readList(
storedValues.pendingAssistantFrames(operationId, "assistant-response"),
undefined,
BACKGROUND_CONTEXT,
),
).toEqual([]);
const resumed = new Drive({ operationId }, BACKGROUND_CONTEXT);
fixture.lane.activeDrive = resumed;
expect(await driveOperation(fixture.lane, resumed)).toMatchObject({
kind: "settled",
outcome: { status: "aborted" },
});
});
it("ignores deferred-provider cancellation failure", async () => {
const fixture = await createFixture();
const deferred = deferredOperation(fixture, false);
const provider: Provider = {
...fixture.faux.provider,
cancelDeferred: async () => {
throw new Error("remote cancellation failed");
},
};
fixture.models.setProvider(provider);
await installOperation(fixture, {
state: deferred.state,
intent: { kind: "run", promptEntryIds: [] },
entries: [deferred.entry],
terminalEvents: ["run_end"],
});
expect(await driveOperation(fixture.lane, fixture.drive)).toMatchObject({
kind: "settled",
outcome: { status: "aborted" },
});
});
});

View File

@@ -0,0 +1,613 @@
import {
type AssistantMessage,
type DeferredFetchOptions,
fauxAssistantMessage,
fauxProvider,
fauxToolCall,
type MutableModels,
type Provider,
} from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createModels } from "../../../../ai/src/models.ts";
import type { HarnessEvent, WatchHandle } from "../../../src/harness/agent-harness.ts";
import { DEFAULT_COMPACTION_SETTINGS } from "../../../src/harness/compaction/compaction.ts";
import { BACKGROUND_CONTEXT } from "../../../src/harness/context.ts";
import { HookRegistry } from "../../../src/harness/hooks.ts";
import { runCheckpoint, startRun } from "../../../src/harness/runtime/drive/checkpoint.ts";
import { recoverDeferredPoll, runDeferred, runDeferredSuspended } from "../../../src/harness/runtime/drive/deferred.ts";
import { runGeneration } from "../../../src/harness/runtime/drive/generation.ts";
import { Lane } from "../../../src/harness/runtime/lane.ts";
import { restoreLane } from "../../../src/harness/runtime/restore.ts";
import { type Config, Drive } from "../../../src/harness/runtime/types.ts";
import { MemoryStorage } from "../../../src/harness/session/memory.ts";
import { StorageBackedSession } from "../../../src/harness/session/session.ts";
import { GatingStorage } from "../../../src/harness/session/testing/gating-storage.ts";
import { InstrumentedStorage } from "../../../src/harness/session/testing/instrumented-storage.ts";
import {
type AssistantReadyOperation,
type AssistantRetryWaitOperation,
type DeferredEffectPendingOperation,
type DeferredSuspendedOperation,
type LaneConfiguration,
type OperationState,
operationScopeOf,
type Session,
type Write,
} from "../../../src/harness/session/types.ts";
import * as storedValues from "../../../src/harness/session/values.ts";
import { deferred } from "./test-utils.ts";
const sessions: Session[] = [];
const operationId = "01950000-0000-7000-8000-000000000001";
interface Fixture {
backend: MemoryStorage;
gating: GatingStorage | undefined;
storage: InstrumentedStorage;
session: Session;
lane: Lane<undefined>;
drive: Drive;
models: MutableModels;
faux: ReturnType<typeof fauxProvider>;
hooks: HookRegistry;
events: HarnessEvent[];
config: Config<undefined>;
}
function unusedWatch<T>(): WatchHandle<T> {
throw new Error("watch is not used by retry/deferred tests");
}
async function createFixture(
options: { gated?: boolean; pendingFetches?: number; deferredSubmission?: boolean } = {},
): Promise<Fixture> {
const backend = new MemoryStorage({ now: () => 100 });
const gating = options.gated ? new GatingStorage(backend) : undefined;
const storage = new InstrumentedStorage(gating ?? backend);
const session = new StorageBackedSession(
{ id: `retry-deferred-${sessions.length}`, createdAt: 1, storageVersion: 1 },
storage,
);
sessions.push(session);
const faux = fauxProvider({
tokenSize: { min: 1, max: 1 },
deferred: { pendingFetches: options.pendingFetches ?? 0 },
});
const model = faux.getModel();
const configuration: LaneConfiguration = {
model: { provider: model.provider, modelId: model.id },
thinkingLevel: "off",
activeToolNames: [],
};
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.branchTip("main"), null),
storedValues.setValue(storedValues.laneConfig("main"), configuration),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const models = createModels();
models.setProvider(faux.provider);
const hooks = new HookRegistry(() => {});
const events: HarnessEvent[] = [];
const config: Config<undefined> = {
tools: [],
resources: {},
streamOptions: { deferred: options.deferredSubmission ?? true },
retryPolicy: { enabled: true, maxRetries: 3, baseDelayMs: 10 },
compaction: DEFAULT_COMPACTION_SETTINGS,
steeringMode: "all",
followUpMode: "all",
toolExecution: "parallel",
toolContext: undefined,
systemPrompt: undefined,
toProviderMessages: (messages) =>
messages.filter(
(message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult",
),
entryProjectors: {},
};
const lane = new Lane<undefined>(
"main",
session,
models,
hooks,
await restoreLane(session, "main", BACKGROUND_CONTEXT),
(cause) => (cause instanceof Error ? cause : new Error(String(cause))),
(batch) => {
events.push(...structuredClone(batch));
return Promise.resolve();
},
unusedWatch,
() => config,
);
const admission = await lane.accept({ kind: "prompt", operationId, prompt: "question" }, BACKGROUND_CONTEXT);
if (!admission.ok) throw admission.error;
const drive = new Drive({ operationId }, BACKGROUND_CONTEXT);
lane.activeDrive = drive;
storage.clearCommitAttempts();
return { backend, gating, storage, session, lane, drive, models, faux, hooks, events, config };
}
function currentRun(fixture: Fixture): OperationState {
const operation = fixture.lane.state.operation;
if (operation === null) throw new Error("fixture has no operation");
return operation.state;
}
async function advanceToReady(fixture: Fixture): Promise<AssistantReadyOperation> {
const starting = currentRun(fixture);
if (starting.at !== "starting") throw new Error("run did not start at its initial boundary");
await startRun(fixture.lane, fixture.drive, starting);
let run = currentRun(fixture);
if (run.at !== "checkpoint") throw new Error("run did not reach checkpoint");
await runCheckpoint(fixture.lane, fixture.drive, run);
run = currentRun(fixture);
if (run.at !== "assistant.ready") {
throw new Error("run did not reach ready generation");
}
return run;
}
async function replaceRunState(fixture: Fixture, nextState: OperationState, extraWrites: Write[] = []): Promise<void> {
await fixture.lane.command((state) => {
const operation = state.operation;
if (operation === null) throw new Error("fixture has no operation");
return {
kind: "commit",
writes: [...extraWrites, storedValues.setValue(storedValues.operationState(operationId), nextState)],
next: { ...state, operation: { meta: operation.meta, state: nextState } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
}
async function submitDeferred(
fixture: Fixture,
response: AssistantMessage = fauxAssistantMessage("done", { timestamp: 20 }),
): Promise<DeferredSuspendedOperation> {
fixture.faux.setResponses([response]);
const ready = await advanceToReady(fixture);
await runGeneration(fixture.lane, fixture.drive, ready);
const run = currentRun(fixture);
if (run.at !== "deferred.suspended") {
throw new Error("initial request did not suspend");
}
return run;
}
async function installUnknownPoll(
fixture: Fixture,
suspended: DeferredSuspendedOperation,
): Promise<DeferredEffectPendingOperation> {
const effectPending: DeferredEffectPendingOperation = {
...operationScopeOf(currentRun(fixture)),
at: "deferred.effect_pending",
stepId: suspended.stepId,
sourceEntryId: suspended.sourceEntryId,
poll: suspended.poll + 1,
responseEntryId: fixture.session.idGenerator.next(),
usageId: fixture.session.idGenerator.next(),
configuration: suspended.configuration,
streamOptions: suspended.streamOptions,
};
await replaceRunState(fixture, effectPending, [
storedValues.appendList(storedValues.pendingAssistantFrames(operationId, effectPending.responseEntryId), {
type: "text_delta",
contentIndex: 0,
delta: "old",
}),
]);
return effectPending;
}
function currentDeferred(fixture: Fixture): DeferredSuspendedOperation | DeferredEffectPendingOperation {
const run = currentRun(fixture);
if (run.at !== "deferred.suspended" && run.at !== "deferred.effect_pending") {
throw new Error("fixture has no deferred phase");
}
return run;
}
function installDrive(fixture: Fixture, options: { waitForRetry?: boolean; pollDeferred?: boolean }): Drive {
const drive = new Drive({ operationId, ...options }, BACKGROUND_CONTEXT);
fixture.lane.activeDrive = drive;
return drive;
}
async function expectProjectionRestores(fixture: Fixture): Promise<void> {
expect(fixture.lane.state).toEqual(await restoreLane(fixture.session, "main", BACKGROUND_CONTEXT));
}
afterEach(async () => {
vi.useRealTimers();
for (const session of sessions.splice(0)) await session.close(BACKGROUND_CONTEXT);
});
describe("runtime assistant retry wait", () => {
it("classifies a live retryable provider error into durable retry wait", async () => {
const fixture = await createFixture({ deferredSubmission: false });
const ready = await advanceToReady(fixture);
fixture.faux.setResponses([
fauxAssistantMessage([], { stopReason: "error", errorMessage: "503 service unavailable", timestamp: 10 }),
]);
expect(await runGeneration(fixture.lane, fixture.drive, ready)).toEqual({
kind: "continue",
});
expect(currentRun(fixture)).toMatchObject({
at: "assistant.retry_wait",
nextAttempt: 2,
errorMessage: "503 service unavailable",
});
expect(fixture.events.at(-1)).toMatchObject({ type: "retry_scheduled", attempt: 2 });
await expectProjectionRestores(fixture);
});
it("returns a durable waiting outcome without a timer or write when local waiting is disabled", async () => {
vi.useFakeTimers();
vi.setSystemTime(1_000);
const fixture = await createFixture({ deferredSubmission: false });
const ready = await advanceToReady(fixture);
const retryWait: AssistantRetryWaitOperation = {
...operationScopeOf(ready),
at: "assistant.retry_wait",
generationContext: ready.generationContext,
nextAttempt: 2,
notBefore: 1_100,
errorMessage: "retry",
};
await replaceRunState(fixture, retryWait);
fixture.storage.clearCommitAttempts();
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
expect(await runGeneration(fixture.lane, fixture.drive, retryWait)).toEqual({
kind: "waiting",
outcome: { kind: "waiting", operationId, reason: "retry", notBefore: 1_100 },
});
expect(setTimeoutSpy).not.toHaveBeenCalled();
expect(fixture.storage.getCommitAttempts()).toEqual([]);
});
it("commits ready at the deadline and emits retry lifecycle around the next attempt", async () => {
vi.useFakeTimers();
vi.setSystemTime(2_000);
const fixture = await createFixture({ deferredSubmission: false });
const ready = await advanceToReady(fixture);
const retryWait: AssistantRetryWaitOperation = {
...operationScopeOf(ready),
at: "assistant.retry_wait",
generationContext: ready.generationContext,
nextAttempt: 2,
notBefore: 2_000,
errorMessage: "retry",
};
await replaceRunState(fixture, retryWait);
fixture.storage.clearCommitAttempts();
expect(await runGeneration(fixture.lane, fixture.drive, retryWait)).toEqual({
kind: "continue",
});
const next = currentRun(fixture);
if (next.at !== "assistant.ready") {
throw new Error("retry did not become ready");
}
expect(fixture.events.at(-1)).toMatchObject({ type: "retry_start", attempt: 2 });
fixture.faux.setResponses([fauxAssistantMessage("retried", { timestamp: 30 })]);
await runGeneration(fixture.lane, fixture.drive, next);
expect(fixture.events).toContainEqual(expect.objectContaining({ type: "retry_end", attempt: 2, success: true }));
await expectProjectionRestores(fixture);
});
it("admits an abort-aware timer only for local waiting", async () => {
vi.useFakeTimers();
vi.setSystemTime(3_000);
const fixture = await createFixture({ deferredSubmission: false });
const ready = await advanceToReady(fixture);
const retryWait: AssistantRetryWaitOperation = {
...operationScopeOf(ready),
at: "assistant.retry_wait",
generationContext: ready.generationContext,
nextAttempt: 2,
notBefore: 3_100,
errorMessage: "retry",
};
await replaceRunState(fixture, retryWait);
const drive = installDrive(fixture, { waitForRetry: true });
fixture.storage.clearCommitAttempts();
const waiting = runGeneration(fixture.lane, drive, retryWait);
await vi.advanceTimersByTimeAsync(99);
expect(fixture.storage.getCommitAttempts()).toEqual([]);
await vi.advanceTimersByTimeAsync(1);
expect(await waiting).toEqual({ kind: "continue" });
expect(currentRun(fixture)).toMatchObject({
at: "assistant.ready",
nextAttempt: 2,
});
});
});
describe("runtime deferred polling", () => {
it("waits without a permit, then performs at most one pending poll from captured options", async () => {
const fixture = await createFixture({ pendingFetches: 1 });
const suspended = await submitDeferred(fixture);
fixture.storage.clearCommitAttempts();
expect(await runDeferredSuspended(fixture.lane, fixture.drive, suspended)).toEqual({
kind: "waiting",
outcome: {
kind: "waiting",
operationId,
reason: "deferred",
deferred: expect.objectContaining({ id: expect.any(String) }),
},
});
expect(fixture.faux.state.deferredFetchCount).toBe(0);
expect(fixture.storage.getCommitAttempts()).toEqual([]);
let hookOptions: unknown;
fixture.hooks.on("before_request", (event) => {
if (event.step === "deferred") hookOptions = event.streamOptions;
return undefined;
});
let fetchOptions: DeferredFetchOptions | undefined;
const provider = fixture.faux.provider;
const fetchDeferred = provider.fetchDeferred;
if (fetchDeferred === undefined) throw new Error("faux provider has no deferred fetch");
const wrapped: Provider = {
...provider,
fetchDeferred: (model, handle, options) => {
fetchOptions = options;
return fetchDeferred(model, handle, options);
},
};
fixture.models.setProvider(wrapped);
const driveOptions = { operationId, pollDeferred: true } as const;
const drive = new Drive(driveOptions, BACKGROUND_CONTEXT);
fixture.lane.activeDrive = drive;
expect(await runDeferred(fixture.lane, drive, currentDeferred(fixture))).toEqual({
kind: "continue",
});
expect(drive.deferredPermits).toBe(0);
expect(driveOptions).toEqual({ operationId, pollDeferred: true });
expect(hookOptions).toMatchObject({ deferred: false });
expect(fetchOptions).toMatchObject({ wait: 0 });
expect(fetchOptions?.signal).toBe(drive.gate.signal);
expect(currentDeferred(fixture)).toMatchObject({ at: "deferred.suspended", poll: 1 });
expect(fixture.faux.state.deferredFetchCount).toBe(1);
expect(fixture.events.slice(-2).map((event) => event.type)).toEqual(["turn_end", "run_suspend"]);
expect(await runDeferred(fixture.lane, drive, currentDeferred(fixture))).toMatchObject({
kind: "waiting",
outcome: { reason: "deferred" },
});
expect(fixture.faux.state.deferredFetchCount).toBe(1);
await expectProjectionRestores(fixture);
});
it("declines poll intent when cancellation wins preparation", async () => {
const fixture = await createFixture();
const suspended = await submitDeferred(fixture);
const drive = installDrive(fixture, { pollDeferred: true });
const hookStarted = deferred();
const releaseHook = deferred();
fixture.hooks.on("before_request", async (event) => {
if (event.step !== "deferred") return undefined;
hookStarted.resolve();
await releaseHook.promise;
return undefined;
});
const polling = runDeferredSuspended(fixture.lane, drive, suspended);
await hookStarted.promise;
await fixture.lane.command((state) => {
const operation = state.operation;
if (operation === null) throw new Error("missing operation");
const nextState: OperationState = {
...operation.state,
control: { status: "cancel_requested", requestedAt: 10 },
};
return {
kind: "commit",
writes: [storedValues.setValue(storedValues.operationState(operationId), nextState)],
next: { ...state, operation: { meta: operation.meta, state: nextState } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
releaseHook.resolve();
expect(await polling).toEqual({ kind: "continue" });
expect(fixture.faux.state.deferredFetchCount).toBe(0);
expect(currentRun(fixture)).toMatchObject({
at: "deferred.suspended",
control: { status: "cancel_requested" },
});
});
it("plans ready deferred tool calls with the poll turn identity and follower result ids", async () => {
const fixture = await createFixture();
const suspended = await submitDeferred(
fixture,
fauxAssistantMessage(fauxToolCall("lookup", { query: "value" }), {
stopReason: "toolUse",
timestamp: 20,
}),
);
const drive = installDrive(fixture, { pollDeferred: true });
expect(await runDeferredSuspended(fixture.lane, drive, suspended)).toEqual({
kind: "continue",
});
const run = currentRun(fixture);
if (run.at !== "tools") throw new Error("deferred tool response did not create a batch");
expect(run.batch).toMatchObject({
turnId: `${suspended.stepId}:poll:1`,
calls: [{ status: "planned", sourceIndex: 0 }],
});
expect(run.batch.calls[0]?.resultEntryId.slice(0, 13)).toBe(run.latestAssistantEntryId?.slice(0, 13));
expect(fixture.events.at(-1)).toMatchObject({ type: "usage" });
await expectProjectionRestores(fixture);
});
it("consumes its permit only after the fresh intent commit lands", async () => {
const fixture = await createFixture({ gated: true });
const suspended = await submitDeferred(
fixture,
fauxAssistantMessage([], { stopReason: "error", errorMessage: "failed", timestamp: 20 }),
);
const gating = fixture.gating;
if (gating === undefined) throw new Error("fixture is not gated");
const drive = installDrive(fixture, { pollDeferred: true });
gating.arm();
const polling = runDeferredSuspended(fixture.lane, drive, suspended);
await gating.waitPending();
expect(drive.deferredPermits).toBe(1);
expect(fixture.faux.state.deferredFetchCount).toBe(0);
await gating.next();
expect(drive.deferredPermits).toBe(0);
await expect.poll(() => fixture.faux.state.deferredFetchCount).toBe(1);
await gating.next(2); // start frame, then response settlement
expect(await polling).toMatchObject({
kind: "settled",
outcome: { operationId, kind: "run", status: "failed" },
});
expect(fixture.lane.state.operation).toBeNull();
});
it("leaves suspended state unchanged when the fresh intent is discarded", async () => {
const fixture = await createFixture({ gated: true });
const suspended = await submitDeferred(fixture);
const gating = fixture.gating;
if (gating === undefined) throw new Error("fixture is not gated");
const drive = installDrive(fixture, { pollDeferred: true });
gating.arm();
const polling = runDeferredSuspended(fixture.lane, drive, suspended);
void polling.catch(() => {});
await gating.waitPending();
gating.discard();
await expect(polling).rejects.toThrow("commit discarded");
expect(drive.deferredPermits).toBe(1);
expect(fixture.faux.state.deferredFetchCount).toBe(0);
const durable = await fixture.backend.getValue(storedValues.operationState(operationId), BACKGROUND_CONTEXT);
expect(durable?.value).toMatchObject({ at: "deferred.suspended", poll: 0 });
});
it("replaces an unknown poll under fresh ids at the same poll number and deletes old frames", async () => {
const fixture = await createFixture();
const suspended = await submitDeferred(fixture);
const unknown = await installUnknownPoll(fixture, suspended);
fixture.storage.clearCommitAttempts();
const noPermit = installDrive(fixture, {});
expect(await recoverDeferredPoll(fixture.lane, noPermit, unknown)).toMatchObject({
kind: "waiting",
outcome: { reason: "deferred" },
});
expect(
await fixture.session.readList(
storedValues.pendingAssistantFrames(operationId, unknown.responseEntryId),
undefined,
BACKGROUND_CONTEXT,
),
).toHaveLength(1);
expect(fixture.faux.state.deferredFetchCount).toBe(0);
const replacement = installDrive(fixture, { pollDeferred: true });
expect(await recoverDeferredPoll(fixture.lane, replacement, unknown)).toEqual({
kind: "continue",
});
const intent = fixture.storage
.getCommitAttempts()
.find((writes) =>
writes.some(
(write) =>
write.kind === "list" &&
write.op === "delete" &&
write.namespace === "pi.pending.assistant_frame" &&
write.key.endsWith(unknown.responseEntryId),
),
);
expect(intent?.map((write) => `${write.kind}:${"op" in write ? write.op : "insert"}`)).toEqual([
"list:delete",
"value:set",
]);
const intentState = intent?.find(
(write) => write.kind === "value" && write.op === "set" && write.namespace === "pi.op.state",
);
expect(intentState).toMatchObject({
value: {
at: "deferred.effect_pending",
poll: unknown.poll,
responseEntryId: expect.not.stringMatching(unknown.responseEntryId),
usageId: expect.not.stringMatching(unknown.usageId),
},
});
expect(await fixture.session.getEntry(unknown.responseEntryId, BACKGROUND_CONTEXT)).toBeUndefined();
expect((await fixture.storage.scanUsage({}, BACKGROUND_CONTEXT)).some((row) => row.id === unknown.usageId)).toBe(
false,
);
expect(
await fixture.session.readList(
storedValues.pendingAssistantFrames(operationId, unknown.responseEntryId),
undefined,
BACKGROUND_CONTEXT,
),
).toEqual([]);
expect(currentRun(fixture).at).toBe("checkpoint");
expect(fixture.events.some((event) => "recovery" in event && event.recovery === true)).toBe(true);
await expectProjectionRestores(fixture);
});
it("abandons unknown ids and frames into configuration failure when the captured model is unavailable", async () => {
const fixture = await createFixture();
const suspended = await submitDeferred(fixture);
const unknown = await installUnknownPoll(fixture, suspended);
fixture.models.deleteProvider(fixture.faux.provider.id);
fixture.storage.clearCommitAttempts();
const drive = installDrive(fixture, { pollDeferred: true });
expect(await recoverDeferredPoll(fixture.lane, drive, unknown)).toMatchObject({
kind: "settled",
outcome: { operationId, kind: "run", status: "failed", error: { code: "model_unavailable" } },
});
expect(drive.deferredPermits).toBe(1);
expect(fixture.faux.state.deferredFetchCount).toBe(0);
expect(fixture.lane.state.operation).toBeNull();
expect(
fixture.storage
.getCommitAttempts()
.at(-1)
?.map((write) =>
write.kind === "value" || write.kind === "list"
? `${write.kind}:${write.op}:${write.namespace}`
: write.kind,
),
).toEqual([
"value:delete:pi.op.meta",
"value:delete:pi.op.state",
"list:delete:pi.pending.assistant_frame",
"value:set:pi.result",
"value:set:pi.lane.state",
]);
expect(
fixture.storage
.getCommitAttempts()
.flat()
.some((write) => write.kind === "entry" || write.kind === "usage"),
).toBe(false);
await expectProjectionRestores(fixture);
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,289 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { BACKGROUND_CONTEXT } from "../../../src/harness/context.ts";
import { operationCleanupWrites, operationResultRecord } from "../../../src/harness/runtime/drive/terminal.ts";
import { MemoryStorage } from "../../../src/harness/session/memory.ts";
import { StorageBackedSession } from "../../../src/harness/session/session.ts";
import type {
AssistantEffectPendingOperation,
LaneConfiguration,
NavigationReadyToCommitOperation,
OperationMeta,
OperationScope,
OperationState,
Session,
SummaryDecidingOperation,
ToolsOperation,
Write,
} from "../../../src/harness/session/types.ts";
import * as storedValues from "../../../src/harness/session/values.ts";
const sessions: Session[] = [];
const configuration: LaneConfiguration = {
model: { provider: "test", modelId: "model" },
thinkingLevel: "off",
activeToolNames: [],
};
function runScope(): OperationScope {
return {
control: { status: "running" },
settings: {
compaction: { enabled: true, reserveTokens: 1_000, keepRecentTokens: 2_000 },
steeringMode: "all",
followUpMode: "all",
toolExecution: "parallel",
},
latestAssistantEntryId: null,
};
}
function meta(operationId: string, state: OperationState): OperationMeta {
const intent: OperationMeta["intent"] =
state.at === "summary.deciding"
? { kind: "compaction" }
: state.at === "navigation.ready_to_commit"
? { kind: "navigation", targetId: state.targetId, summarize: false }
: { kind: "run", promptEntryIds: [] };
return { operationId, lane: "main", sourceTipId: null, startedAt: 1, intent };
}
async function createSession(): Promise<{ session: Session; storage: MemoryStorage }> {
const storage = new MemoryStorage();
const session = new StorageBackedSession(
{ id: `terminal-${sessions.length}`, createdAt: 1, storageVersion: 1 },
storage,
);
sessions.push(session);
return { session, storage };
}
async function commit(session: Session, writes: Write[]): Promise<void> {
await session.mutate(async (mutator) => {
await mutator.commit(writes, BACKGROUND_CONTEXT);
}, BACKGROUND_CONTEXT);
}
function address(write: Write): string {
if (write.kind === "entry") return `entry:${write.entry.id}`;
if (write.kind === "usage") return `usage:${write.row.id}`;
return `${write.kind}:${write.op}:${write.namespace}:${write.key}`;
}
async function seedLeftovers(session: Session, operationId: string, state: OperationState): Promise<void> {
await commit(session, [
storedValues.setValue(storedValues.operationMeta(operationId), meta(operationId, state)),
storedValues.setValue(storedValues.operationState(operationId), state),
storedValues.setValue(storedValues.operationToolArgs(operationId, "step", 0), { value: true }),
storedValues.setValue(storedValues.operationToolMemo(operationId, "invocation", "memo"), { value: true }),
storedValues.setValue(storedValues.operationPreparation(operationId, "task"), {
kind: "branch_summary",
messages: [],
fileOps: { read: [], written: [], edited: [] },
totalTokens: 0,
}),
storedValues.setValue(storedValues.pendingToolOutput(operationId, "invocation"), {
content: [{ type: "text", text: "partial" }],
details: {},
}),
]);
}
afterEach(async () => {
vi.restoreAllMocks();
for (const session of sessions.splice(0)) await session.close(BACKGROUND_CONTEXT);
});
describe("runtime terminal cleanup mechanics", () => {
it("deletes every operation-owned family and exact live frame list but preserves the lane inbox", async () => {
const { session } = await createSession();
const operationId = "run";
const responseEntryId = "response";
const state: AssistantEffectPendingOperation = {
...runScope(),
at: "assistant.effect_pending",
generationContext: {
stepId: "step",
triggerEntryId: "trigger",
configuration,
streamOptions: {},
retryPolicy: { maxAttempts: 2, baseDelayMs: 1 },
overflowRecoveryUsed: false,
},
attempt: 1,
responseEntryId,
usageId: "usage",
intendedOutputLimit: 100,
contextWindow: 1_000,
control: { status: "cancel_requested", requestedAt: 2 },
};
await seedLeftovers(session, operationId, state);
await commit(session, [
...["steer", "follow", "write", "next"].map((id) =>
storedValues.setValue(storedValues.pendingEntry(id), {
type: "custom",
customType: "test",
payload: { id },
}),
),
storedValues.appendList(storedValues.pendingAssistantFrames(operationId, responseEntryId), {
type: "text_delta",
contentIndex: 0,
delta: "partial",
}),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox: [
{ entryId: "steer", kind: "steer" },
{ entryId: "follow", kind: "followUp" },
{ entryId: "write", kind: "write" },
{ entryId: "next", kind: "nextRun" },
],
}),
]);
const writes = await operationCleanupWrites(session, operationId, state, BACKGROUND_CONTEXT);
expect(writes.map(address)).toEqual([
"value:delete:pi.op.meta:run",
"value:delete:pi.op.state:run",
"value:delete:pi.op.tool_args:run:step:0",
"value:delete:pi.op.tool_memo:run:invocation:memo",
"value:delete:pi.op.preparation:run:task",
"value:delete:pi.pending.tool_output:run:invocation",
"list:delete:pi.pending.assistant_frame:run:response",
]);
await commit(session, writes);
for (const id of ["steer", "follow", "write", "next"]) {
expect(await session.getValue(storedValues.pendingEntry(id), BACKGROUND_CONTEXT)).toBeDefined();
}
expect((await session.getValue(storedValues.laneState("main"), BACKGROUND_CONTEXT))?.value.inbox).toEqual([
{ entryId: "steer", kind: "steer" },
{ entryId: "follow", kind: "followUp" },
{ entryId: "write", kind: "write" },
{ entryId: "next", kind: "nextRun" },
]);
expect(
await session.readList(
storedValues.pendingAssistantFrames(operationId, responseEntryId),
undefined,
BACKGROUND_CONTEXT,
),
).toEqual([]);
});
it("deletes staged tool outcomes and leaves completed results alone", async () => {
const { session } = await createSession();
const operationId = "tools";
const state: ToolsOperation = {
...runScope(),
at: "tools",
batch: {
assistantEntryId: "assistant",
configuration,
turnId: "step",
calls: [
{ status: "outcome_ready", sourceIndex: 0, resultEntryId: "staged", terminate: false },
{ status: "completed", sourceIndex: 1, resultEntryId: "placed", terminate: false },
],
},
};
await seedLeftovers(session, operationId, state);
await commit(session, [
storedValues.setValue(storedValues.pendingEntry("staged"), {
type: "message",
payload: {
role: "toolResult",
toolCallId: "call",
toolName: "tool",
content: [],
isError: false,
timestamp: 1,
},
}),
]);
const writes = await operationCleanupWrites(session, operationId, state, BACKGROUND_CONTEXT);
expect(writes.map(address)).toContain("value:delete:pi.pending.entry:staged");
expect(writes.map(address)).not.toContain("value:delete:pi.pending.entry:placed");
});
it.each([
[
"compaction",
{
...runScope(),
at: "summary.deciding",
task: { taskId: "task", reason: "manual", boundary: { kind: "finish" } },
} satisfies SummaryDecidingOperation,
],
[
"navigation",
{
...runScope(),
at: "navigation.ready_to_commit",
targetId: null,
} satisfies NavigationReadyToCommitOperation,
],
] as const)("defensively deletes leftover %s operation families", async (operationId, state) => {
const { session } = await createSession();
await seedLeftovers(session, operationId, state);
const writes = await operationCleanupWrites(session, operationId, state, BACKGROUND_CONTEXT);
expect(writes.map(address)).toEqual([
`value:delete:pi.op.meta:${operationId}`,
`value:delete:pi.op.state:${operationId}`,
`value:delete:pi.op.tool_args:${operationId}:step:0`,
`value:delete:pi.op.tool_memo:${operationId}:invocation:memo`,
`value:delete:pi.op.preparation:${operationId}:task`,
`value:delete:pi.pending.tool_output:${operationId}:invocation`,
]);
});
});
describe("runtime operation result records", () => {
it("constructs one flat immutable observation from terminal metadata", () => {
vi.spyOn(Date, "now").mockReturnValue(20);
const record = operationResultRecord(
{
operationId: "run",
lane: "main",
sourceTipId: "source",
startedAt: 10,
intent: { kind: "run", promptEntryIds: ["prompt"] },
},
"failed",
"tip",
{ code: "provider", message: "failed" },
);
expect(record).toEqual({
operationId: "run",
kind: "run",
status: "failed",
error: { code: "provider", message: "failed" },
fromTipId: "source",
tipId: "tip",
startedAt: 10,
endedAt: 20,
});
});
it("rejects errors on non-failed records and missing errors on failed records", () => {
const metadata: OperationMeta = {
operationId: "run",
lane: "main",
sourceTipId: null,
startedAt: 1,
intent: { kind: "run", promptEntryIds: [] },
};
expect(() => operationResultRecord(metadata, "completed", "tip", { code: "x", message: "x" })).toThrow(
"Only a failed operation result may carry an error",
);
expect(() => operationResultRecord(metadata, "failed", "tip")).toThrow(
"Only a failed operation result may carry an error",
);
});
});

View File

@@ -0,0 +1,763 @@
import {
createModels,
fauxAssistantMessage,
fauxProvider,
fauxToolCall,
type ToolResultMessage,
} from "@earendil-works/pi-ai";
import { Type } from "typebox";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { HarnessEvent, WatchHandle } from "../../../src/harness/agent-harness.ts";
import { DEFAULT_COMPACTION_SETTINGS } from "../../../src/harness/compaction/compaction.ts";
import { BACKGROUND_CONTEXT, type Context } from "../../../src/harness/context.ts";
import { HookRegistry } from "../../../src/harness/hooks.ts";
import { runTools } from "../../../src/harness/runtime/drive/tools.ts";
import { Lane } from "../../../src/harness/runtime/lane.ts";
import { restoreLane } from "../../../src/harness/runtime/restore.ts";
import { type Config, Drive } from "../../../src/harness/runtime/types.ts";
import { insertEntry } from "../../../src/harness/session/commit.ts";
import { MemoryStorage } from "../../../src/harness/session/memory.ts";
import { StorageBackedSession } from "../../../src/harness/session/session.ts";
import type {
LaneConfiguration,
OperationState,
Session,
ToolCall,
ToolsOperation,
Write,
} from "../../../src/harness/session/types.ts";
import * as storedValues from "../../../src/harness/session/values.ts";
import type {
AgentHarnessTool,
AgentHarnessToolInvocation,
AgentHarnessToolUpdateCallback,
} from "../../../src/harness/types.ts";
const sessions: Session[] = [];
const schema = Type.Object({ value: Type.String() });
interface Fixture {
session: Session;
lane: Lane<undefined>;
drive: Drive;
hooks: HookRegistry;
events: HarnessEvent[];
assistantEntryId: string;
resultEntryIds: string[];
operationId: string;
observations: string[];
}
interface FixtureOptions {
calls: Array<{ name: string; value?: string }>;
tools?: AgentHarnessTool<undefined>[];
mode?: "sequential" | "parallel";
stopReason?: "toolUse" | "length";
callStates?: (resultEntryIds: string[]) => ToolCall[];
extraWrites?: (fixture: {
operationId: string;
assistantEntryId: string;
resultEntryIds: string[];
run: ToolsOperation;
}) => Write[];
toolContext?: Config<undefined>["toolContext"];
cancelled?: boolean;
onEmit?: (events: readonly HarnessEvent[]) => Promise<void>;
}
function unusedWatch<T>(): WatchHandle<T> {
throw new Error("watch is not used by tool procedure tests");
}
class ObservedMemoryStorage extends MemoryStorage {
readonly observations: string[] = [];
override async commit(writes: Write[], context: Context) {
const result = await super.commit(writes, context);
if (
writes.some((write) => write.kind === "value" && write.op === "set" && write.namespace === "pi.op.tool_args")
) {
this.observations.push("intent_commit");
}
const stagesOutcome = writes.some(
(write) => write.kind === "value" && write.op === "set" && write.namespace === "pi.pending.entry",
);
if (stagesOutcome) this.observations.push("outcome_commit");
if (
!stagesOutcome &&
writes.some(
(write) => write.kind === "value" && write.op === "delete" && write.namespace === "pi.pending.tool_output",
)
) {
this.observations.push("replay_commit");
}
return result;
}
}
function deferred<T = void>(): { promise: Promise<T>; resolve(value: T): void } {
let resolvePromise: ((value: T) => void) | undefined;
const promise = new Promise<T>((resolve) => {
resolvePromise = resolve;
});
return { promise, resolve: (value) => resolvePromise?.(value) };
}
async function waitFor(predicate: () => boolean): Promise<void> {
for (let index = 0; index < 200; index += 1) {
if (predicate()) return;
await new Promise((resolve) => setTimeout(resolve, 0));
}
throw new Error("condition was not reached");
}
function tool(
name: string,
execute: (
value: string,
onUpdate: AgentHarnessToolUpdateCallback<{ progress?: string }> | undefined,
invocation: AgentHarnessToolInvocation,
context: Parameters<AgentHarnessTool<undefined>["execute"]>[5],
) => Promise<{
content: Array<{ type: "text"; text: string }>;
details: { value?: string };
usage?: ToolResultMessage["usage"];
addedToolNames?: string[];
terminate?: boolean;
}>,
replay: "never" | "safe" = "never",
): AgentHarnessTool<undefined, typeof schema, { progress?: string } | { value?: string }> {
return {
name,
label: name,
description: name,
parameters: schema,
replay,
execute: async (_toolCallId, args, onUpdate, _toolContext, invocation, context) =>
execute(args.value, onUpdate, invocation, context),
};
}
async function createFixture(options: FixtureOptions): Promise<Fixture> {
const storage = new ObservedMemoryStorage({ now: () => 100 });
const session = new StorageBackedSession(
{ id: `drive-tools-${sessions.length}`, createdAt: 1, storageVersion: 1 },
storage,
);
sessions.push(session);
const operationId = session.idGenerator.next(10);
const assistantEntryId = session.idGenerator.next(20);
const resultEntryIds = options.calls.map(() => session.idGenerator.next(20));
const faux = fauxProvider();
const model = faux.getModel();
const configuration: LaneConfiguration = {
model: { provider: model.provider, modelId: model.id },
thinkingLevel: "off",
activeToolNames: options.calls.map(({ name }) => name),
};
const assistant = fauxAssistantMessage(
options.calls.map(({ name, value = name }, index) => fauxToolCall(name, { value }, { id: `call-${index}` })),
{ stopReason: options.stopReason ?? "toolUse", timestamp: 20 },
);
const calls =
options.callStates?.(resultEntryIds) ??
resultEntryIds.map((resultEntryId, index) => ({
status: "planned" as const,
sourceIndex: index,
resultEntryId,
}));
const run: ToolsOperation = {
at: "tools",
control: options.cancelled ? { status: "cancel_requested", requestedAt: 30 } : { status: "running" },
settings: {
compaction: DEFAULT_COMPACTION_SETTINGS,
steeringMode: "all",
followUpMode: "all",
toolExecution: options.mode ?? "parallel",
},
batch: { assistantEntryId, configuration, turnId: "turn-1", calls },
latestAssistantEntryId: assistantEntryId,
};
const writes: Write[] = [
insertEntry({ id: assistantEntryId, parentId: null, type: "message", message: assistant }),
storedValues.setValue(storedValues.branchTip("main"), assistantEntryId),
storedValues.setValue(storedValues.laneConfig("main"), configuration),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox: [],
}),
storedValues.setValue(storedValues.operationMeta(operationId), {
operationId,
lane: "main",
sourceTipId: null,
startedAt: 10,
intent: { kind: "run", promptEntryIds: [] },
}),
storedValues.setValue(storedValues.operationState(operationId), run),
...(options.extraWrites?.({ operationId, assistantEntryId, resultEntryIds, run }) ?? []),
];
await session.mutate((mutator) => mutator.commit(writes, BACKGROUND_CONTEXT), BACKGROUND_CONTEXT);
const models = createModels();
models.setProvider(faux.provider);
const hooks = new HookRegistry(() => {});
const events: HarnessEvent[] = [];
const config: Config<undefined> = {
tools: options.tools ?? [],
resources: {},
streamOptions: {},
retryPolicy: { enabled: false, maxRetries: 0, baseDelayMs: 0 },
compaction: DEFAULT_COMPACTION_SETTINGS,
steeringMode: "all",
followUpMode: "all",
toolExecution: options.mode ?? "parallel",
toolContext: options.toolContext,
systemPrompt: undefined,
toProviderMessages: (messages) =>
messages.filter(
(message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult",
),
entryProjectors: {},
};
const lane = new Lane<undefined>(
"main",
session,
models,
hooks,
await restoreLane(session, "main", BACKGROUND_CONTEXT),
(cause) => (cause instanceof Error ? cause : new Error(String(cause))),
async (batch) => {
events.push(...structuredClone(batch));
storage.observations.push(...batch.map((event) => event.type));
await options.onEmit?.(batch);
},
unusedWatch,
() => config,
);
const drive = new Drive({ operationId }, BACKGROUND_CONTEXT);
lane.activeDrive = drive;
return {
session,
lane,
drive,
hooks,
events,
assistantEntryId,
resultEntryIds,
operationId,
observations: storage.observations,
};
}
function currentRun(fixture: Fixture): OperationState {
const operation = fixture.lane.state.operation;
if (operation === null) throw new Error("fixture has no operation");
return operation.state;
}
function currentCalls(fixture: Fixture): ToolCall[] {
const run = currentRun(fixture);
if (run.at !== "tools") return [];
return run.batch.calls;
}
async function driveTools(fixture: Fixture) {
const run = currentRun(fixture);
if (run.at !== "tools") throw new Error("fixture has no tool batch");
return runTools(fixture.lane, fixture.drive, run);
}
async function expectProjectionRestores(fixture: Fixture): Promise<void> {
expect(await restoreLane(fixture.session, "main", BACKGROUND_CONTEXT)).toEqual(fixture.lane.state);
}
afterEach(async () => {
await Promise.all(sessions.splice(0).map((session) => session.close(BACKGROUND_CONTEXT)));
});
describe("durable tool batch", () => {
it("executes a sequential batch with memos, checkpoints, hooks, usage, and source-order placement", async () => {
let contextResolutions = 0;
let lateInvocation: AgentHarnessToolInvocation | undefined;
const usage = {
input: 1,
output: 2,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 3,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
};
const first = tool("first", async (value, onUpdate, invocation) => {
lateInvocation = invocation;
await invocation.setMemo("step/a", { value: "memo" });
expect(await invocation.getMemo("step/a")).toEqual({ value: "memo" });
onUpdate?.(
{ content: [{ type: "text", text: "partial" }], details: { progress: "partial" } },
{ checkpoint: true },
);
return { content: [{ type: "text", text: value }], details: { value }, usage };
});
const second = tool("second", async (value) => ({
content: [{ type: "text", text: value }],
details: { value },
addedToolNames: ["introduced"],
}));
const fixture = await createFixture({
calls: [{ name: "first" }, { name: "second" }],
tools: [first, second],
mode: "sequential",
toolContext: () => {
contextResolutions += 1;
return undefined;
},
});
let checkpointSeenByAfterHook = false;
fixture.hooks.on("before_tool", ({ toolName, args }) =>
toolName === "first" ? { args: { ...args, value: "prepared" } } : undefined,
);
fixture.hooks.on("after_tool", async ({ toolName }) => {
if (toolName === "first") {
checkpointSeenByAfterHook =
(await fixture.session.getValue(
storedValues.pendingToolOutput(fixture.operationId, fixture.resultEntryIds[0]!),
BACKGROUND_CONTEXT,
)) !== undefined;
}
return undefined;
});
expect(await driveTools(fixture)).toEqual({ kind: "continue" });
const transcript = await fixture.lane.findEntries({ order: "oldestFirst" }, BACKGROUND_CONTEXT);
expect(transcript.map(({ id }) => id)).toEqual([fixture.assistantEntryId, ...fixture.resultEntryIds]);
expect(transcript[1]?.type === "message" ? transcript[1].message : undefined).toMatchObject({
role: "toolResult",
content: [{ type: "text", text: "prepared" }],
});
expect(contextResolutions).toBe(1);
expect(checkpointSeenByAfterHook).toBe(true);
expect(currentRun(fixture)).toMatchObject({
at: "checkpoint",
continuation: { kind: "need_assistant" },
});
expect(fixture.lane.state.configuration.activeToolNames).toEqual(["first", "second", "introduced"]);
expect(
await fixture.session.scanValues(
storedValues.operationToolArgsPrefix(fixture.operationId),
BACKGROUND_CONTEXT,
),
).toEqual([]);
expect(
await fixture.session.scanValues(
storedValues.operationToolMemoPrefix(fixture.operationId),
BACKGROUND_CONTEXT,
),
).toEqual([]);
expect(
await fixture.session.getValue(
storedValues.pendingToolOutput(fixture.operationId, fixture.resultEntryIds[0]!),
BACKGROUND_CONTEXT,
),
).toBeUndefined();
await expect(lateInvocation?.setMemo("late", true)).rejects.toThrow("no longer owns");
const eventTypes = fixture.events.map(({ type }) => type);
expect(eventTypes.indexOf("tool_update")).toBeLessThan(eventTypes.indexOf("tool_end"));
const firstIntent = fixture.observations.indexOf("intent_commit");
const firstStart = fixture.observations.indexOf("tool_start");
const firstUpdate = fixture.observations.indexOf("tool_update");
const firstCommit = fixture.observations.indexOf("outcome_commit");
const firstEnd = fixture.observations.indexOf("tool_end");
const firstPlacement = fixture.observations.indexOf("entry_added");
expect(firstIntent).toBeLessThan(firstStart);
expect(firstStart).toBeLessThan(firstUpdate);
expect(firstUpdate).toBeLessThan(firstCommit);
expect(firstCommit).toBeLessThan(firstEnd);
expect(firstEnd).toBeLessThan(firstPlacement);
expect(fixture.events.filter(({ type }) => type === "entry_added")).toHaveLength(2);
expect(fixture.events.filter(({ type }) => type === "usage")).toHaveLength(1);
expect(fixture.events.at(-1)?.type).toBe("turn_end");
await expectProjectionRestores(fixture);
});
it("stages parallel completion order but materializes source order", async () => {
const finishA = deferred<void>();
const finishB = deferred<void>();
const started: string[] = [];
const make = (name: string, finish: ReturnType<typeof deferred<void>>) =>
tool(name, async () => {
started.push(name);
await finish.promise;
return { content: [{ type: "text", text: name }], details: { value: name } };
});
const fixture = await createFixture({
calls: [{ name: "a" }, { name: "b" }],
tools: [make("a", finishA), make("b", finishB)],
mode: "parallel",
});
const running = driveTools(fixture);
await waitFor(() => started.length === 2);
finishB.resolve();
await waitFor(() => currentCalls(fixture)[1]?.status === "outcome_ready");
expect(currentCalls(fixture).map(({ status }) => status)).toEqual(["effect_pending", "outcome_ready"]);
expect(await fixture.session.getEntry(fixture.resultEntryIds[1]!, BACKGROUND_CONTEXT)).toBeUndefined();
expect(fixture.events.find((event) => event.type === "tool_start" && event.toolName === "b")).toMatchObject({
args: { value: "b" },
});
expect(fixture.events.find((event) => event.type === "tool_end" && event.toolName === "b")).toMatchObject({
result: { content: [{ type: "text", text: "b" }] },
});
finishA.resolve();
await running;
const transcript = await fixture.lane.findEntries({ order: "oldestFirst" }, BACKGROUND_CONTEXT);
expect(transcript.map(({ id }) => id)).toEqual([
fixture.assistantEntryId,
fixture.resultEntryIds[0],
fixture.resultEntryIds[1],
]);
await expectProjectionRestores(fixture);
});
it("safe-replays persisted arguments and memos while interrupting unsafe effects", async () => {
const safeExecute = vi.fn(async (_value: string, _onUpdate: unknown, invocation: AgentHarnessToolInvocation) => {
expect(invocation.invocationId).toBeDefined();
expect(await invocation.getMemo("step/a")).toEqual({ complete: true });
return { content: [{ type: "text" as const, text: "safe replay" }], details: { value: "safe" } };
});
const unsafeExecute = vi.fn(async () => ({
content: [{ type: "text" as const, text: "must not run" }],
details: {},
}));
const fixture = await createFixture({
calls: [{ name: "safe" }, { name: "unsafe" }],
tools: [tool("safe", safeExecute, "safe"), tool("unsafe", unsafeExecute, "never")],
callStates: (ids) => [
{ status: "effect_pending", sourceIndex: 0, resultEntryId: ids[0]!, replay: "safe" },
{ status: "effect_pending", sourceIndex: 1, resultEntryId: ids[1]!, replay: "never" },
],
extraWrites: ({ operationId, resultEntryIds }) => [
storedValues.setValue(storedValues.operationToolArgs(operationId, "turn-1", 0), { value: "persisted" }),
storedValues.setValue(storedValues.operationToolArgs(operationId, "turn-1", 1), { value: "unsafe" }),
storedValues.setValue(storedValues.operationToolMemo(operationId, resultEntryIds[0]!, "step/a"), {
complete: true,
}),
storedValues.setValue(storedValues.pendingToolOutput(operationId, resultEntryIds[0]!), {
content: [{ type: "text", text: "old progress" }],
details: {},
}),
storedValues.setValue(storedValues.pendingToolOutput(operationId, resultEntryIds[1]!), {
content: [{ type: "text", text: "durable partial" }],
details: { progress: "kept" },
}),
],
});
await driveTools(fixture);
expect(safeExecute).toHaveBeenCalledOnce();
expect(safeExecute.mock.calls[0]?.[0]).toBe("persisted");
expect(unsafeExecute).not.toHaveBeenCalled();
expect(
fixture.events.find((event) => event.type === "tool_start" && event.toolCallId === "call-0"),
).toMatchObject({ recovery: true, args: { value: "persisted" } });
expect(fixture.observations.indexOf("replay_commit")).toBeLessThan(fixture.observations.indexOf("tool_start"));
expect(fixture.events.find((event) => event.type === "tool_end" && event.toolCallId === "call-0")).toMatchObject({
recovery: true,
isError: false,
});
const unsafeEntry = await fixture.session.getEntry(fixture.resultEntryIds[1]!, BACKGROUND_CONTEXT);
expect(unsafeEntry?.type === "message" ? unsafeEntry.message : undefined).toMatchObject({
role: "toolResult",
isError: true,
details: { progress: "kept" },
});
expect(
unsafeEntry?.type === "message" && unsafeEntry.message.role === "toolResult"
? unsafeEntry.message.content.at(-1)
: undefined,
).toMatchObject({ type: "text", text: expect.stringContaining("external outcome is unknown") });
expect(fixture.events.some((event) => event.type === "tool_start" && event.toolCallId === "call-1")).toBe(false);
expect(fixture.events.find((event) => event.type === "tool_end" && event.toolCallId === "call-1")).toMatchObject({
recovery: true,
isError: true,
});
});
it("reconciles a restored cancelled batch without hooks, context, or effects", async () => {
const execute = vi.fn(async () => ({ content: [], details: {} }));
const context = vi.fn(() => undefined);
const fixture = await createFixture({
calls: [{ name: "planned" }, { name: "pending" }],
tools: [tool("planned", execute), tool("pending", execute, "safe")],
toolContext: context,
cancelled: true,
callStates: (ids) => [
{ status: "planned", sourceIndex: 0, resultEntryId: ids[0]! },
{ status: "effect_pending", sourceIndex: 1, resultEntryId: ids[1]!, replay: "safe" },
],
extraWrites: ({ operationId, resultEntryIds }) => [
storedValues.setValue(storedValues.operationToolArgs(operationId, "turn-1", 1), { value: "pending" }),
storedValues.setValue(storedValues.pendingToolOutput(operationId, resultEntryIds[1]!), {
content: [{ type: "text", text: "checkpoint" }],
details: {},
}),
],
});
const beforeTool = vi.fn(() => undefined);
const afterTool = vi.fn(() => undefined);
fixture.hooks.on("before_tool", beforeTool);
fixture.hooks.on("after_tool", afterTool);
await driveTools(fixture);
expect(execute).not.toHaveBeenCalled();
expect(context).not.toHaveBeenCalled();
expect(beforeTool).not.toHaveBeenCalled();
expect(afterTool).not.toHaveBeenCalled();
expect(currentRun(fixture)).toMatchObject({
at: "checkpoint",
control: { status: "cancel_requested" },
continuation: { kind: "need_assistant" },
});
const pending = await fixture.session.getEntry(fixture.resultEntryIds[1]!, BACKGROUND_CONTEXT);
expect(
pending?.type === "message" && pending.message.role === "toolResult"
? pending.message.content.at(-1)
: undefined,
).toMatchObject({ type: "text", text: expect.stringContaining("external outcome is unknown") });
const starts = fixture.events.filter((event) => event.type === "tool_start");
const ends = fixture.events.filter((event) => event.type === "tool_end");
expect(starts).toHaveLength(1);
expect(starts[0]).toMatchObject({ toolName: "planned", args: { value: "planned" } });
expect(ends).toHaveLength(2);
expect(ends.find((event) => event.toolCallId === starts[0]?.toolCallId)).toMatchObject({ isError: true });
});
it("materializes outcome-ready state without resolving tools or tool context", async () => {
const execute = vi.fn(async () => ({ content: [], details: {} }));
const context = vi.fn(() => undefined);
const staged: ToolResultMessage = {
role: "toolResult",
toolCallId: "call-0",
toolName: "ready",
content: [{ type: "text", text: "already done" }],
addedToolNames: ["later"],
isError: false,
timestamp: 30,
};
const fixture = await createFixture({
calls: [{ name: "ready" }],
tools: [tool("ready", execute)],
toolContext: context,
callStates: (ids) => [{ status: "outcome_ready", sourceIndex: 0, resultEntryId: ids[0]!, terminate: true }],
extraWrites: ({ resultEntryIds }) => [
storedValues.setValue(storedValues.pendingEntry(resultEntryIds[0]!), {
type: "message",
payload: staged,
}),
],
});
await driveTools(fixture);
expect(execute).not.toHaveBeenCalled();
expect(context).not.toHaveBeenCalled();
expect(currentRun(fixture)).toMatchObject({
at: "checkpoint",
continuation: { kind: "may_finish", includeFinalAssistant: false },
});
expect(fixture.lane.state.configuration.activeToolNames).toEqual(["ready", "later"]);
expect(fixture.events[0]).toMatchObject({ type: "turn_start", turnId: "turn-1", recovery: true });
expect(fixture.events.at(-1)).toMatchObject({ type: "turn_end", turnId: "turn-1", recovery: true });
});
it("never executes genuine-length or missing tool calls", async () => {
const execute = vi.fn(async () => ({ content: [], details: {} }));
const truncated = await createFixture({
calls: [{ name: "present" }],
tools: [tool("present", execute)],
stopReason: "length",
});
const truncatedAfterTool = vi.fn(() => undefined);
truncated.hooks.on("after_tool", truncatedAfterTool);
await driveTools(truncated);
expect(execute).not.toHaveBeenCalled();
const truncatedEntry = await truncated.session.getEntry(truncated.resultEntryIds[0]!, BACKGROUND_CONTEXT);
expect(
truncatedEntry?.type === "message" && truncatedEntry.message.role === "toolResult"
? truncatedEntry.message.content[0]
: undefined,
).toMatchObject({ type: "text", text: expect.stringContaining("arguments may be truncated") });
expect(truncatedAfterTool).not.toHaveBeenCalled();
expect(truncated.events.filter((event) => event.type === "tool_start")).toHaveLength(1);
expect(truncated.events.filter((event) => event.type === "tool_end")).toHaveLength(1);
expect(truncated.observations.indexOf("outcome_commit")).toBeLessThan(
truncated.observations.indexOf("tool_start"),
);
expect(truncated.observations.indexOf("tool_start")).toBeLessThan(truncated.observations.indexOf("tool_end"));
expect(truncated.observations.indexOf("tool_end")).toBeLessThan(truncated.observations.indexOf("entry_added"));
const missing = await createFixture({ calls: [{ name: "missing" }], tools: [] });
const missingAfterTool = vi.fn(() => undefined);
missing.hooks.on("after_tool", missingAfterTool);
await driveTools(missing);
const missingEntry = await missing.session.getEntry(missing.resultEntryIds[0]!, BACKGROUND_CONTEXT);
expect(missingEntry?.type === "message" ? missingEntry.message : undefined).toMatchObject({
role: "toolResult",
isError: true,
content: [{ type: "text", text: 'Tool "missing" is unavailable' }],
});
expect(
missingEntry?.type === "message" && missingEntry.message.role === "toolResult"
? Object.hasOwn(missingEntry.message, "details")
: true,
).toBe(false);
expect(missingAfterTool).not.toHaveBeenCalled();
expect(missing.events.filter((event) => event.type === "tool_start")).toHaveLength(1);
const missingEnds = missing.events.filter((event) => event.type === "tool_end");
expect(missingEnds).toHaveLength(1);
expect(missing.events.find((event) => event.type === "tool_start")).toMatchObject({
args: { value: "missing" },
});
expect(missingEnds[0]).toMatchObject({
result: { content: [{ type: "text", text: 'Tool "missing" is unavailable' }], details: undefined },
isError: true,
});
});
it("awaits update delivery and checkpoint persistence before after_tool", async () => {
const releaseUpdate = deferred<void>();
const updateQueued = deferred<void>();
let afterStarted = false;
const fixture = await createFixture({
calls: [{ name: "updating" }],
tools: [
tool("updating", async (_value, onUpdate) => {
onUpdate?.(
{ content: [{ type: "text", text: "partial" }], details: { progress: "partial" } },
{ checkpoint: true },
);
return { content: [{ type: "text", text: "done" }], details: {} };
}),
],
onEmit: async (events) => {
if (events.some(({ type }) => type === "tool_update")) {
updateQueued.resolve();
await releaseUpdate.promise;
}
},
});
fixture.hooks.on("after_tool", async () => {
afterStarted = true;
expect(
await fixture.session.getValue(
storedValues.pendingToolOutput(fixture.operationId, fixture.resultEntryIds[0]!),
BACKGROUND_CONTEXT,
),
).toBeDefined();
return undefined;
});
const running = driveTools(fixture);
await updateQueued.promise;
await new Promise((resolve) => setTimeout(resolve, 0));
expect(afterStarted).toBe(false);
releaseUpdate.resolve();
await running;
expect(afterStarted).toBe(true);
});
it("does not stage a cancelled outcome before cancellation is durable", async () => {
const execute = vi.fn(async () => ({ content: [], details: {} }));
const fixture = await createFixture({
calls: [{ name: "cancel-before-admission" }],
tools: [tool("cancel-before-admission", execute)],
mode: "sequential",
});
const cancellation = deferred<void>();
fixture.drive.beginAbort(cancellation.promise);
const running = driveTools(fixture);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(currentCalls(fixture)[0]?.status).toBe("planned");
expect(
await fixture.session.getValue(storedValues.pendingEntry(fixture.resultEntryIds[0]!), BACKGROUND_CONTEXT),
).toBeUndefined();
await fixture.lane.command((state) => {
const operation = state.operation;
if (operation === null) throw new Error("missing operation");
const nextRun: OperationState = {
...operation.state,
control: { status: "cancel_requested", requestedAt: 40 },
};
return {
kind: "commit",
writes: [storedValues.setValue(storedValues.operationState(fixture.operationId), nextRun)],
next: { ...state, operation: { meta: operation.meta, state: nextRun } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
cancellation.resolve();
fixture.drive.signalAbort();
await running;
expect(execute).not.toHaveBeenCalled();
expect(await fixture.session.getEntry(fixture.resultEntryIds[0]!, BACKGROUND_CONTEXT)).toBeDefined();
});
it("drains live updates before after_tool and stages a non-terminating result after cancellation", async () => {
const updateDelivery = deferred<void>();
const started = deferred<void>();
let updateDelivered = false;
const afterTool = vi.fn(() => {
expect(updateDelivered).toBe(true);
return undefined;
});
const executing = tool("slow", async (_value, onUpdate, _invocation, context) => {
onUpdate?.({ content: [{ type: "text", text: "partial" }], details: { progress: "partial" } });
started.resolve();
await new Promise<void>((_resolve, reject) => {
context.abortSignal?.addEventListener("abort", () => reject(new Error("cancelled effect")), { once: true });
});
return { content: [], details: {}, terminate: true };
});
const fixture = await createFixture({
calls: [{ name: "slow" }],
tools: [executing],
mode: "sequential",
onEmit: async (events) => {
if (events.some(({ type }) => type === "tool_update")) {
await updateDelivery.promise;
updateDelivered = true;
}
},
});
fixture.hooks.on("after_tool", afterTool);
const running = driveTools(fixture);
await started.promise;
const cancellation = deferred<void>();
fixture.drive.beginAbort(cancellation.promise);
await fixture.lane.command((state) => {
const operation = state.operation;
if (operation === null) throw new Error("missing operation");
const nextRun: OperationState = {
...operation.state,
control: { status: "cancel_requested", requestedAt: 40 },
};
return {
kind: "commit",
writes: [storedValues.setValue(storedValues.operationState(fixture.operationId), nextRun)],
next: { ...state, operation: { meta: operation.meta, state: nextRun } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
cancellation.resolve();
fixture.drive.signalAbort();
updateDelivery.resolve();
await running;
expect(afterTool).not.toHaveBeenCalled();
const entry = await fixture.session.getEntry(fixture.resultEntryIds[0]!, BACKGROUND_CONTEXT);
expect(entry?.type === "message" ? entry.terminate : undefined).toBeUndefined();
expect(entry?.type === "message" ? entry.message : undefined).toMatchObject({
role: "toolResult",
isError: true,
});
});
});

View File

@@ -0,0 +1,397 @@
import { createModels, fauxAssistantMessage, fauxProvider, type Provider } from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it, vi } from "vitest";
import { AgentHarness, HarnessFault, InvalidLane, UnknownTarget } from "../../../src/harness/agent-harness.ts";
import { BACKGROUND_CONTEXT } from "../../../src/harness/context.ts";
import { Harness } from "../../../src/harness/runtime/harness.ts";
import { Lane } from "../../../src/harness/runtime/lane.ts";
import { MemorySessionRepo, MemoryStorage } from "../../../src/harness/session/memory.ts";
import { StorageBackedSession } from "../../../src/harness/session/session.ts";
import type { LaneConfiguration, Session } from "../../../src/harness/session/types.ts";
import { branchTip, laneConfig, laneState, pendingEntry, setValue } from "../../../src/harness/session/values.ts";
import { deferred } from "./test-utils.ts";
const sessions: Session[] = [];
function harnessOptions(session: Session) {
const provider = fauxProvider();
const models = createModels();
models.setProvider(provider.provider);
return {
session,
models,
model: provider.getModel(),
thinkingLevel: "medium" as const,
activeToolNames: ["read", "bash"],
};
}
async function createSession(id = `session-${sessions.length}`): Promise<Session> {
const session = new StorageBackedSession({ id, createdAt: 1, storageVersion: 1 }, new MemoryStorage());
sessions.push(session);
return session;
}
async function createHarness(session?: Session): Promise<Harness<object | undefined>> {
session ??= await createSession();
const created = await AgentHarness.create(harnessOptions(session), BACKGROUND_CONTEXT);
if (!(created.harness instanceof Harness)) throw new Error("Expected runtime Harness");
return created.harness;
}
const configured = {
model: { provider: "faux", modelId: "faux-1" },
thinkingLevel: "low",
activeToolNames: ["read"],
} satisfies LaneConfiguration;
afterEach(async () => {
for (const session of sessions.splice(0)) await session.close(BACKGROUND_CONTEXT);
});
describe("runtime Harness lane management", () => {
it("attaches to a fresh Session without creating an implicit main lane", async () => {
const session = await createSession();
const harness = await createHarness(session);
expect(await harness.lanes(BACKGROUND_CONTEXT)).toEqual([]);
expect(await session.branch("main", BACKGROUND_CONTEXT)).toBeUndefined();
expect("accept" in harness).toBe(false);
expect("getTipId" in harness).toBe(false);
expect("appendMessage" in harness).toBe(false);
});
it("atomically gets or creates a complete AgentLane", async () => {
const session = await createSession();
const harness = await createHarness(session);
const events: string[] = [];
harness.events.on("lane_created", (event) => {
events.push(event.lane);
});
const lane = await harness.lane("main", BACKGROUND_CONTEXT);
const same = await harness.lane("main", { createAt: "ignored" }, BACKGROUND_CONTEXT);
expect(lane).toBeInstanceOf(Lane);
expect(same).toBe(lane);
expect(await lane.getTipId(BACKGROUND_CONTEXT)).toBeNull();
expect(await lane.getThinkingLevel(BACKGROUND_CONTEXT)).toBe("medium");
expect(await lane.getActiveTools(BACKGROUND_CONTEXT)).toEqual(["read", "bash"]);
expect((await session.getValue(laneState("main"), BACKGROUND_CONTEXT))?.value).toEqual({
currentOperationId: null,
lastOperationId: null,
inbox: [],
});
expect(events).toEqual(["main"]);
expect(await harness.lanes(BACKGROUND_CONTEXT)).toMatchObject([{ name: "main", tipId: null }]);
});
it("returns one published AgentLane under concurrent acquisition", async () => {
const session = await createSession();
const harness = await createHarness(session);
const listener = vi.fn();
harness.events.on("lane_created", listener);
const [first, second, third] = await Promise.all([
harness.lane("main", BACKGROUND_CONTEXT),
harness.lane("main", BACKGROUND_CONTEXT),
harness.lane("main", BACKGROUND_CONTEXT),
]);
expect(second).toBe(first);
expect(third).toBe(first);
expect(listener).toHaveBeenCalledTimes(1);
});
it("uses one stable provider session id per lane", async () => {
const session = await createSession("shared-session");
const faux = fauxProvider();
faux.setResponses([
fauxAssistantMessage("main one"),
fauxAssistantMessage("main two"),
fauxAssistantMessage("review"),
]);
const sessionIds: Array<string | undefined> = [];
const base = faux.provider;
const provider: Provider = {
id: base.id,
name: base.name,
auth: base.auth,
getModels: () => base.getModels(),
stream: (model, context, options) => base.stream(model, context, options),
streamSimple: (model, context, options) => {
sessionIds.push(options?.sessionId);
return base.streamSimple(model, context, options);
},
};
const models = createModels();
models.setProvider(provider);
const created = await AgentHarness.create(
{ session, models, model: faux.getModel(), activeToolNames: [] },
BACKGROUND_CONTEXT,
);
const main = await created.harness.lane("main", BACKGROUND_CONTEXT);
const review = await created.harness.lane("review", BACKGROUND_CONTEXT);
await main.prompt("one", undefined, BACKGROUND_CONTEXT);
await main.prompt("two", undefined, BACKGROUND_CONTEXT);
await review.prompt("review", undefined, BACKGROUND_CONTEXT);
expect(sessionIds).toEqual(["shared-session:main", "shared-session:main", "shared-session:review"]);
});
it("serializes commands from different AgentLanes on the one Session line", async () => {
const harness = await createHarness();
const main = await harness.lane("main", BACKGROUND_CONTEXT);
const review = await harness.lane("review", BACKGROUND_CONTEXT);
if (!(main instanceof Lane) || !(review instanceof Lane)) throw new Error("Expected runtime Lanes");
const started = deferred();
const gate = deferred();
const order: string[] = [];
const first = main.command(async () => {
order.push("main:start");
started.resolve();
await gate.promise;
order.push("main:end");
return { kind: "return", result: undefined };
}, BACKGROUND_CONTEXT);
await started.promise;
const second = review.command(() => {
order.push("review");
return { kind: "return", result: undefined };
}, BACKGROUND_CONTEXT);
await Promise.resolve();
expect(order).toEqual(["main:start"]);
gate.resolve();
await Promise.all([first, second]);
expect(order).toEqual(["main:start", "main:end", "review"]);
});
it("uses createAt only for a missing lane and validates the target", async () => {
const session = await createSession();
await session.mutate(
(mutator) =>
mutator.commit(
[
{
kind: "entry",
entry: {
id: "target",
parentId: null,
type: "custom",
customType: "target",
},
},
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const harness = await createHarness(session);
const lane = await harness.lane("review", { createAt: "target" }, BACKGROUND_CONTEXT);
expect(await lane.getTipId(BACKGROUND_CONTEXT)).toBe("target");
expect(await harness.lane("review", { createAt: "missing" }, BACKGROUND_CONTEXT)).toBe(lane);
await expect(harness.lane("missing", { createAt: "unknown" }, BACKGROUND_CONTEXT)).rejects.toBeInstanceOf(
UnknownTarget,
);
await expect(harness.lane("", BACKGROUND_CONTEXT)).rejects.toBeInstanceOf(InvalidLane);
await expect(harness.lane("bad\u0000name", BACKGROUND_CONTEXT)).rejects.toBeInstanceOf(InvalidLane);
});
it("attaches agent state to a data-only Branch without moving its tip", async () => {
const session = await createSession();
await session.mutate(
(mutator) =>
mutator.commit(
[
{
kind: "entry",
entry: {
id: "target",
parentId: null,
type: "custom",
customType: "target",
},
},
setValue(branchTip("main"), "target"),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const harness = await createHarness(session);
expect(await harness.lanes(BACKGROUND_CONTEXT)).toEqual([]);
const lane = await harness.lane("main", BACKGROUND_CONTEXT);
expect(await lane.getTipId(BACKGROUND_CONTEXT)).toBe("target");
expect((await session.getValue(laneConfig("main"), BACKGROUND_CONTEXT))?.value).toEqual({
model: { provider: "faux", modelId: "faux-1" },
thinkingLevel: "medium",
activeToolNames: ["read", "bash"],
});
});
it("keeps AgentLane appends operation-aware while exposing the Branch surface directly", async () => {
const session = await createSession();
const harness = await createHarness(session);
const lane = await harness.lane("main", BACKGROUND_CONTEXT);
const idleId = await lane.appendCustomEntry("idle", undefined, BACKGROUND_CONTEXT);
expect(await lane.getTipId(BACKGROUND_CONTEXT)).toBe(idleId);
const admission = await lane.accept({ kind: "prompt", prompt: "run" }, BACKGROUND_CONTEXT);
if (!admission.ok) throw admission.error;
const acceptedTip = await lane.getTipId(BACKGROUND_CONTEXT);
const pendingId = await lane.appendCustomEntry("pending", { queued: true }, BACKGROUND_CONTEXT);
expect(await lane.getTipId(BACKGROUND_CONTEXT)).toBe(acceptedTip);
expect((await session.getValue(branchTip("main"), BACKGROUND_CONTEXT))?.value).toBe(acceptedTip);
expect((await session.getValue(pendingEntry(pendingId), BACKGROUND_CONTEXT))?.value).toEqual({
type: "custom",
customType: "pending",
payload: { queued: true },
});
expect((await session.getValue(laneState("main"), BACKGROUND_CONTEXT))?.value.inbox).toEqual([
{ entryId: pendingId, kind: "write" },
]);
});
it("flushes queued writes before a new idle append in one commit", async () => {
const session = await createSession();
const harness = await createHarness(session);
const lane = await harness.lane("main", BACKGROUND_CONTEXT);
if (!(lane instanceof Lane)) throw new Error("Expected runtime Lane");
const first = session.idGenerator.next();
const second = session.idGenerator.next();
await lane.command((state) => {
const inbox = [
{ entryId: first, kind: "write" as const },
{ entryId: second, kind: "write" as const },
];
return {
kind: "commit",
writes: [
setValue(pendingEntry(first), { type: "custom", customType: "queued-first" }),
setValue(pendingEntry(second), { type: "custom", customType: "queued-second" }),
setValue(laneState("main"), { currentOperationId: null, lastOperationId: null, inbox }),
],
next: { ...state, inbox },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
const appended = await lane.appendCustomEntry("new", undefined, BACKGROUND_CONTEXT);
expect((await lane.findEntries({ order: "oldestFirst" }, BACKGROUND_CONTEXT)).map((entry) => entry.id)).toEqual([
first,
second,
appended,
]);
expect(lane.state.inbox).toEqual([]);
expect((await session.getValue(laneState("main"), BACKGROUND_CONTEXT))?.value.inbox).toEqual([]);
expect(await session.getValue(pendingEntry(first), BACKGROUND_CONTEXT)).toBeUndefined();
expect(await session.getValue(pendingEntry(second), BACKGROUND_CONTEXT)).toBeUndefined();
});
it("restores complete lanes without requiring main", async () => {
const session = await createSession();
await session.mutate(
(mutator) =>
mutator.commit(
[
setValue(branchTip("review"), null),
setValue(laneConfig("review"), configured),
setValue(laneState("review"), {
currentOperationId: null,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const { harness, open } = await AgentHarness.create(harnessOptions(session), BACKGROUND_CONTEXT);
expect(open).toEqual([]);
expect((await harness.lanes(BACKGROUND_CONTEXT)).map(({ name }) => name)).toEqual(["review"]);
expect(await harness.lane("review", BACKGROUND_CONTEXT)).toBeDefined();
expect(await session.branch("main", BACKGROUND_CONTEXT)).toBeUndefined();
});
it("rejects partial durable lane state as a Harness fault", async () => {
const session = await createSession();
await session.mutate(
(mutator) =>
mutator.commit(
[setValue(branchTip("main"), null), setValue(laneConfig("main"), configured)],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
await expect(AgentHarness.create(harnessOptions(session), BACKGROUND_CONTEXT)).rejects.toBeInstanceOf(
HarnessFault,
);
});
});
describe("runtime Harness global metadata", () => {
it("preserves value_update publication and delivery", async () => {
const session = await createSession();
const harness = await createHarness(session);
const seen: string[] = [];
harness.events.on("value_update", async (event) => {
seen.push(event.value === "session_name" ? `name:${await harness.getName(BACKGROUND_CONTEXT)}` : event.value);
});
await harness.setName("named", BACKGROUND_CONTEXT);
await harness.setLabel("entry", "label", BACKGROUND_CONTEXT);
expect(await harness.getName(BACKGROUND_CONTEXT)).toBe("named");
expect(await harness.getLabel("entry", BACKGROUND_CONTEXT)).toBe("label");
expect(seen).toEqual(["name:named", "entry_label"]);
});
it("publishes previous and current data-bearing global configuration", async () => {
const harness = await createHarness();
const events: unknown[] = [];
harness.events.on("config_update", (event) => {
events.push(event);
});
await harness.setStreamOptions({ timeoutMs: 123 }, BACKGROUND_CONTEXT);
await harness.setSteeringMode("one-at-a-time", BACKGROUND_CONTEXT);
expect(events).toEqual([
expect.objectContaining({
type: "config_update",
property: "streamOptions",
previous: {},
value: { timeoutMs: 123 },
}),
expect.objectContaining({
type: "config_update",
property: "steeringMode",
previous: "all",
value: "one-at-a-time",
}),
]);
});
it("closes every lane and rejects later acquisition", async () => {
const harness = await createHarness();
const lane = await harness.lane("main", BACKGROUND_CONTEXT);
await harness.close(BACKGROUND_CONTEXT);
await expect(harness.lane("other", BACKGROUND_CONTEXT)).rejects.toThrow("closed");
await expect(lane.getTipId(BACKGROUND_CONTEXT)).rejects.toThrow("closed");
});
it("MemorySessionRepo creation also remains branchless", async () => {
const repo = new MemorySessionRepo();
const session = await repo.create({ id: "repo-session" }, BACKGROUND_CONTEXT);
expect(await session.branch("main", BACKGROUND_CONTEXT)).toBeUndefined();
await session.close(BACKGROUND_CONTEXT);
await repo.close(BACKGROUND_CONTEXT);
});
});

View File

@@ -0,0 +1,391 @@
import { type Api, createModels, fauxProvider, type Model } from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it } from "vitest";
import { HarnessClosed, type HarnessEvent } from "../../../src/harness/agent-harness.ts";
import { DEFAULT_COMPACTION_SETTINGS } from "../../../src/harness/compaction/compaction.ts";
import { BACKGROUND_CONTEXT, type Context } from "../../../src/harness/context.ts";
import { HarnessEventBus } from "../../../src/harness/events.ts";
import { HookRegistry } from "../../../src/harness/hooks.ts";
import { convertToLlm } from "../../../src/harness/messages.ts";
import { Lane } from "../../../src/harness/runtime/lane.ts";
import { restoreLane } from "../../../src/harness/runtime/restore.ts";
import { StorageBackedSession } from "../../../src/harness/session/session.ts";
import type { LaneConfiguration, Session, StartingOperation } from "../../../src/harness/session/types.ts";
import * as storedValues from "../../../src/harness/session/values.ts";
import { ControlledMemoryStorage, deferred } from "./test-utils.ts";
const sessions: Session[] = [];
const configuration: LaneConfiguration = {
model: { provider: "test", modelId: "model" },
thinkingLevel: "off",
activeToolNames: [],
};
async function createLane(
emitBatch: (events: readonly HarnessEvent[], context: Context) => Promise<void> = () => Promise.resolve(),
): Promise<{
lane: Lane<undefined>;
model: Model<Api>;
session: Session;
storage: ControlledMemoryStorage;
}> {
const storage = new ControlledMemoryStorage();
const session = new StorageBackedSession(
{ id: `runtime-lane-${sessions.length}`, createdAt: 1, storageVersion: 1 },
storage,
);
sessions.push(session);
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.branchTip("main"), null),
storedValues.setValue(storedValues.laneConfig("main"), configuration),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const faux = fauxProvider();
const models = createModels();
models.setProvider(faux.provider);
return {
lane: new Lane<undefined>(
"main",
session,
models,
new HookRegistry(() => {}),
await restoreLane(session, "main", BACKGROUND_CONTEXT),
(cause) => (cause instanceof Error ? cause : new Error(String(cause))),
emitBatch,
(snapshot) => ({
snapshot,
start: () => {},
resnapshot: () => Promise.resolve(snapshot),
unsubscribe: () => {},
}),
() => ({
tools: [],
resources: {},
streamOptions: {},
retryPolicy: { enabled: true, maxRetries: 3, baseDelayMs: 1_000 },
compaction: DEFAULT_COMPACTION_SETTINGS,
steeringMode: "all",
followUpMode: "all",
toolExecution: "parallel",
toolContext: undefined,
systemPrompt: undefined,
toProviderMessages: (messages) => convertToLlm(messages),
entryProjectors: {},
}),
),
model: faux.getModel(),
session,
storage,
};
}
function setThinkingLevel(
lane: Lane<undefined>,
thinkingLevel: LaneConfiguration["thinkingLevel"],
observed?: LaneConfiguration["thinkingLevel"][],
): Promise<LaneConfiguration["thinkingLevel"]> {
return lane.command((state) => {
observed?.push(state.configuration.thinkingLevel);
const next = { ...state, configuration: { ...state.configuration, thinkingLevel } };
return {
kind: "commit",
writes: [storedValues.setValue(storedValues.laneConfig(lane.name), next.configuration)],
next,
materialize: () => thinkingLevel,
};
}, BACKGROUND_CONTEXT);
}
afterEach(async () => {
for (const session of sessions.splice(0)) await session.close(BACKGROUND_CONTEXT);
});
describe("runtime Lane commands", () => {
it("reads and replaces configuration from owned state", async () => {
const { lane, model, session } = await createLane();
const activeToolNames = ["read"];
await lane.setModel({ provider: model.provider, modelId: model.id }, BACKGROUND_CONTEXT);
await lane.setThinkingLevel("high", BACKGROUND_CONTEXT);
await lane.setActiveTools(activeToolNames, BACKGROUND_CONTEXT);
expect(await lane.getModel(BACKGROUND_CONTEXT)).toBe(model);
expect(await lane.getThinkingLevel(BACKGROUND_CONTEXT)).toBe("high");
expect(await lane.getActiveTools(BACKGROUND_CONTEXT)).toBe(activeToolNames);
expect((await session.getValue(storedValues.laneConfig("main"), BACKGROUND_CONTEXT))?.value).toEqual({
model: { provider: model.provider, modelId: model.id },
thinkingLevel: "high",
activeToolNames,
});
});
it("derives queued configuration updates from the latest committed state", async () => {
const { lane, model, storage } = await createLane();
const commitStarted = deferred();
const releaseCommit = deferred();
storage.beforeNextCommit = async () => {
commitStarted.resolve();
await releaseCommit.promise;
};
const modelUpdate = lane.setModel({ provider: model.provider, modelId: model.id }, BACKGROUND_CONTEXT);
await commitStarted.promise;
const thinkingUpdate = lane.setThinkingLevel("high", BACKGROUND_CONTEXT);
releaseCommit.resolve();
await Promise.all([modelUpdate, thinkingUpdate]);
expect(lane.state.configuration).toEqual({
model: { provider: model.provider, modelId: model.id },
thinkingLevel: "high",
activeToolNames: [],
});
});
it("returns a promise value without holding the lane line", async () => {
const { lane } = await createLane();
const completion = deferred();
let completed = false;
const joined = lane
.command(() => ({ kind: "return", result: completion.promise }), BACKGROUND_CONTEXT)
.then(() => {
completed = true;
});
await lane.setThinkingLevel("high", BACKGROUND_CONTEXT);
expect(completed).toBe(false);
completion.resolve();
await joined;
});
it("returns an expected rejection without faulting the lane", async () => {
const { lane } = await createLane();
const rejection = new Error("declined");
await expect(lane.command(() => ({ kind: "reject", error: rejection }), BACKGROUND_CONTEXT)).rejects.toBe(
rejection,
);
expect(await lane.getTipId(BACKGROUND_CONTEXT)).toBeNull();
await lane.setThinkingLevel("high", BACKGROUND_CONTEXT);
});
it("passes bounded reads and commit metadata through the serialized command", async () => {
const { lane } = await createLane();
let storedConfiguration: LaneConfiguration | undefined;
let memoryPublished = false;
const commit = await lane.command(async (state, reader) => {
storedConfiguration = (await reader.getValue(storedValues.laneConfig("main"), BACKGROUND_CONTEXT))?.value;
const configuration: LaneConfiguration = { ...state.configuration, thinkingLevel: "high" };
const next = { ...state, configuration };
return {
kind: "commit",
writes: [storedValues.setValue(storedValues.laneConfig("main"), configuration)],
next,
materialize: (commit) => {
memoryPublished = lane.state === next;
return commit;
},
};
}, BACKGROUND_CONTEXT);
expect(storedConfiguration).toEqual(configuration);
expect(memoryPublished).toBe(true);
expect(commit.seqs).toHaveLength(1);
expect(commit.timestamp).toEqual(expect.any(Number));
});
it("rejects thenable materialization after publishing committed memory", async () => {
const { lane, session } = await createLane();
await expect(
// @ts-expect-error Exercise the runtime guard against untyped callers.
lane.command((state) => {
const configuration: LaneConfiguration = { ...state.configuration, thinkingLevel: "high" };
return {
kind: "commit",
writes: [storedValues.setValue(storedValues.laneConfig("main"), configuration)],
next: { ...state, configuration },
materialize: async () => undefined,
};
}, BACKGROUND_CONTEXT),
).rejects.toThrow("Lane command materialize() must be synchronous");
expect(lane.state.configuration.thinkingLevel).toBe("high");
expect((await session.getValue(storedValues.laneConfig("main"), BACKGROUND_CONTEXT))?.value.thinkingLevel).toBe(
"high",
);
});
it("preserves committed memory when synchronous event publication fails", async () => {
const bus = new HarnessEventBus();
const { lane, session } = await createLane((events, context) => bus.emitBatch(events, context));
const uncloneable = {
type: "run_start",
runId: "run",
lane: "main",
invalid: () => undefined,
} as unknown as HarnessEvent;
await expect(
lane.command((state) => {
const configuration: LaneConfiguration = { ...state.configuration, thinkingLevel: "high" };
return {
kind: "commit",
writes: [storedValues.setValue(storedValues.laneConfig("main"), configuration)],
next: { ...state, configuration },
materialize: () => undefined,
events: () => [uncloneable],
};
}, BACKGROUND_CONTEXT),
).rejects.toMatchObject({ name: "DataCloneError" });
expect(lane.state.configuration.thinkingLevel).toBe("high");
expect((await session.getValue(storedValues.laneConfig("main"), BACKGROUND_CONTEXT))?.value.thinkingLevel).toBe(
"high",
);
});
it("rejects work after sealing while an admitted commit finishes", async () => {
const { lane, storage } = await createLane();
const commitStarted = deferred();
const releaseCommit = deferred();
storage.beforeNextCommit = async () => {
commitStarted.resolve();
await releaseCommit.promise;
};
const admitted = setThinkingLevel(lane, "high");
await commitStarted.promise;
const closed = new HarnessClosed();
lane.seal(closed);
await expect(lane.getTipId(BACKGROUND_CONTEXT)).rejects.toBe(closed);
await expect(lane.setThinkingLevel("low", BACKGROUND_CONTEXT)).rejects.toBe(closed);
releaseCommit.resolve();
await admitted;
expect(lane.state.configuration.thinkingLevel).toBe("high");
});
it("publishes memory only after the durable commit succeeds", async () => {
const { lane, session, storage } = await createLane();
const commitStarted = deferred();
const releaseCommit = deferred();
storage.beforeNextCommit = async () => {
commitStarted.resolve();
await releaseCommit.promise;
};
const command = setThinkingLevel(lane, "high");
await commitStarted.promise;
expect(lane.state.configuration.thinkingLevel).toBe("off");
releaseCommit.resolve();
expect(await command).toBe("high");
expect(lane.state.configuration.thinkingLevel).toBe("high");
expect((await session.getValue(storedValues.laneConfig("main"), BACKGROUND_CONTEXT))?.value.thinkingLevel).toBe(
"high",
);
});
it("preserves memory when the durable commit fails", async () => {
const { lane, session, storage } = await createLane();
const failure = new Error("commit failed");
storage.beforeNextCommit = async () => {
throw failure;
};
await expect(setThinkingLevel(lane, "high")).rejects.toBe(failure);
expect(lane.state.configuration.thinkingLevel).toBe("off");
expect((await session.getValue(storedValues.laneConfig("main"), BACKGROUND_CONTEXT))?.value.thinkingLevel).toBe(
"off",
);
});
it("diverts ordinary work but settles against latest cancelled control", async () => {
const { lane, session } = await createLane();
const accepted = await lane.accept({ kind: "prompt", prompt: "hello" }, BACKGROUND_CONTEXT);
expect(accepted.ok).toBe(true);
const operation = lane.state.operation;
if (operation?.state.at !== "starting") throw new Error("missing accepted operation");
const cancelled: StartingOperation = {
...operation.state,
control: { status: "cancel_requested", requestedAt: 1 },
};
await lane.command(
(state) => ({
kind: "commit",
writes: [storedValues.setValue(storedValues.operationState(operation.meta.operationId), cancelled)],
next: { ...state, operation: { meta: operation.meta, state: cancelled } },
materialize: () => undefined,
}),
BACKGROUND_CONTEXT,
);
let continued = false;
expect(
await lane.continueOperation(
cancelled,
() => {
continued = true;
return { kind: "return", result: "continued" };
},
BACKGROUND_CONTEXT,
),
).toEqual({ kind: "cancel_requested" });
expect(continued).toBe(false);
const settled = await lane.settleOperation(
cancelled,
(state, current) => {
expect(current.control.status).toBe("cancel_requested");
const inbox = [...state.inbox, { entryId: "accepted-during-cancellation", kind: "write" as const }];
return {
kind: "commit",
writes: [],
operationState: current,
lane: { inbox },
materialize: () => inbox,
};
},
BACKGROUND_CONTEXT,
);
expect(settled).toEqual([{ entryId: "accepted-during-cancellation", kind: "write" }]);
expect(lane.state.inbox).toEqual(settled);
expect((await session.getValue(storedValues.laneState("main"), BACKGROUND_CONTEXT))?.value.inbox).toEqual(
settled,
);
});
it("plans queued commands from the latest committed memory", async () => {
const { lane, storage } = await createLane();
const commitStarted = deferred();
const releaseCommit = deferred();
const observed: LaneConfiguration["thinkingLevel"][] = [];
storage.beforeNextCommit = async () => {
commitStarted.resolve();
await releaseCommit.promise;
};
const first = setThinkingLevel(lane, "high", observed);
await commitStarted.promise;
const second = setThinkingLevel(lane, "medium", observed);
await Promise.resolve();
expect(observed).toEqual(["off"]);
releaseCommit.resolve();
expect(await Promise.all([first, second])).toEqual(["high", "medium"]);
expect(observed).toEqual(["off", "high"]);
expect(lane.state.configuration.thinkingLevel).toBe("medium");
});
});

View File

@@ -0,0 +1,341 @@
import { type AssistantMessageFrame, createModels } from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { WatchHandle } from "../../../src/harness/agent-harness.ts";
import { DEFAULT_COMPACTION_SETTINGS } from "../../../src/harness/compaction/compaction.ts";
import { BACKGROUND_CONTEXT, type Context, withAbortSignal } from "../../../src/harness/context.ts";
import { HookRegistry } from "../../../src/harness/hooks.ts";
import { Lane } from "../../../src/harness/runtime/lane.ts";
import { openFrameProgress, openToolProgress } from "../../../src/harness/runtime/progress.ts";
import { type Config, Drive, type LaneState as RuntimeLaneState } from "../../../src/harness/runtime/types.ts";
import { MemoryStorage } from "../../../src/harness/session/memory.ts";
import { StorageBackedSession } from "../../../src/harness/session/session.ts";
import {
type AssistantEffectPendingOperation,
type CheckpointOperation,
type OperationState,
operationScopeOf,
type Session,
type Write,
} from "../../../src/harness/session/types.ts";
import * as storedValues from "../../../src/harness/session/values.ts";
import { deferred } from "./test-utils.ts";
const sessions: Session[] = [];
const operationId = "operation";
const configuration = {
model: { provider: "test", modelId: "model" },
thinkingLevel: "off" as const,
activeToolNames: [],
};
class FailingStorage extends MemoryStorage {
failure: Error | undefined;
beforeNextCommit: (() => Promise<void>) | undefined;
override async commit(writes: Write[], context: Context) {
const beforeCommit = this.beforeNextCommit;
this.beforeNextCommit = undefined;
await beforeCommit?.();
if (this.failure === undefined) return super.commit(writes, context);
const failure = this.failure;
this.failure = undefined;
throw failure;
}
}
const runtimeConfig: Config<undefined> = {
tools: [],
resources: {},
streamOptions: {},
retryPolicy: { enabled: true, maxRetries: 3, baseDelayMs: 1_000 },
compaction: DEFAULT_COMPACTION_SETTINGS,
steeringMode: "all",
followUpMode: "all",
toolExecution: "parallel",
toolContext: undefined,
systemPrompt: undefined,
toProviderMessages: () => [],
entryProjectors: {},
};
function unusedWatch<T>(): WatchHandle<T> {
throw new Error("watch is not used by progress tests");
}
function runScope() {
return {
control: { status: "running" as const },
settings: {
compaction: DEFAULT_COMPACTION_SETTINGS,
steeringMode: "all" as const,
followUpMode: "all" as const,
toolExecution: "parallel" as const,
},
inbox: { steer: [], followUp: [], writes: [] },
latestAssistantEntryId: null,
};
}
function assistantEffectPending(responseEntryId: string): AssistantEffectPendingOperation {
return {
...runScope(),
at: "assistant.effect_pending",
generationContext: {
stepId: "step",
triggerEntryId: "trigger",
configuration,
streamOptions: {},
retryPolicy: { maxAttempts: 2, baseDelayMs: 1 },
overflowRecoveryUsed: false,
},
attempt: 1,
responseEntryId,
usageId: "usage",
intendedOutputLimit: 100,
contextWindow: 1_000,
};
}
async function createFixture(
state: OperationState,
storage = new FailingStorage(),
): Promise<{ lane: Lane<undefined>; drive: Drive; storage: FailingStorage }> {
const session = new StorageBackedSession(
{ id: `progress-${sessions.length}`, createdAt: 1, storageVersion: 1 },
storage,
);
sessions.push(session);
const meta = {
operationId,
lane: "main",
sourceTipId: null,
startedAt: 1,
intent: { kind: "run" as const, promptEntryIds: [] },
};
const projection: RuntimeLaneState = {
tipId: null,
configuration,
inbox: [],
lastOperationId: null,
operation: { meta, state },
};
const lane = new Lane<undefined>(
"main",
session,
createModels(),
new HookRegistry(() => {}),
projection,
(cause) => (cause instanceof Error ? cause : new Error(String(cause))),
() => Promise.resolve(),
unusedWatch,
() => runtimeConfig,
);
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.branchTip("main"), null),
storedValues.setValue(storedValues.laneConfig("main"), configuration),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox: [],
}),
storedValues.setValue(storedValues.operationMeta(operationId), meta),
storedValues.setValue(storedValues.operationState(operationId), state),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const drive = new Drive({ operationId }, BACKGROUND_CONTEXT);
lane.activeDrive = drive;
return { lane, drive, storage };
}
afterEach(async () => {
for (const session of sessions.splice(0)) await session.close(BACKGROUND_CONTEXT);
});
describe("runtime active drive ownership", () => {
it("settles or rejects the shared completion without exposing owner controls through the gate", async () => {
const settled = new Drive({ operationId }, BACKGROUND_CONTEXT);
const outcome = { kind: "waiting", operationId, reason: "retry", notBefore: 10 } as const;
settled.settle(outcome);
settled.fail(new Error("late failure"));
await expect(settled.completion).resolves.toBe(outcome);
const failed = new Drive({ operationId }, BACKGROUND_CONTEXT);
const error = new Error("drive failed");
failed.fail(error);
await expect(failed.completion).rejects.toBe(error);
expect("beginAbort" in failed.gate).toBe(false);
});
it("strips invocation cancellation from pass context and owns policy and gate control", () => {
const controller = new AbortController();
const drive = new Drive(
{ operationId, waitForRetry: true, pollDeferred: true },
withAbortSignal(controller.signal, BACKGROUND_CONTEXT),
);
expect(drive.operationId).toBe(operationId);
expect(drive.context.abortSignal).toBeUndefined();
expect(drive.waitForRetry).toBe(true);
expect(drive.deferredPermits).toBe(1);
const closed = new Error("closed");
drive.closeGate(closed);
expect(drive.gate.signal.aborted).toBe(true);
expect(() => drive.gate.admit(() => undefined)).toThrow(closed);
});
});
describe("runtime progress channels", () => {
it("enqueues assistant frames in order, seals admission, and exposes the settlement delete", async () => {
const responseEntryId = "response";
const { lane, drive } = await createFixture(assistantEffectPending(responseEntryId));
const progress = openFrameProgress(lane, drive, responseEntryId);
const frames: AssistantMessageFrame[] = [
{ type: "text_delta", contentIndex: 0, delta: "a" },
{ type: "text_delta", contentIndex: 0, delta: "b" },
];
progress.write(frames[0]!);
progress.write(frames[1]!);
progress.seal();
progress.write({ type: "text_delta", contentIndex: 0, delta: "late" });
await progress.drain();
expect(
(
await lane.session.readList(
storedValues.pendingAssistantFrames(operationId, responseEntryId),
undefined,
BACKGROUND_CONTEXT,
)
).map(({ value }) => value),
).toEqual(frames);
});
it("declines a queued frame after the authoritative projection leaves its phase without control reads", async () => {
const responseEntryId = "response";
const { lane, drive, storage } = await createFixture(assistantEffectPending(responseEntryId));
const progress = openFrameProgress(lane, drive, responseEntryId);
const commitStarted = deferred();
const releaseCommit = deferred();
storage.beforeNextCommit = async () => {
commitStarted.resolve();
await releaseCommit.promise;
};
const getValue = vi.spyOn(storage, "getValue");
const moving = lane.command((state) => {
const operation = state.operation;
if (operation === null) throw new Error("missing operation");
const nextState: CheckpointOperation = {
...operationScopeOf(operation.state),
at: "checkpoint",
continuation: { kind: "may_finish", includeFinalAssistant: true },
triggerEntryId: responseEntryId,
};
return {
kind: "commit",
writes: [storedValues.setValue(storedValues.operationState(operationId), nextState)],
next: { ...state, operation: { meta: operation.meta, state: nextState } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
await commitStarted.promise;
progress.write({ type: "text_delta", contentIndex: 0, delta: "late" });
releaseCommit.resolve();
await moving;
await progress.drain();
expect(
await lane.session.readList(
storedValues.pendingAssistantFrames(operationId, responseEntryId),
undefined,
BACKGROUND_CONTEXT,
),
).toEqual([]);
expect(getValue).not.toHaveBeenCalled();
});
it("declines a queued frame after terminal projection publication", async () => {
const responseEntryId = "response";
const { lane, drive, storage } = await createFixture(assistantEffectPending(responseEntryId));
const progress = openFrameProgress(lane, drive, responseEntryId);
const commitStarted = deferred();
const releaseCommit = deferred();
storage.beforeNextCommit = async () => {
commitStarted.resolve();
await releaseCommit.promise;
};
const getValue = vi.spyOn(storage, "getValue");
const ending = lane.command(
(state) => ({
kind: "commit",
writes: [
storedValues.deleteValue(storedValues.operationMeta(operationId)),
storedValues.deleteValue(storedValues.operationState(operationId)),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: state.inbox,
}),
],
next: { ...state, operation: null },
materialize: () => undefined,
}),
BACKGROUND_CONTEXT,
);
await commitStarted.promise;
progress.write({ type: "text_delta", contentIndex: 0, delta: "late" });
releaseCommit.resolve();
await ending;
await progress.drain();
expect(
await lane.session.readList(
storedValues.pendingAssistantFrames(operationId, responseEntryId),
undefined,
BACKGROUND_CONTEXT,
),
).toEqual([]);
expect(getValue).not.toHaveBeenCalled();
});
it("replaces tool checkpoints in invocation order", async () => {
const invocationId = "result";
const { lane, drive } = await createFixture({
...runScope(),
at: "tools",
batch: {
assistantEntryId: "assistant",
configuration,
turnId: "turn",
calls: [{ status: "effect_pending", sourceIndex: 0, resultEntryId: invocationId, replay: "safe" }],
},
} satisfies OperationState);
const progress = openToolProgress(lane, drive, "turn", 0, invocationId);
progress.write({ content: [{ type: "text", text: "first" }], details: {} });
progress.write({ content: [{ type: "text", text: "second" }], details: {} });
await progress.drain();
expect(
(await lane.session.getValue(storedValues.pendingToolOutput(operationId, invocationId), BACKGROUND_CONTEXT))
?.value.content,
).toEqual([{ type: "text", text: "second" }]);
});
it("retains the rejecting write promise so drain propagates commit failure", async () => {
const responseEntryId = "response";
const { lane, drive, storage } = await createFixture(assistantEffectPending(responseEntryId));
const failure = new Error("frame commit failed");
storage.failure = failure;
const progress = openFrameProgress(lane, drive, responseEntryId);
progress.write({ type: "text_delta", contentIndex: 0, delta: "lost" });
await expect(progress.drain()).rejects.toBe(failure);
});
});

View File

@@ -0,0 +1,295 @@
import { createModels, fauxAssistantMessage, fauxProvider } from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it } from "vitest";
import { AgentHarness, type HarnessEvent, type LaneSnapshot } from "../../../src/harness/agent-harness.ts";
import { BACKGROUND_CONTEXT } from "../../../src/harness/context.ts";
import { reduceLaneSnapshot } from "../../../src/harness/runtime/reducer.ts";
import { MemoryStorage } from "../../../src/harness/session/memory.ts";
import { StorageBackedSession } from "../../../src/harness/session/session.ts";
import type { Session } from "../../../src/harness/session/types.ts";
const sessions: Session[] = [];
async function createFixture(options: { deferred?: boolean } = {}) {
const session = new StorageBackedSession(
{ id: `reducer-${sessions.length}`, createdAt: 1, storageVersion: 1 },
new MemoryStorage(),
);
sessions.push(session);
const faux = fauxProvider();
const models = createModels();
models.setProvider(faux.provider);
const { harness } = await AgentHarness.create(
{
session,
models,
model: faux.getModel(),
...(options.deferred === true ? { streamOptions: { deferred: true } } : {}),
},
BACKGROUND_CONTEXT,
);
const lane = await harness.lane("main", BACKGROUND_CONTEXT);
return { harness, lane, faux };
}
async function settleEvents(): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, 0));
}
function fold(snapshot: LaneSnapshot, events: HarnessEvent[]): LaneSnapshot {
for (const event of events) {
if (reduceLaneSnapshot(snapshot, event) === "rebase") throw new Error(`Unexpected rebase for ${event.type}`);
}
return snapshot;
}
afterEach(async () => {
for (const session of sessions.splice(0)) await session.close(BACKGROUND_CONTEXT);
});
describe("lane snapshot reducer", () => {
it("folds an ordinary run to the authoritative resnapshot", async () => {
const { lane, faux } = await createFixture();
const watch = await lane.watch(BACKGROUND_CONTEXT);
const events: HarnessEvent[] = [];
watch.start((event) => {
events.push(event);
});
faux.setResponses([fauxAssistantMessage("answer")]);
expect(await lane.prompt("question", undefined, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { kind: "run", status: "completed" },
});
await settleEvents();
expect(fold(watch.snapshot, events)).toEqual(await watch.resnapshot(BACKGROUND_CONTEXT));
watch.unsubscribe();
});
it("folds suspend and resume without closing the operation early", async () => {
const { lane, faux } = await createFixture({ deferred: true });
const watch = await lane.watch(BACKGROUND_CONTEXT);
const events: HarnessEvent[] = [];
watch.start((event) => {
events.push(event);
});
faux.setResponses([fauxAssistantMessage("answer")]);
expect(await lane.prompt("question", undefined, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { status: "suspended" },
});
await settleEvents();
let replica = fold(watch.snapshot, events);
expect(replica.operation).toMatchObject({ kind: "run", deferred: { poll: 0 } });
expect(replica).toEqual(await watch.resnapshot(BACKGROUND_CONTEXT));
events.length = 0;
expect(await lane.resume(BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { kind: "run", status: "completed" },
});
await settleEvents();
replica = fold(watch.snapshot, events);
expect(replica).toEqual(await watch.resnapshot(BACKGROUND_CONTEXT));
watch.unsubscribe();
});
it("folds standalone compaction and preserves segment semantics", async () => {
const { lane, faux } = await createFixture();
await lane.appendMessage({ role: "user", content: "history", timestamp: 1 }, BACKGROUND_CONTEXT);
const watch = await lane.watch(BACKGROUND_CONTEXT);
const events: HarnessEvent[] = [];
watch.start((event) => {
events.push(event);
});
faux.setResponses([fauxAssistantMessage("summary")]);
expect(await lane.compact(undefined, BACKGROUND_CONTEXT)).toMatchObject({
ok: true,
value: { compaction: { status: "completed" } },
});
await settleEvents();
const replica = fold(watch.snapshot, events);
expect(replica.operation).toBeNull();
expect(replica.lastResult).toMatchObject({ kind: "compaction", status: "completed" });
expect(replica).toEqual(await watch.resnapshot(BACKGROUND_CONTEXT));
watch.unsubscribe();
});
it("replicates globally ordered queue changes", async () => {
const { lane } = await createFixture();
const watch = await lane.watch(BACKGROUND_CONTEXT);
const events: HarnessEvent[] = [];
watch.start((event) => {
events.push(event);
});
await lane.nextRun("next", undefined, BACKGROUND_CONTEXT);
const steer = await lane.steer("steer", undefined, BACKGROUND_CONTEXT);
await lane.followUp("follow", undefined, BACKGROUND_CONTEXT);
if (!steer.ok) throw steer.error;
await lane.cancelQueued(steer.value.entryId, BACKGROUND_CONTEXT);
await settleEvents();
const replica = fold(watch.snapshot, events);
expect(replica.queues.map((item) => item.kind)).toEqual(["nextRun", "followUp"]);
expect(replica).toEqual(await watch.resnapshot(BACKGROUND_CONTEXT));
watch.unsubscribe();
});
it("keeps in-run compaction segments inside the open run", async () => {
const { lane } = await createFixture();
const snapshot = (await lane.watch(BACKGROUND_CONTEXT)).snapshot;
const running: LaneSnapshot = {
...snapshot,
operation: {
id: "run",
kind: "run",
startedAt: 1,
fromTipId: null,
status: "open",
runningTools: [],
},
};
expect(
reduceLaneSnapshot(running, {
type: "compaction_start",
lane: "main",
runId: "run",
reason: "threshold",
startedAt: 2,
}),
).toBeUndefined();
expect(
reduceLaneSnapshot(running, {
type: "compaction_end",
lane: "main",
runId: "run",
reason: "threshold",
status: "declined",
endedAt: 3,
}),
).toBeUndefined();
expect(running).toMatchObject({ operation: { id: "run", kind: "run" } });
});
it("retains settled parallel tools until each source-ordered result is placed", async () => {
const { lane } = await createFixture();
const snapshot = (await lane.watch(BACKGROUND_CONTEXT)).snapshot;
snapshot.operation = {
id: "run",
kind: "run",
startedAt: 1,
fromTipId: null,
status: "open",
runningTools: [],
};
const result = (text: string) => ({ content: [{ type: "text" as const, text }], details: { text } });
const start = (index: number): HarnessEvent => ({
type: "tool_start",
lane: "main",
runId: "run",
turnId: "turn",
toolCallId: `call-${index}`,
toolName: `tool-${index}`,
args: { index },
});
const end = (index: number): HarnessEvent => ({
type: "tool_end",
lane: "main",
runId: "run",
turnId: "turn",
toolCallId: `call-${index}`,
toolName: `tool-${index}`,
result: result(`done-${index}`),
isError: false,
terminate: false,
});
const entry = (index: number): HarnessEvent => ({
type: "entry_added",
lane: "main",
entry: {
id: `result-${index}`,
parentId: index === 0 ? null : `result-${index - 1}`,
seq: index + 1,
timestamp: index + 1,
type: "message",
message: {
role: "toolResult",
toolCallId: `call-${index}`,
toolName: `tool-${index}`,
content: [{ type: "text", text: `done-${index}` }],
isError: false,
timestamp: index + 1,
},
},
});
for (let index = 0; index < 3; index += 1) reduceLaneSnapshot(snapshot, start(index));
expect(snapshot.operation.runningTools).toHaveLength(3);
reduceLaneSnapshot(snapshot, start(0));
expect(snapshot.operation.runningTools).toHaveLength(3);
reduceLaneSnapshot(snapshot, {
type: "tool_update",
lane: "main",
runId: "stale",
turnId: "turn",
toolCallId: "call-1",
toolName: "tool-1",
partialResult: result("stale"),
});
expect(snapshot.operation.runningTools[1]).not.toHaveProperty("result");
reduceLaneSnapshot(snapshot, end(2));
expect(snapshot.operation.runningTools.find(({ toolCallId }) => toolCallId === "call-2")).toMatchObject({
status: "settled",
args: { index: 2 },
result: result("done-2"),
});
reduceLaneSnapshot(snapshot, end(0));
expect(snapshot.operation.runningTools.map(({ status }) => status)).toEqual(["settled", "running", "settled"]);
reduceLaneSnapshot(snapshot, entry(0));
expect(snapshot.operation.runningTools.map(({ toolCallId }) => toolCallId)).toEqual(["call-1", "call-2"]);
expect(snapshot.transcript.map(({ id }) => id)).toEqual(["result-0"]);
reduceLaneSnapshot(snapshot, end(1));
expect(snapshot.operation.runningTools.every(({ status }) => status === "settled")).toBe(true);
reduceLaneSnapshot(snapshot, entry(1));
expect(snapshot.operation.runningTools.map(({ toolCallId }) => toolCallId)).toEqual(["call-2"]);
reduceLaneSnapshot(snapshot, entry(2));
expect(snapshot.operation.runningTools).toEqual([]);
expect(snapshot.transcript.map(({ id }) => id)).toEqual(["result-0", "result-1", "result-2"]);
});
it("marks navigation completion for rebase", async () => {
const { lane } = await createFixture();
const snapshot = (await lane.watch(BACKGROUND_CONTEXT)).snapshot;
const reduced = reduceLaneSnapshot(
{
...snapshot,
operation: {
id: "navigation",
kind: "navigation",
startedAt: 1,
fromTipId: null,
status: "open",
runningTools: [],
},
},
{
type: "navigation_end",
lane: "main",
runId: "navigation",
status: "completed",
fromTipId: null,
tipId: "target",
endedAt: 2,
},
);
expect(reduced).toBe("rebase");
});
});

View File

@@ -0,0 +1,441 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_COMPACTION_SETTINGS } from "../../../src/harness/compaction/compaction.ts";
import { BACKGROUND_CONTEXT } from "../../../src/harness/context.ts";
import { restoreLane, restoreSession } from "../../../src/harness/runtime/restore.ts";
import { MemorySessionRepo, MemoryStorage } from "../../../src/harness/session/memory.ts";
import type {
CheckpointOperation,
LaneConfiguration,
OperationMeta,
OperationScope,
OperationState,
ResultBoundary,
Session,
} from "../../../src/harness/session/types.ts";
import * as storedValues from "../../../src/harness/session/values.ts";
const repos: MemorySessionRepo[] = [];
const configuration: LaneConfiguration = {
model: { provider: "test", modelId: "model" },
thinkingLevel: "off",
activeToolNames: [],
};
async function createSession(): Promise<Session> {
const repo = new MemorySessionRepo();
repos.push(repo);
const session = await repo.create({}, BACKGROUND_CONTEXT);
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.branchTip("main"), null),
storedValues.setValue(storedValues.laneConfig("main"), configuration),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
return session;
}
function operationScope(): OperationScope {
return {
control: { status: "running" },
settings: {
compaction: DEFAULT_COMPACTION_SETTINGS,
steeringMode: "all",
followUpMode: "all",
toolExecution: "parallel",
},
latestAssistantEntryId: null,
};
}
function runState(triggerEntryId: string): CheckpointOperation {
return {
...operationScope(),
at: "checkpoint",
continuation: { kind: "need_assistant", overflowRecoveryUsed: false },
triggerEntryId,
};
}
function summaryState(boundary: ResultBoundary): OperationState {
return {
...operationScope(),
at: "summary.deciding",
task: {
taskId: "task",
...(boundary.kind === "finish"
? { reason: "manual" as const }
: boundary.kind === "resume_checkpoint"
? { reason: "threshold" as const }
: {}),
boundary,
},
};
}
afterEach(async () => {
for (const repo of repos.splice(0)) await repo.close(BACKGROUND_CONTEXT);
});
describe("runtime lane restore", () => {
it("restores an idle lane's latest operation id without reading its result", async () => {
const session = await createSession();
const result = {
operationId: session.idGenerator.next(),
kind: "navigation" as const,
status: "completed" as const,
fromTipId: null,
tipId: null,
startedAt: 1,
endedAt: 2,
};
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.operationResult(result.operationId), result),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: result.operationId,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const state = await restoreLane(session, "main", BACKGROUND_CONTEXT);
expect(state).toEqual({
tipId: null,
configuration,
inbox: [],
lastOperationId: result.operationId,
operation: null,
});
});
it("restores an open operation without interpreting its referenced payloads", async () => {
const session = await createSession();
const operationId = session.idGenerator.next();
const missingTriggerId = session.idGenerator.next();
const meta: OperationMeta = {
operationId,
lane: "main",
sourceTipId: null,
startedAt: 1,
intent: { kind: "run", promptEntryIds: [] },
};
const state = runState(missingTriggerId);
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.operationMeta(operationId), meta),
storedValues.setValue(storedValues.operationState(operationId), state),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const restored = await restoreLane(session, "main", BACKGROUND_CONTEXT);
expect(restored.operation).toEqual({ meta, state });
});
it("validates current-operation identity, lane ownership, and intent/state compatibility", async () => {
for (const corruption of ["identity", "lane", "kind"] as const) {
const session = await createSession();
const operationId = session.idGenerator.next();
const base: OperationMeta = {
operationId,
lane: "main",
sourceTipId: null,
startedAt: 1,
intent: { kind: "run", promptEntryIds: [] },
};
const meta: OperationMeta =
corruption === "identity"
? { ...base, operationId: "different-operation" }
: corruption === "lane"
? { ...base, lane: "worker" }
: { ...base, intent: { kind: "navigation", targetId: null, summarize: false } };
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.operationMeta(operationId), meta),
storedValues.setValue(storedValues.operationState(operationId), runState("trigger")),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
await expect(restoreLane(session, "main", BACKGROUND_CONTEXT)).rejects.toBeInstanceOf(Error);
}
});
it("accepts exactly the family-neutral state reachability matrix", async () => {
const resume = { kind: "resume_checkpoint", resumeAfter: runState("trigger") } satisfies ResultBoundary;
const finish = { kind: "finish" } satisfies ResultBoundary;
const navigation = { kind: "commit_navigation", targetId: "target" } satisfies ResultBoundary;
const cases: { intent: OperationMeta["intent"]; state: OperationState; accepted: boolean }[] = [
{ intent: { kind: "run", promptEntryIds: [] }, state: runState("trigger"), accepted: true },
{ intent: { kind: "run", promptEntryIds: [] }, state: summaryState(resume), accepted: true },
{ intent: { kind: "run", promptEntryIds: [] }, state: summaryState(finish), accepted: false },
{ intent: { kind: "run", promptEntryIds: [] }, state: summaryState(navigation), accepted: false },
{ intent: { kind: "compaction" }, state: summaryState(finish), accepted: true },
{ intent: { kind: "compaction" }, state: summaryState(resume), accepted: false },
{ intent: { kind: "compaction" }, state: summaryState(navigation), accepted: false },
{ intent: { kind: "compaction" }, state: runState("trigger"), accepted: false },
{
intent: { kind: "navigation", targetId: null, summarize: false },
state: { ...operationScope(), at: "navigation.ready_to_commit", targetId: null },
accepted: true,
},
{
intent: { kind: "navigation", targetId: "target", summarize: false },
state: { ...operationScope(), at: "navigation.ready_to_commit", targetId: "different" },
accepted: false,
},
{
intent: { kind: "navigation", targetId: "target", summarize: false },
state: summaryState(navigation),
accepted: false,
},
{
intent: { kind: "navigation", targetId: "target", summarize: true },
state: summaryState(navigation),
accepted: true,
},
{
intent: { kind: "navigation", targetId: "different", summarize: true },
state: summaryState(navigation),
accepted: false,
},
{
intent: { kind: "navigation", targetId: "target", summarize: true },
state: { ...operationScope(), at: "navigation.ready_to_commit", targetId: "target" },
accepted: false,
},
{
intent: { kind: "navigation", targetId: "target", summarize: true },
state: summaryState(finish),
accepted: false,
},
{
intent: { kind: "navigation", targetId: "target", summarize: true },
state: summaryState(resume),
accepted: false,
},
{
intent: { kind: "navigation", targetId: "target", summarize: false },
state: runState("trigger"),
accepted: false,
},
];
for (const testCase of cases) {
const session = await createSession();
const operationId = session.idGenerator.next();
const meta: OperationMeta = {
operationId,
lane: "main",
sourceTipId: null,
startedAt: 1,
intent: testCase.intent,
};
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.operationMeta(operationId), meta),
storedValues.setValue(storedValues.operationState(operationId), testCase.state),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const restored = restoreLane(session, "main", BACKGROUND_CONTEXT);
if (testCase.accepted)
await expect(restored).resolves.toMatchObject({ operation: { meta, state: testCase.state } });
else await expect(restored).rejects.toThrow("does not match state");
}
});
it.each([
[storedValues.branchTip("main").namespace, storedValues.deleteValue(storedValues.branchTip("main"))],
[storedValues.laneConfig("main").namespace, storedValues.deleteValue(storedValues.laneConfig("main"))],
[storedValues.laneState("main").namespace, storedValues.deleteValue(storedValues.laneState("main"))],
] as const)("requires %s", async (namespace, write) => {
const session = await createSession();
await session.mutate((mutator) => mutator.commit([write], BACKGROUND_CONTEXT), BACKGROUND_CONTEXT);
await expect(restoreLane(session, "main", BACKGROUND_CONTEXT)).rejects.toThrow(`missing ${namespace.slice(3)}`);
});
it.each([storedValues.operationMeta("").namespace, storedValues.operationState("").namespace] as const)(
"requires %s for the current operation",
async (namespace) => {
const session = await createSession();
const operationId = session.idGenerator.next();
const meta: OperationMeta = {
operationId,
lane: "main",
sourceTipId: null,
startedAt: 1,
intent: { kind: "run", promptEntryIds: [] },
};
const state = runState(session.idGenerator.next());
await session.mutate(
(mutator) =>
mutator.commit(
[
...(namespace === storedValues.operationMeta(operationId).namespace
? []
: [storedValues.setValue(storedValues.operationMeta(operationId), meta)]),
...(namespace === storedValues.operationState(operationId).namespace
? []
: [storedValues.setValue(storedValues.operationState(operationId), state)]),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
await expect(restoreLane(session, "main", BACKGROUND_CONTEXT)).rejects.toThrow(
`missing ${namespace.slice(3)}`,
);
},
);
it("restores every configured lane exactly once without writing", async () => {
const session = await createSession();
const workerConfiguration: LaneConfiguration = {
model: { provider: "test", modelId: "worker" },
thinkingLevel: "high",
activeToolNames: ["read"],
};
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.branchTip("worker"), null),
storedValues.setValue(storedValues.laneConfig("worker"), workerConfiguration),
storedValues.setValue(storedValues.laneState("worker"), {
currentOperationId: null,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const operationId = session.idGenerator.next();
const meta: OperationMeta = {
operationId,
lane: "worker",
sourceTipId: null,
startedAt: 1,
intent: { kind: "run", promptEntryIds: [] },
};
const state = runState(session.idGenerator.next());
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.operationMeta(operationId), meta),
storedValues.setValue(storedValues.operationState(operationId), state),
storedValues.setValue(storedValues.laneState("worker"), {
currentOperationId: operationId,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
const before = {
leaves: await session.scanValues(storedValues.branchTipInventoryPrefix(), BACKGROUND_CONTEXT),
mainConfiguration: await session.getValue(storedValues.laneConfig("main"), BACKGROUND_CONTEXT),
workerConfiguration: await session.getValue(storedValues.laneConfig("worker"), BACKGROUND_CONTEXT),
mainState: await session.getValue(storedValues.laneState("main"), BACKGROUND_CONTEXT),
workerState: await session.getValue(storedValues.laneState("worker"), BACKGROUND_CONTEXT),
};
const mutate = vi.spyOn(session, "mutate");
const getValue = vi.spyOn(MemoryStorage.prototype, "getValue");
const readList = vi.spyOn(MemoryStorage.prototype, "readList");
const lanes = await restoreSession(session, BACKGROUND_CONTEXT);
expect([...lanes.keys()].sort()).toEqual(["main", "worker"]);
expect(lanes.get("main")?.configuration).toEqual(configuration);
expect(lanes.get("worker")).toMatchObject({
configuration: workerConfiguration,
operation: { meta, state },
});
expect(mutate).toHaveBeenCalledTimes(1);
expect(getValue.mock.calls.map(([address]) => ({ namespace: address.namespace, key: address.key }))).toEqual([
{ namespace: storedValues.operationMeta("").namespace, key: operationId },
{ namespace: storedValues.operationState("").namespace, key: operationId },
]);
expect(readList).not.toHaveBeenCalled();
getValue.mockRestore();
expect({
leaves: await session.scanValues(storedValues.branchTipInventoryPrefix(), BACKGROUND_CONTEXT),
mainConfiguration: await session.getValue(storedValues.laneConfig("main"), BACKGROUND_CONTEXT),
workerConfiguration: await session.getValue(storedValues.laneConfig("worker"), BACKGROUND_CONTEXT),
mainState: await session.getValue(storedValues.laneState("main"), BACKGROUND_CONTEXT),
workerState: await session.getValue(storedValues.laneState("worker"), BACKGROUND_CONTEXT),
}).toEqual(before);
readList.mockRestore();
});
it("allows an empty inventory but rejects lane values without a Branch", async () => {
const repo = new MemorySessionRepo();
repos.push(repo);
const empty = await repo.create({}, BACKGROUND_CONTEXT);
await expect(restoreSession(empty, BACKGROUND_CONTEXT)).resolves.toEqual(new Map());
const session = await createSession();
await session.mutate(
(mutator) => mutator.commit([storedValues.deleteValue(storedValues.branchTip("main"))], BACKGROUND_CONTEXT),
BACKGROUND_CONTEXT,
);
await expect(restoreSession(session, BACKGROUND_CONTEXT)).rejects.toThrow('Lane "main" is missing branch.tip');
});
});

View File

@@ -0,0 +1,32 @@
import { BACKGROUND_CONTEXT } from "../../../src/harness/context.ts";
import { MemoryStorage } from "../../../src/harness/session/memory.ts";
import type { CommitResult, Write } from "../../../src/harness/session/types.ts";
export class FailingMemoryStorage extends MemoryStorage {
failure: Error | undefined;
override commit(writes: Write[]) {
const failure = this.failure;
this.failure = undefined;
return failure === undefined ? super.commit(writes, BACKGROUND_CONTEXT) : Promise.reject(failure);
}
}
export class ControlledMemoryStorage extends MemoryStorage {
beforeNextCommit: (() => Promise<void>) | undefined;
override async commit(writes: Write[]): Promise<CommitResult> {
const beforeCommit = this.beforeNextCommit;
this.beforeNextCommit = undefined;
await beforeCommit?.();
return super.commit(writes, BACKGROUND_CONTEXT);
}
}
export function deferred(): { promise: Promise<void>; resolve(): void } {
let resolvePromise: (() => void) | undefined;
const promise = new Promise<void>((resolve) => {
resolvePromise = resolve;
});
return { promise, resolve: () => resolvePromise?.() };
}

View File

@@ -0,0 +1,595 @@
import { type AssistantMessageFrame, createModels, fauxAssistantMessage, fauxProvider } from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it, vi } from "vitest";
import { type HarnessEvent, HarnessFault, type WatchHandle } from "../../../src/harness/agent-harness.ts";
import { DEFAULT_COMPACTION_SETTINGS } from "../../../src/harness/compaction/compaction.ts";
import { BACKGROUND_CONTEXT, type Context, createContextKey, withContextValue } from "../../../src/harness/context.ts";
import { createAgentHarness, Harness } from "../../../src/harness/runtime/harness.ts";
import { Lane } from "../../../src/harness/runtime/lane.ts";
import * as sessionWrites from "../../../src/harness/session/commit.ts";
import { MemoryStorage } from "../../../src/harness/session/memory.ts";
import { StorageBackedSession } from "../../../src/harness/session/session.ts";
import type {
OperationMeta,
OperationScope,
OperationState,
Session,
StorageBranchScan,
Write,
} from "../../../src/harness/session/types.ts";
import * as storedValues from "../../../src/harness/session/values.ts";
import { deferred } from "./test-utils.ts";
const sessions: Session[] = [];
const configuration = {
model: { provider: "configured", modelId: "model" },
thinkingLevel: "off" as const,
activeToolNames: [],
};
async function createSession(storage: MemoryStorage = new MemoryStorage()): Promise<Session> {
const session = new StorageBackedSession(
{ id: `watch-${sessions.length}`, createdAt: 1, storageVersion: 1 },
storage,
);
sessions.push(session);
await session.mutate(
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.branchTip("main"), null),
storedValues.setValue(storedValues.laneConfig("main"), configuration),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [],
}),
],
BACKGROUND_CONTEXT,
),
BACKGROUND_CONTEXT,
);
return session;
}
async function commit(session: Session, writes: Write[]): Promise<void> {
await session.mutate((mutator) => mutator.commit(writes, BACKGROUND_CONTEXT), BACKGROUND_CONTEXT);
}
function runScope(control: OperationScope["control"] = { status: "running" }): OperationScope {
return {
control,
settings: {
compaction: DEFAULT_COMPACTION_SETTINGS,
steeringMode: "all",
followUpMode: "all",
toolExecution: "parallel",
},
latestAssistantEntryId: null,
};
}
async function attach(
session: Session,
): Promise<Lane<object | undefined> & Pick<Harness<object | undefined>, "events">> {
const provider = fauxProvider();
const models = createModels();
models.setProvider(provider.provider);
const { harness } = await createAgentHarness({ session, models, model: provider.getModel() }, BACKGROUND_CONTEXT);
if (!(harness instanceof Harness)) throw new Error("Expected runtime Harness");
const lane = await harness.lane("main", BACKGROUND_CONTEXT);
if (!(lane instanceof Lane)) throw new Error("Expected runtime Lane");
return Object.assign(lane, { events: harness.events });
}
class BlockingScanStorage extends MemoryStorage {
block = false;
readonly started = deferred();
readonly release = deferred();
override async scanBranch(query: StorageBranchScan, context: Context) {
if (this.block) {
this.started.resolve();
await this.release.promise;
}
return super.scanBranch(query, context);
}
}
afterEach(async () => {
for (const session of sessions.splice(0)) await session.close(BACKGROUND_CONTEXT);
});
describe("runtime lane watch", () => {
it("captures a compaction-bounded transcript and isolates the returned snapshot", async () => {
const session = await createSession();
await commit(session, [
sessionWrites.insertEntry({ id: "root", parentId: null, type: "custom", customType: "root" }),
sessionWrites.insertEntry({
id: "compact",
parentId: "root",
type: "compaction",
summary: "summary",
retainedTail: [],
tokensBefore: 10,
fromHook: false,
}),
sessionWrites.insertEntry({
id: "after",
parentId: "compact",
type: "message",
message: { role: "user", content: "after", timestamp: 2 },
}),
storedValues.setValue(storedValues.branchTip("main"), "after"),
]);
const harness = await attach(session);
const first = await harness.watch(BACKGROUND_CONTEXT);
expect(first.snapshot.transcript.map(({ id }) => id)).toEqual(["compact", "after"]);
expect(first.snapshot).toMatchObject({
lane: "main",
tipId: "after",
configuration,
stats: { messageCount: 1 },
operation: null,
queues: [],
faulted: false,
});
first.snapshot.transcript.length = 0;
first.snapshot.tipId = null;
const second = await harness.watch(BACKGROUND_CONTEXT);
expect(second.snapshot.transcript.map(({ id }) => id)).toEqual(["compact", "after"]);
expect(second.snapshot.tipId).toBe("after");
first.unsubscribe();
second.unsubscribe();
});
it("dereferences queues, pending writes, and deferred handles", async () => {
const session = await createSession();
const handle = { provider: "provider", modelId: "model", api: "test", id: "deferred" };
const sourceMessage = fauxAssistantMessage([], { stopReason: "deferred", deferred: handle });
const ids = {
next: "next",
steer: "steer",
follow: "follow",
write: "write",
};
const payload = (content: string) => ({
type: "message" as const,
payload: { role: "user" as const, content, timestamp: 1 },
});
const operationId = session.idGenerator.next();
const state: OperationState = {
...runScope({ status: "cancel_requested", requestedAt: 2 }),
at: "deferred.suspended",
stepId: "step",
sourceEntryId: "source",
poll: 3,
configuration,
streamOptions: {},
};
const meta: OperationMeta = {
operationId,
lane: "main",
sourceTipId: null,
startedAt: 1,
intent: { kind: "run", promptEntryIds: [] },
};
await commit(session, [
sessionWrites.insertEntry({ id: "source", parentId: null, type: "message", message: sourceMessage }),
...Object.values(ids).map((id) =>
storedValues.setValue(
storedValues.pendingEntry(id),
id === ids.write ? { type: "custom", customType: "note", payload: { id } } : payload(id),
),
),
storedValues.setValue(storedValues.operationMeta(operationId), meta),
storedValues.setValue(storedValues.operationState(operationId), state),
storedValues.setValue(storedValues.branchTip("main"), "source"),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox: [
{ entryId: ids.next, kind: "nextRun" },
{ entryId: ids.steer, kind: "steer" },
{ entryId: ids.follow, kind: "followUp" },
{ entryId: ids.write, kind: "write" },
],
}),
]);
const harness = await attach(session);
const watch = await harness.watch(BACKGROUND_CONTEXT);
expect(watch.snapshot.queues).toMatchObject([
{ entryId: ids.next, kind: "nextRun", type: "message", message: { content: ids.next } },
{ entryId: ids.steer, kind: "steer", type: "message", message: { content: ids.steer } },
{ entryId: ids.follow, kind: "followUp", type: "message", message: { content: ids.follow } },
{ entryId: ids.write, kind: "write", type: "custom", customType: "note", data: { id: ids.write } },
]);
expect(watch.snapshot.operation).toMatchObject({
id: operationId,
status: "aborting",
deferred: { handle, poll: 3 },
runningTools: [],
});
watch.unsubscribe();
});
it("omits streaming presentation when an effect-pending response has no frames", async () => {
const session = await createSession();
const operationId = session.idGenerator.next();
await commit(session, [
storedValues.setValue(storedValues.operationMeta(operationId), {
operationId,
lane: "main",
sourceTipId: null,
startedAt: 1,
intent: { kind: "run", promptEntryIds: [] },
}),
storedValues.setValue(storedValues.operationState(operationId), {
...runScope(),
at: "assistant.effect_pending",
generationContext: {
stepId: "step",
triggerEntryId: "trigger",
configuration,
streamOptions: {},
retryPolicy: { maxAttempts: 2, baseDelayMs: 0 },
overflowRecoveryUsed: false,
},
attempt: 1,
responseEntryId: "response-without-frames",
usageId: "usage",
intendedOutputLimit: 100,
contextWindow: 1000,
}),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox: [],
}),
]);
const harness = await attach(session);
const watch = await harness.watch(BACKGROUND_CONTEXT);
expect(watch.snapshot.operation).not.toHaveProperty("streamingMessage");
watch.unsubscribe();
});
it("reduces assistant frames and projects running and settled tools with full-content indexes", async () => {
const frameSession = await createSession();
const partial = fauxAssistantMessage([], { stopReason: "pending" });
const frames: AssistantMessageFrame[] = [
{ type: "start", partial },
{ type: "text_start", contentIndex: 0, content: { type: "text", text: "" } },
{ type: "text_delta", contentIndex: 0, delta: "partial" },
];
const frameOperationId = frameSession.idGenerator.next();
await commit(frameSession, [
storedValues.setValue(storedValues.operationMeta(frameOperationId), {
operationId: frameOperationId,
lane: "main",
sourceTipId: null,
startedAt: 1,
intent: { kind: "run", promptEntryIds: [] },
}),
storedValues.setValue(storedValues.operationState(frameOperationId), {
...runScope(),
at: "assistant.effect_pending",
generationContext: {
stepId: "step",
triggerEntryId: "trigger",
configuration,
streamOptions: {},
retryPolicy: { maxAttempts: 2, baseDelayMs: 0 },
overflowRecoveryUsed: false,
},
attempt: 1,
responseEntryId: "response",
usageId: "usage",
intendedOutputLimit: 100,
contextWindow: 1000,
}),
...frames.map((frame) =>
storedValues.appendList(storedValues.pendingAssistantFrames(frameOperationId, "response"), frame),
),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: frameOperationId,
lastOperationId: null,
inbox: [],
}),
]);
const frameHarness = await attach(frameSession);
const frameWatch = await frameHarness.watch(BACKGROUND_CONTEXT);
expect(frameWatch.snapshot.operation?.streamingMessage?.content).toEqual([{ type: "text", text: "partial" }]);
frameWatch.unsubscribe();
const toolSession = await createSession();
const toolUsage = {
input: 1,
output: 2,
cacheRead: 3,
cacheWrite: 4,
totalTokens: 10,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
};
const assistant = fauxAssistantMessage([
{ type: "text", text: "before" },
{ type: "toolCall", id: "call-completed", name: "completed", arguments: { source: "completed" } },
{ type: "toolCall", id: "call-running", name: "read", arguments: { source: "running" } },
{
type: "toolCall",
id: "call-without-checkpoint",
name: "write",
arguments: { source: "without-checkpoint" },
},
{ type: "toolCall", id: "call-ready", name: "real", arguments: { source: "real" } },
{ type: "toolCall", id: "call-synthetic", name: "missing", arguments: { source: "synthetic" } },
{ type: "toolCall", id: "call-planned", name: "planned", arguments: { source: "planned" } },
]);
const toolOperationId = toolSession.idGenerator.next();
await commit(toolSession, [
sessionWrites.insertEntry({ id: "assistant", parentId: null, type: "message", message: assistant }),
storedValues.setValue(storedValues.operationMeta(toolOperationId), {
operationId: toolOperationId,
lane: "main",
sourceTipId: null,
startedAt: 1,
intent: { kind: "run", promptEntryIds: [] },
}),
storedValues.setValue(storedValues.operationState(toolOperationId), {
...runScope(),
at: "tools",
batch: {
assistantEntryId: "assistant",
configuration,
turnId: "turn",
calls: [
{ status: "completed", sourceIndex: 1, resultEntryId: "completed", terminate: false },
{ status: "effect_pending", sourceIndex: 2, resultEntryId: "result", replay: "safe" },
{
status: "effect_pending",
sourceIndex: 3,
resultEntryId: "without-checkpoint",
replay: "never",
},
{ status: "outcome_ready", sourceIndex: 4, resultEntryId: "ready", terminate: true },
{ status: "outcome_ready", sourceIndex: 5, resultEntryId: "synthetic", terminate: false },
{ status: "planned", sourceIndex: 6, resultEntryId: "planned" },
],
},
}),
sessionWrites.insertEntry({
id: "completed",
parentId: "assistant",
type: "message",
message: {
role: "toolResult",
toolCallId: "call-completed",
toolName: "completed",
content: [{ type: "text", text: "completed" }],
isError: false,
timestamp: 2,
},
}),
storedValues.setValue(storedValues.operationToolArgs(toolOperationId, "turn", 2), { path: "file" }),
storedValues.setValue(storedValues.operationToolArgs(toolOperationId, "turn", 3), { path: "output" }),
storedValues.setValue(storedValues.operationToolArgs(toolOperationId, "turn", 4), { path: "settled" }),
storedValues.setValue(storedValues.pendingToolOutput(toolOperationId, "result"), {
content: [{ type: "text", text: "partial" }],
details: { bytes: 1 },
}),
storedValues.setValue(storedValues.pendingEntry("ready"), {
type: "message",
payload: {
role: "toolResult",
toolCallId: "call-ready",
toolName: "real",
content: [{ type: "text", text: "settled" }],
details: { kind: "real" },
usage: toolUsage,
addedToolNames: ["later"],
isError: false,
timestamp: 3,
},
}),
storedValues.setValue(storedValues.pendingEntry("synthetic"), {
type: "message",
payload: {
role: "toolResult",
toolCallId: "call-synthetic",
toolName: "missing",
content: [{ type: "text", text: "unavailable" }],
isError: true,
timestamp: 4,
},
}),
storedValues.setValue(storedValues.branchTip("main"), "completed"),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: toolOperationId,
lastOperationId: null,
inbox: [],
}),
]);
const toolHarness = await attach(toolSession);
const toolWatch = await toolHarness.watch(BACKGROUND_CONTEXT);
expect(toolWatch.snapshot.transcript.map(({ id }) => id)).toEqual(["assistant", "completed"]);
expect(toolWatch.snapshot.operation?.runningTools).toEqual([
{
status: "running",
toolCallId: "call-running",
toolName: "read",
args: { path: "file" },
result: { content: [{ type: "text", text: "partial" }], details: { bytes: 1 } },
},
{
status: "running",
toolCallId: "call-without-checkpoint",
toolName: "write",
args: { path: "output" },
},
{
status: "settled",
toolCallId: "call-ready",
toolName: "real",
args: { path: "settled" },
result: {
content: [{ type: "text", text: "settled" }],
details: { kind: "real" },
usage: toolUsage,
addedToolNames: ["later"],
terminate: true,
},
isError: false,
},
{
status: "settled",
toolCallId: "call-synthetic",
toolName: "missing",
args: { source: "synthetic" },
result: {
content: [{ type: "text", text: "unavailable" }],
details: undefined,
},
isError: true,
},
]);
expect(toolWatch.snapshot.operation?.runningTools[1]).not.toHaveProperty("result");
toolWatch.unsubscribe();
});
it("faults missing or mismatched staged outcome-ready results", async () => {
for (const corruption of ["missing", "mismatched"] as const) {
const session = await createSession();
const operationId = session.idGenerator.next();
const writes: Write[] = [
sessionWrites.insertEntry({
id: "assistant",
parentId: null,
type: "message",
message: fauxAssistantMessage([
{ type: "toolCall", id: "call", name: "read", arguments: { path: "file" } },
]),
}),
storedValues.setValue(storedValues.operationMeta(operationId), {
operationId,
lane: "main",
sourceTipId: null,
startedAt: 1,
intent: { kind: "run", promptEntryIds: [] },
}),
storedValues.setValue(storedValues.operationState(operationId), {
...runScope(),
at: "tools",
batch: {
assistantEntryId: "assistant",
configuration,
turnId: "turn",
calls: [{ status: "outcome_ready", sourceIndex: 0, resultEntryId: "result", terminate: false }],
},
}),
storedValues.setValue(storedValues.branchTip("main"), "assistant"),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: operationId,
lastOperationId: null,
inbox: [],
}),
];
if (corruption === "mismatched") {
writes.push(
storedValues.setValue(storedValues.pendingEntry("result"), {
type: "message",
payload: {
role: "toolResult",
toolCallId: "other-call",
toolName: "read",
content: [],
isError: false,
timestamp: 2,
},
}),
);
}
await commit(session, writes);
const harness = await attach(session);
await expect(harness.watch(BACKGROUND_CONTEXT)).rejects.toBeInstanceOf(HarnessFault);
}
});
it("faults required payload corruption and unsubscribes the incomplete watcher", async () => {
const session = await createSession();
await commit(session, [
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
lastOperationId: null,
inbox: [{ entryId: "missing", kind: "nextRun" }],
}),
]);
const harness = await attach(session);
let unsubscribed = false;
const originalWatch = harness.events.watch.bind(harness.events);
vi.spyOn(harness.events, "watch").mockImplementation(
<T>(snapshot: T, filter: (event: HarnessEvent) => boolean, context: Context): WatchHandle<T> => {
const handle = originalWatch(snapshot, filter, context);
return {
...handle,
unsubscribe: () => {
unsubscribed = true;
handle.unsubscribe();
},
};
},
);
await expect(harness.watch(BACKGROUND_CONTEXT)).rejects.toBeInstanceOf(HarnessFault);
expect(unsubscribed).toBe(true);
});
it("returns snapshot-before plus buffered events when watch wins the lane line", async () => {
const storage = new BlockingScanStorage();
const session = await createSession(storage);
await commit(session, [
sessionWrites.insertEntry({ id: "root", parentId: null, type: "custom", customType: "root" }),
storedValues.setValue(storedValues.branchTip("main"), "root"),
]);
const harness = await attach(session);
storage.block = true;
const watchPromise = harness.watch(BACKGROUND_CONTEXT);
await storage.started.promise;
const sourceKey = createContextKey<string>("watch.event.source");
const sourceContext = withContextValue(sourceKey, "append", BACKGROUND_CONTEXT);
const append = harness.appendMessage({ role: "user", content: "later", timestamp: 2 }, sourceContext);
storage.release.resolve();
const watch = await watchPromise;
expect(watch.snapshot.transcript.map(({ id }) => id)).toEqual(["root"]);
const seen: Array<{ type: string; sameContext: boolean }> = [];
watch.start((event, eventContext) => {
seen.push({ type: event.type, sameContext: eventContext === sourceContext });
});
await append;
await new Promise((resolve) => setTimeout(resolve, 0));
expect(seen.map(({ type }) => type)).toEqual(["message_start", "message_end", "entry_added"]);
expect(seen.every(({ sameContext }) => sameContext)).toBe(true);
watch.unsubscribe();
});
it("returns snapshot-after without replay when publication wins", async () => {
const session = await createSession();
const harness = await attach(session);
const entryId = await harness.appendMessage(
{ role: "user", content: "existing", timestamp: 1 },
BACKGROUND_CONTEXT,
);
const watch = await harness.watch(BACKGROUND_CONTEXT);
const seen = vi.fn();
watch.start(seen);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(watch.snapshot.transcript.map(({ id }) => id)).toEqual([entryId]);
expect(seen).not.toHaveBeenCalled();
watch.unsubscribe();
});
});