feat(agent): add durable generation procedures

This commit is contained in:
Mario Zechner 2026-08-24 21:56:02 +02:00
parent 9f666bfbbb
commit 9b034fc45e
8 changed files with 2131 additions and 31 deletions

View file

@ -173,7 +173,7 @@ TX[ upsert pi.op.state/O = effect_pending (reserves response n2, usage u1) ]
… provider streams … ← the uncertain window
TX[ append pi.pending.assistant_frame/O:n2 += frame ] ← one per streamed event,
enqueued without awaiting
TX[ insert entry n2, upsert pi.lane.leaf = n2, insert usage u1,
TX[ insert entry n2, insert usage u1, upsert pi.lane.leaf = n2,
delete list pi.pending.assistant_frame/O:n2,
upsert pi.op.state/O = tools (result id n3 reserved) ]
TX[ upsert pi.op.tool_args/O:s1:0, upsert pi.op.state/O = call 0 effect_pending ]
@ -245,7 +245,7 @@ Source type provenance:
- `AgentMessage`, `AgentTool`, `AgentToolResult`, `QueueMode`, and `ThinkingLevel`: `packages/agent/src/types.ts`.
- `Skill`, `PromptTemplate`, `AgentHarnessResources` (`Resources` below), `AgentHarnessTool`, `AgentHarnessToolContextSource`, `AgentHarnessToolInvocation`, `AgentHarnessToolUpdateCallback`, `AgentHarnessToolUpdateOptions`, `AgentHarnessStreamOptions`, and `AgentHarnessStreamOptionsPatch`: `packages/agent/src/harness/types.ts`.
- `Model`, `Models`, `Usage`, `RetryPolicy`, `StopReason`, `AssistantMessage`, `ImageContent`, provider messages, stream options, and deferred handles: `packages/ai`. `AiContext` below aliases pi-ai's provider request `Context` to distinguish it from the harness invocation `Context`.
- `Model`, `Models`, `Tool`, `Usage`, `RetryPolicy`, `StopReason`, `AssistantMessage`, `ImageContent`, provider messages, stream options, and deferred handles: `packages/ai`. `AiContext` below aliases pi-ai's provider request `Context` to distinguish it from the harness invocation `Context`.
- `AssistantMessageFrame`, `assistantMessageEventToFrame`, and `reduceAssistantMessageFrames`: `packages/ai` (`@earendil-works/pi-ai`), `src/utils/assistant-message-frame.ts`. The harness defines no second frame codec or reducer.
- `CompactionSettings`, `CompactionPreparation`, `CompactResult`, `BranchPreparation`, and `BranchSummaryResult`: `packages/agent/src/harness/compaction/`. Existing preparation and split-turn algorithms remain the implementation starting point unless this document explicitly changes them.
- `TelemetryContext` and typed schema helpers: `packages/telemetry`; the agent-owned schemas remain in `packages/agent/src/harness/telemetry.ts`.
@ -1397,14 +1397,14 @@ The complete transition table — every row is one `commit()`; classification or
| assistant `effect_pending` | settlement classifies `deferred` with a valid handle | §3.7's deferred row | suspended, `poll: 0`, `sourceEntryId: R` |
| suspended, poll *k* | captured model is unavailable when a drive supplies `pollDeferred: true` | `TX[ S(failure_drain{error:{code:"model_unavailable", ...}, provenance:{kind:"configuration"}}) ]` — the handle is abandoned without fabricating a fetch response or usage | failure_drain |
| suspended, poll *k* | captured model resolves and a drive supplies `pollDeferred: true`: the poll's `before_request` settlement commits its intent, consuming the pass's single poll permit | mint fresh R′ and U′, then `TX[ S(deferred{effect_pending, poll k+1, responseEntryId R′, usageId U′}) ]` | effect_pending, poll *k*+1 |
| effect_pending, poll *k*+1 | fetch returns **pending** with a completely equal handle | `TX[ insert response entry R′, upsert pi.lane.leaf = R′, insert usage U′, S(latestAssistantEntryId=R′, deferred{suspended, sourceEntryId R′, poll k+1}) ]` — the pending response becomes the next source and the operation re-suspends; no second poll this invocation | suspended, poll *k*+1 |
| effect_pending | fetch returns **pending** with a mismatched handle | normalize to a durable `error` response explaining the mismatch: `TX[ insert normalized response R′, upsert pi.lane.leaf = R′, insert usage U′, S(latestAssistantEntryId=R′, failure_drain{error, provenance:response R′}) ]` | failure_drain |
| effect_pending | fetch returns **ready** with tool calls | `TX[ insert response R′, upsert pi.lane.leaf = R′, insert usage U′, S(latestAssistantEntryId=R′, tools{plan with reserved result ids}) ]` — result ids minted as followers of R′ (§1.2) | tools |
| effect_pending | fetch returns **ready** without tool calls | `TX[ insert response R′, upsert pi.lane.leaf = R′, insert usage U′, S(latestAssistantEntryId=R′, checkpoint{may_finish, includeFinalAssistant:true}) ]` | checkpoint |
| effect_pending | fetch settles as a provider `error` | `TX[ insert response R′, upsert pi.lane.leaf = R′, insert usage U′, S(latestAssistantEntryId=R′, failure_drain{error, provenance:response R′}) ]` — polls have no retry path | failure_drain |
| effect_pending, poll *k*+1 | fetch returns **pending** with a completely equal handle | `TX[ insert response entry R′, insert usage U′, upsert pi.lane.leaf = R′, S(latestAssistantEntryId=R′, deferred{suspended, sourceEntryId R′, poll k+1}) ]` — the pending response becomes the next source and the operation re-suspends; no second poll this invocation | suspended, poll *k*+1 |
| effect_pending | fetch returns **pending** with a mismatched handle | normalize to a durable `error` response explaining the mismatch: `TX[ insert normalized response R′, insert usage U′, upsert pi.lane.leaf = R′, S(latestAssistantEntryId=R′, failure_drain{error, provenance:response R′}) ]` | failure_drain |
| effect_pending | fetch returns **ready** with tool calls | `TX[ insert response R′, insert usage U′, upsert pi.lane.leaf = R′, S(latestAssistantEntryId=R′, tools{plan with reserved result ids}) ]` — result ids minted as followers of R′ (§1.2) | tools |
| effect_pending | fetch returns **ready** without tool calls | `TX[ insert response R′, insert usage U′, upsert pi.lane.leaf = R′, S(latestAssistantEntryId=R′, checkpoint{may_finish, includeFinalAssistant:true}) ]` | checkpoint |
| effect_pending | fetch settles as a provider `error` | `TX[ insert response R′, insert usage U′, upsert pi.lane.leaf = R′, S(latestAssistantEntryId=R′, failure_drain{error, provenance:response R′}) ]` — polls have no retry path | failure_drain |
| effect_pending, restored, running control | crash left the poll's outcome unknown; a drive with a poll permit finds the captured model unavailable | `TX[ delete list pi.pending.assistant_frame/O:R′, S(failure_drain{error:{code:"model_unavailable", ...}, provenance:{kind:"configuration"}}) ]` — abandon the old reserved response/usage strings without fabricating settlement | failure_drain |
| effect_pending, restored, running control | crash left the poll's outcome unknown; the next drive with a poll permit resolves the captured model and replaces it | mint fresh R″/U″ and commit a fresh intent at the **same** poll number — an unknown-outcome poll never completed, so `poll` does not increment; the old reserved id strings are abandoned, never materialized, and the intent transaction deletes the abandoned old frame list; the replacement poll starts a fresh list under its fresh response id | effect_pending, poll *k*+1 |
| effect_pending, cancelled control | reconciliation, live or restored (§4.5, §4.6) | synthetic settlement under the **existing** reserved ids, preserving partial content reduced from committed frames: `TX[ insert synthetic aborted response R′, upsert pi.lane.leaf = R′, insert zero usage U′, delete frame list O:R′, S(latestAssistantEntryId=R′, cancelled checkpoint{may_finish}) ]` | cancelled checkpoint → aborted finish |
| effect_pending, cancelled control | reconciliation, live or restored (§4.5, §4.6) | synthetic settlement under the **existing** reserved ids, preserving partial content reduced from committed frames: `TX[ insert synthetic aborted response R′, insert zero usage U′, upsert pi.lane.leaf = R′, delete frame list O:R′, S(latestAssistantEntryId=R′, cancelled checkpoint{may_finish}) ]` | cancelled checkpoint → aborted finish |
| suspended, cancelled control | reconciliation | no fetch starts; best-effort `cancel_deferred` targets the newest source (§4.6), and the operation finishes through the aborted terminal transaction | terminal |
### Structural work
@ -1559,13 +1559,13 @@ Pre-acceptance rejections write **nothing**: `LaneBusy`, `NothingToCompact`, `In
| checkpoint `need_assistant` | ordinary procedure | snapshot current lane config, stream options, and normalized retry policy inline into the context in `TX[ S(assistant{ready, nextAttempt:1}) ]` | ready |
| assistant `ready` | captured model or configured active-tool definition is unavailable before request admission | `TX[ S(failure_drain{error:{code:"model_unavailable" | "configured_tools_unavailable", ...}, provenance:{kind:"configuration"}}) ]` — no response/usage ids are reserved and no assistant entry or usage row is fabricated | failure_drain |
| assistant `ready` | identities resolve and `before_request` completes | mint R and U, then `TX[ S(assistant{effect_pending, attempt=nextAttempt, responseEntryId R, usageId U, intendedOutputLimit, contextWindow}) ]` | effect_pending |
| effect_pending | settles with tool calls | `TX[ insert response entry R, upsert pi.lane.leaf = R, insert usage U, delete frame list O:R, S(latestAssistantEntryId=R, tools{plan with reserved result ids}) ]` | tools |
| effect_pending | retryable error, attempts remain | `TX[ insert response entry R, upsert pi.lane.leaf = R, insert usage U, S(latestAssistantEntryId=R, assistant{retry_wait, nextAttempt k+1, notBefore}) ]` | retry_wait |
| effect_pending | first overflow, preparation non-empty | `TX[ insert response entry R **normalized to error**, upsert pi.lane.leaf = R, insert usage U, upsert pi.op.preparation/O:{taskId} = P, S(latestAssistantEntryId=R, compaction{reason:overflow, structural:{deciding, taskId}, resumeAfter:{checkpoint, prior trigger, need_assistant(true)}}) ]` | compaction |
| effect_pending | first overflow, preparation empty | `TX[ insert normalized response entry R, upsert pi.lane.leaf = R, insert usage U, S(latestAssistantEntryId=R, failure_drain{error, provenance:response R}) ]` | failure_drain |
| effect_pending | `stopReason: "deferred"` | `TX[ insert response entry R, upsert pi.lane.leaf = R, insert usage U, S(latestAssistantEntryId=R, deferred{suspended, sourceEntryId R, poll 0, configuration/options copied}) ]` | deferred |
| effect_pending | `stop` or genuine `length` | `TX[ insert response entry R, upsert pi.lane.leaf = R, insert usage U, S(latestAssistantEntryId=R, checkpoint{may_finish, includeFinalAssistant:true}) ]` | checkpoint |
| effect_pending | terminal error, retries exhausted, or 2nd overflow | `TX[ insert response entry R, upsert pi.lane.leaf = R, insert usage U, S(latestAssistantEntryId=R, failure_drain{error, provenance:response R}) ]` | failure_drain |
| effect_pending | settles with tool calls | `TX[ insert response entry R, insert usage U, upsert pi.lane.leaf = R, delete frame list O:R, S(latestAssistantEntryId=R, tools{plan with reserved result ids}) ]` | tools |
| effect_pending | retryable error, attempts remain | `TX[ insert response entry R, insert usage U, upsert pi.lane.leaf = R, S(latestAssistantEntryId=R, assistant{retry_wait, nextAttempt k+1, notBefore}) ]` | retry_wait |
| effect_pending | first overflow, preparation non-empty | `TX[ insert response entry R **normalized to error**, insert usage U, upsert pi.lane.leaf = R, upsert pi.op.preparation/O:{taskId} = P, S(latestAssistantEntryId=R, compaction{reason:overflow, structural:{deciding, taskId}, resumeAfter:{checkpoint, prior trigger, need_assistant(true)}}) ]` | compaction |
| effect_pending | first overflow, preparation empty | `TX[ insert normalized response entry R, insert usage U, upsert pi.lane.leaf = R, S(latestAssistantEntryId=R, failure_drain{error, provenance:response R}) ]` | failure_drain |
| effect_pending | `stopReason: "deferred"` | `TX[ insert response entry R, insert usage U, upsert pi.lane.leaf = R, S(latestAssistantEntryId=R, deferred{suspended, sourceEntryId R, poll 0, configuration/options copied}) ]` | deferred |
| effect_pending | `stop` or genuine `length` | `TX[ insert response entry R, insert usage U, upsert pi.lane.leaf = R, S(latestAssistantEntryId=R, checkpoint{may_finish, includeFinalAssistant:true}) ]` | checkpoint |
| effect_pending | terminal error, retries exhausted, or 2nd overflow | `TX[ insert response entry R, insert usage U, upsert pi.lane.leaf = R, S(latestAssistantEntryId=R, failure_drain{error, provenance:response R}) ]` | failure_drain |
| retry_wait | gated timer reaches `notBefore` | `TX[ S(assistant{ready, nextAttempt:k+1}) ]` | ready |
Every settlement row above — tool plan, retry, overflow, deferred, checkpoint, and failure alike — additionally executes `deleteList(pendingAssistantFrames(O, R))`; the first row shows it explicitly and the rest abbreviate. Settlement and frame cleanup are one atomic commit.
@ -1702,7 +1702,7 @@ Structural provider streams are internal: they emit **no** public assistant-mess
```
TX[ insert e_41 = { …assistant response, stopReason: "error",
errorMessage: "context window exceeded: …" },
upsert pi.lane.leaf/main = "e_41", insert usage u_41,
insert usage u_41, upsert pi.lane.leaf/main = "e_41",
upsert pi.op.preparation/op_9:t_1 = <structural preparation>,
S(compaction{ reason: overflow,
structural: { deciding, taskId: "t_1" },
@ -3152,7 +3152,9 @@ interface AssistantResponseMetadata {
}
interface AssistantStreamObserver {
start(message: AssistantMessage, context: Context): void | Promise<void>;
start(message: AssistantMessage,
event: Extract<AssistantMessageEvent, { type: "start" }> | undefined,
context: Context): void | Promise<void>;
update(message: AssistantMessage, event: AssistantMessageEvent,
context: Context): void | Promise<void>;
end(message: SettledAssistantMessage, context: Context): void | Promise<void>;
@ -3161,7 +3163,7 @@ interface AssistantStreamObserver {
interface HarnessAssistantStreamConfig {
model: Model;
systemPrompt: string;
tools?: AgentTool[];
tools?: Tool[];
thinkingLevel: ThinkingLevel;
streamOptions: AgentHarnessStreamOptions;
transformContext?: (
@ -3201,7 +3203,7 @@ transformContext(requestContext, context)
→ map curated stream options + thinking level to SimpleStreamOptions
→ install context.abortSignal, context.telemetryContext, beforePayload, and metadata capture
→ request(aiContext, options, context)
→ observer.start(message, context)
→ observer.start(message, startEvent, context)
→ observer.update(message, event, context)*
→ settle the stream completely
→ afterResponse(settled message, captured metadata, context)
@ -3209,7 +3211,7 @@ transformContext(requestContext, context)
→ return the final settled message
```
It never mutates `messages`. Every callback receives the same explicit invocation Context unless its adapter deliberately derives a child span Context. The harness-supplied observer converts each streamed event with `assistantMessageEventToFrame` and synchronously enqueues its invocation-fenced frame append without awaiting storage (§3.7); `afterResponse` is always installed, even with no hook listeners, because it first stops frame admission and awaits the latest frame-write promise before invoking the optional `after_response` pipeline. If the stream terminates without a start event, it emits `observer.start` for the final message before `observer.end`, matching the harness lifecycle contract; this synthesized lifecycle start occurs after stream consumption and is not converted into or appended as a durable frame. If abort interrupts the parked `afterResponse` adapter, the block awaits the carried abort-mutation promise, skips that hook, emits `observer.end` with the raw settled message, and returns it so the caller can commit it under the now-current cancellation control. `beforePayload` maps to pi-ai's payload callback. Response metadata capture maps to pi-ai's `onResponse`; it is distinct from `afterResponse`, because `onResponse` runs before the response body is consumed while the harness hook transforms the settled assistant message afterward. The harness exposes neither callback through `AgentHarnessStreamOptions`.
It never mutates `messages`. Every callback receives the same explicit invocation Context unless its adapter deliberately derives a child span Context. The harness-supplied observer converts each streamed event with `assistantMessageEventToFrame` and synchronously enqueues its invocation-fenced frame append without awaiting storage (§3.7); `afterResponse` is always installed, even with no hook listeners, because it first stops frame admission and awaits the latest frame-write promise before invoking the optional `after_response` pipeline. If the stream terminates without a start event, it emits `observer.start` with `event: undefined` for the final message before `observer.end`, matching the harness lifecycle contract; this synthesized lifecycle start occurs after stream consumption and is not converted into or appended as a durable frame. If abort interrupts the parked `afterResponse` adapter, the block awaits the carried abort-mutation promise, skips that hook, emits `observer.end` with the raw settled message, and returns it so the caller can commit it under the now-current cancellation control. `beforePayload` maps to pi-ai's payload callback. Response metadata capture maps to pi-ai's `onResponse`; it is distinct from `afterResponse`, because `onResponse` runs before the response body is consumed while the harness hook transforms the settled assistant message afterward. The harness exposes neither callback through `AgentHarnessStreamOptions`.
The request function, not this block, owns registry dispatch, auth, and operation admission:

View file

@ -1,6 +1,6 @@
# WP05 — Direct durable drive
**Status: M2 complete; M3 next.** Independent Opus and GPT-5.6-Sol reviews approved the authoritative-`Lane.state`, straight-line procedure correction and M2 implementation with no blockers.
**Status: M3 complete; M4 next.** Independent Opus, GPT-5.6-Sol, and Fable reviews approved the authoritative-`Lane.state` design and M3 generation implementation with no blockers.
Removes breakpoints/manual drive, then implements the complete durable execution graph. Public drive stays disabled until every reachable phase and reconciliation path exists.
@ -1316,7 +1316,7 @@ Only `packages/coding-agent/src/core/cache-stats.ts` (comment) and vendored `hig
**Modify**
| Path | Change |
|---|---|
| `src/harness/execution/assistant.ts` | `request` admitted through `Gate`; observer receives Context |
| `src/harness/execution/assistant.ts` | use provider-facing `Tool` declarations; observer callbacks receive trailing Context, and `start` also receives the raw start event (or `undefined` for a synthesized lifecycle start) so frame persistence uses pi-ai's codec; the runtime request adapter admits `Models.streamSimple` through `Gate` |
**Behavior/invariants landed.** Preparation precedes intent; ids reserved at intent and honoured at settlement; frames enqueued synchronously without per-frame awaits; settlement deletes the frame list; every transition plans from the current owned projection and publishes its exact next projection; checkpoint separates tree parent/leaf from `triggerEntryId` for unprojected custom writes.
@ -1672,7 +1672,7 @@ Both methods run on `Lane`; procedures do not remove ownership. The external fin
`compact()` and `navigateTree()` are *accept + drive*, so they cannot be written before `accept` admits their operation kinds. Doing the composition first would force a second admission path that only the convenience method can reach.
**Behavior/invariants landed.** Owner-before-idle claim order; join observes delivered events; usage row `seq` from `CommitResult`, and usage-event totals read `commit.stats.usage` inside `events(commit)` — no process accumulator (§2.12); every hook/event/observation carries the emitting Context. `AgentHarness.watchSession` is **explicitly deferred** and keeps its rejection.
**Behavior/invariants landed.** Owner-before-idle claim order; join observes delivered events; usage row `seq` from `CommitResult`, and usage-event totals read `commit.stats.usage` inside `events(commit)` — no process accumulator (§2.12); every hook/event/observation carries the emitting Context. Before public exposure, pi-ai provider streams must guarantee that a queued start event's partial cannot be mutated before `assistantMessageEventToFrame` consumes it; pin this with a back-to-back shared-partial provider test that does not yield between start and block-start publication. `AgentHarness.watchSession` is **explicitly deferred** and keeps its rejection.
**Focused tests**
| Path | Theme |
@ -1681,7 +1681,7 @@ Both methods run on `Lane`; procedures do not remove ownership. The external fin
| `test/harness/runtime2/drive-ownership.test.ts` (independent completion/removal) | run each case with a **non-cooperative provider** and again with a **non-cooperative tool** that ignores its signal and never settles. **External finalization**: after the terminal event is delivered, public completion resolves with the finalizer's outcome while the effect is still stuck; `accept` returns `LaneBusy` throughout; exactly **one** `run_end` is delivered; after the effect finally returns the owner clears and `accept` succeeds. **Invocation abandonment**: the public promise rejects `DriveAbandoned`, a new `drive` claims the lane **before** the old effect returns, durable state is still `effect_pending`, and the new pass runs orphan recovery. **Close/fault**: resolves without waiting; public promises reject `HarnessClosed`/`HarnessFault`. In every case the detached pass produces no unhandled rejection, and `Drive.settle`/`Drive.fail` and exact `removeDrive` remain idempotent |
| `test/harness/runtime2/drive-ownership.test.ts` (ABA fencing) | abandon an invocation whose effect is still running, let a **replacement owner claim the same `operationId`**, then let the old effect return: its transition planner and its progress writes both decline via `isDriveActive` even though the operation id is unchanged; zero commits attributed to the old task (asserted through `InstrumentedStorage`); the replacement owner is **not** cleared by the old task's cleanup; the replacement's own commits succeed |
| `test/harness/runtime2/drive-surfaces.test.ts` | `accept({kind:"compaction"})` and `accept({kind:"navigation"})` admit and drive before any convenience method is used; convenience = accept + drive; `resume`; `requestAbort` through the public method reaches the M7 primitive; `Closed` returned not thrown; listeners settle before the next procedure hook; usage totals per settlement equal `getStats()`; **reopen a session with historical usage** → the first new usage event's totals include the history; **two lanes committing usage concurrently** → each event's totals match its own commit position and the final totals equal `getStats()`; Context lineage |
| `test/harness/runtime2/drive-crash-matrix.test.ts` | release N commits → discard → reopen → drive, for every N in every phase path; every gated boundary exposes its exact committed writes; gated vs ungated byte-identical writes |
| `test/harness/runtime2/drive-crash-matrix.test.ts` | release N commits → discard → reopen → drive, for every N in every phase path; every gated boundary exposes its exact committed writes; gated vs ungated byte-identical writes; a provider that publishes start and then mutates one shared partial for block start without yielding still persists a reducible immutable start frame |
**Commands.** `npm run check` · `./test.sh`

View file

@ -7,8 +7,9 @@ import type {
Message,
Model,
SimpleStreamOptions,
Tool,
} from "@earendil-works/pi-ai";
import type { AgentMessage, AgentTool, ThinkingLevel } from "../../types.ts";
import type { AgentMessage, ThinkingLevel } from "../../types.ts";
import type { Context } from "../context.ts";
import type { SettledAssistantMessage } from "../session/types.ts";
import type { AgentHarnessStreamOptions } from "../types.ts";
@ -22,7 +23,11 @@ export interface AssistantResponseMetadata {
/** Process-local lifecycle observer for one assistant stream. */
export interface AssistantStreamObserver {
start(message: AssistantMessage, context: Context): void | Promise<void>;
start(
message: AssistantMessage,
event: Extract<AssistantMessageEvent, { type: "start" }> | undefined,
context: Context,
): void | Promise<void>;
update(message: AssistantMessage, event: AssistantMessageEvent, context: Context): void | Promise<void>;
end(message: SettledAssistantMessage, context: Context): void | Promise<void>;
}
@ -31,7 +36,7 @@ export interface AssistantStreamObserver {
export interface HarnessAssistantStreamConfig {
model: Model<Api>;
systemPrompt: string;
tools?: AgentTool[];
tools?: Tool[];
thinkingLevel: ThinkingLevel;
streamOptions: AgentHarnessStreamOptions;
transformContext?: (
@ -126,7 +131,7 @@ export async function streamHarnessAssistant(
for await (const event of stream) {
if (event.type === "start") {
started = true;
await config.observer.start({ ...event.partial }, context);
await config.observer.start({ ...event.partial }, event, context);
} else if (isUpdateEvent(event)) {
await config.observer.update({ ...event.partial }, event, context);
}
@ -134,7 +139,7 @@ export async function streamHarnessAssistant(
const settled = (await stream.result()) as SettledAssistantMessage;
if (!started) {
await config.observer.start({ ...settled }, context);
await config.observer.start({ ...settled }, undefined, context);
}
let finalMessage = settled;
if (config.afterResponse) {

View file

@ -0,0 +1,608 @@
import type { AgentMessage } from "../../../types.ts";
import type { HarnessEvent, TerminalOperationOutcome } from "../../agent-harness.ts";
import { insertEntry } from "../../session/commit.ts";
import { buildSessionContext } from "../../session/context.ts";
import { SessionInvariantError } from "../../session/session.ts";
import type {
CheckpointPhase,
Entry,
LaneLastResult,
MessageEntry,
NewEntry,
RunState,
SettledAssistantMessage,
} from "../../session/types.ts";
import {
deleteValue,
laneLastResult as laneLastResultValue,
laneLeaf,
laneState as laneStateValue,
operationState as operationStateValue,
pendingEntry,
setValue,
} from "../../session/values.ts";
import type { Lane } from "../lane.ts";
import type { Drive, LaneCommand, LostOwnership, ProcedureResult } from "../types.ts";
import { operationCleanupWrites } from "./terminal.ts";
type ReadResult<T> = { kind: "value"; value: T } | { kind: "state_changed" } | LostOwnership;
function isSettledAssistant(message: AgentMessage): message is SettledAssistantMessage {
return message.role === "assistant" && message.stopReason !== "pending";
}
function sameCheckpoint(current: CheckpointPhase, expected: CheckpointPhase): boolean {
return (
current.triggerEntryId === expected.triggerEntryId &&
current.thresholdCheckedTriggerEntryId === expected.thresholdCheckedTriggerEntryId &&
current.skipInboxOnce === expected.skipInboxOnce &&
current.continuation.kind === expected.continuation.kind &&
(current.continuation.kind !== "need_assistant" ||
(expected.continuation.kind === "need_assistant" &&
current.continuation.overflowRecoveryUsed === expected.continuation.overflowRecoveryUsed)) &&
(current.continuation.kind !== "may_finish" ||
(expected.continuation.kind === "may_finish" &&
current.continuation.includeFinalAssistant === expected.continuation.includeFinalAssistant))
);
}
function lifecycleEvents(entry: Entry, lane: string, runId: string): HarnessEvent[] {
return entry.type === "message"
? [
{ type: "message_start", lane, runId, message: entry.message },
{ type: "message_end", lane, runId, message: entry.message, entryId: entry.id },
{ type: "entry_added", lane, entry },
]
: [{ type: "entry_added", lane, entry }];
}
async function readRunContext<TContext extends object | undefined>(
lane: Lane<TContext>,
drive: Drive,
phase: CheckpointPhase,
): Promise<ReadResult<AgentMessage[]>> {
const entries = await lane.commandDriveOwned<ReadResult<Entry[]>>(
drive,
async (state, reader): Promise<LaneCommand<ReadResult<Entry[]>>> => {
const operation = state.operation;
if (operation === null || operation.state.kind !== "run") {
throw new SessionInvariantError("Checkpoint context reached a non-run operation");
}
if (operation.state.control.status === "cancel_requested") {
return { kind: "return", result: { kind: "state_changed" } as const };
}
if (operation.state.phase.kind !== "checkpoint" || !sameCheckpoint(operation.state.phase, phase)) {
throw new SessionInvariantError("Checkpoint context lost its durable boundary");
}
if (state.leafId === null) throw new SessionInvariantError("Run checkpoint has no branch leaf");
const path = (
await reader.scanBranch(
{ start: state.leafId, stopAtType: "compaction", order: "newestFirst" },
drive.context,
)
).reverse();
return { kind: "return", result: { kind: "value", value: path } as const };
},
drive.context,
);
if (entries.kind !== "value") return entries;
return {
kind: "value",
value: await buildSessionContext(
entries.value,
{ entryProjectors: lane.readConfig().entryProjectors },
drive.context,
),
};
}
/** Consume before_run and commit the initial checkpoint. */
export async function startRun<TContext extends object | undefined>(
lane: Lane<TContext>,
drive: Drive,
run: RunState,
): Promise<ProcedureResult> {
if (run.phase.kind !== "starting") throw new SessionInvariantError("startRun requires the starting phase");
const prompt = await lane.commandDriveOwned<ReadResult<AgentMessage[]>>(
drive,
async (state, reader): Promise<LaneCommand<ReadResult<AgentMessage[]>>> => {
const operation = state.operation;
if (operation === null || operation.state.kind !== "run" || operation.meta.intent.kind !== "run") {
throw new SessionInvariantError("before_run reached a non-run operation");
}
if (operation.state.control.status === "cancel_requested") {
return { kind: "return", result: { kind: "state_changed" } as const };
}
if (operation.state.phase.kind !== "starting") {
throw new SessionInvariantError("before_run lost the starting boundary");
}
const entries = await reader.getEntries(operation.meta.intent.promptEntryIds, drive.context);
const messages = operation.meta.intent.promptEntryIds.map((id) => {
const entry = entries.get(id);
if (entry?.type !== "message") {
throw new SessionInvariantError(`Run prompt entry ${id} is missing its message`);
}
return entry.message;
});
return { kind: "return", result: { kind: "value", value: messages } as const };
},
drive.context,
);
if (prompt.kind === "lost_ownership") return prompt;
if (prompt.kind === "state_changed") return { kind: "continue" };
const hook = await lane.hooks.runWithGate(
"before_run",
{ lane: lane.name, runId: drive.operationId, prompt: prompt.value, resources: lane.readConfig().resources },
drive.gate,
drive.context,
);
const injected = hook?.messages ?? [];
for (const message of injected) {
if (message.role === "assistant" && message.stopReason === "pending") {
throw new SessionInvariantError("before_run returned a pending assistant message");
}
}
const reserved = injected.map((message) => ({ id: lane.session.idGenerator.next(), message }));
return lane.commandDriveOwned<ProcedureResult>(
drive,
(state) => {
const operation = state.operation;
if (operation === null || operation.state.kind !== "run") {
throw new SessionInvariantError("before_run settlement reached a non-run operation");
}
if (operation.state.control.status === "cancel_requested") {
return { kind: "return", result: { kind: "continue" } as const };
}
if (operation.state.phase.kind !== "starting") {
throw new SessionInvariantError("before_run settlement lost the starting boundary");
}
let parentId = state.leafId;
const entries = reserved.map(({ id, message }) => {
const entry: NewEntry<MessageEntry> = { id, parentId, type: "message", message };
parentId = id;
return entry;
});
const triggerEntryId = parentId;
if (triggerEntryId === null) throw new SessionInvariantError("Run start has no trigger entry");
const nextState: RunState = {
...operation.state,
phase: {
kind: "checkpoint",
continuation: { kind: "need_assistant", overflowRecoveryUsed: false },
triggerEntryId,
},
};
return {
kind: "commit",
writes: [
...entries.map((entry) => insertEntry(entry)),
...(entries.length === 0 ? [] : [setValue(laneLeaf(lane.name), triggerEntryId)]),
setValue(operationStateValue(drive.operationId), nextState),
],
next: {
...state,
leafId: triggerEntryId,
operation: { meta: operation.meta, state: nextState },
},
materialize: () => ({ kind: "continue" }) as const,
events: (commit) =>
entries.flatMap((entry, index) =>
lifecycleEvents(
{ ...entry, seq: commit.seqs[index]!, timestamp: commit.timestamp },
lane.name,
drive.operationId,
),
),
};
},
drive.context,
);
}
async function applyPendingWrites<TContext extends object | undefined>(
lane: Lane<TContext>,
drive: Drive,
phase: CheckpointPhase,
): Promise<ProcedureResult> {
return lane.commandDriveOwned<ProcedureResult>(
drive,
async (state, reader) => {
const operation = state.operation;
if (operation === null || operation.state.kind !== "run") {
throw new SessionInvariantError("Pending-write drain reached a non-run operation");
}
const current = operation.state;
if (current.control.status === "cancel_requested") {
return { kind: "return", result: { kind: "continue" } as const };
}
if (current.phase.kind !== "checkpoint" || !sameCheckpoint(current.phase, phase)) {
throw new SessionInvariantError("Pending-write drain lost its checkpoint boundary");
}
const ids = current.inbox.writes;
if (ids.length === 0) return { kind: "return", result: { kind: "continue" } as const };
const pending = await Promise.all(
ids.map(async (id) => {
const value = await reader.getValue(pendingEntry(id), drive.context);
if (value === undefined) throw new SessionInvariantError(`Pending write ${id} is missing its payload`);
return { id, pending: value.value };
}),
);
let parentId = state.leafId;
let triggerEntryId: string | undefined;
const entries: NewEntry[] = pending.map(({ id, pending: item }) => {
const entry: NewEntry =
item.type === "message"
? { id, parentId, type: "message", message: item.payload }
: {
id,
parentId,
type: "custom",
customType: item.customType,
...(item.payload === undefined ? {} : { data: item.payload }),
};
parentId = id;
if (item.type === "message" || lane.readConfig().entryProjectors[item.customType] !== undefined) {
triggerEntryId = id;
}
return entry;
});
const nextPhase: CheckpointPhase =
triggerEntryId === undefined
? current.phase
: {
kind: "checkpoint",
continuation: { kind: "need_assistant", overflowRecoveryUsed: false },
triggerEntryId,
skipInboxOnce: true,
};
const nextState: RunState = {
...current,
phase: nextPhase,
inbox: { ...current.inbox, writes: [] },
};
return {
kind: "commit",
writes: [
...entries.map((entry) => insertEntry(entry)),
...ids.map((id) => deleteValue(pendingEntry(id))),
setValue(laneLeaf(lane.name), parentId),
setValue(operationStateValue(drive.operationId), nextState),
],
next: { ...state, leafId: parentId, operation: { meta: operation.meta, state: nextState } },
materialize: () => ({ kind: "continue" }) as const,
events: (commit) =>
entries.flatMap((entry, index) =>
lifecycleEvents(
{ ...entry, seq: commit.seqs[index]!, timestamp: commit.timestamp } as Entry,
lane.name,
drive.operationId,
),
),
};
},
drive.context,
);
}
async function consumeQueuedMessages<TContext extends object | undefined>(
lane: Lane<TContext>,
drive: Drive,
phase: CheckpointPhase,
queue: "steer" | "followUp",
): Promise<ProcedureResult> {
return lane.commandDriveOwned<ProcedureResult>(
drive,
async (state, reader) => {
const operation = state.operation;
if (operation === null || operation.state.kind !== "run") {
throw new SessionInvariantError("Queue drain reached a non-run operation");
}
const current = operation.state;
if (current.control.status === "cancel_requested") {
return { kind: "return", result: { kind: "continue" } as const };
}
if (current.phase.kind !== "checkpoint" || !sameCheckpoint(current.phase, phase)) {
throw new SessionInvariantError("Queue drain lost its checkpoint boundary");
}
const allIds = current.inbox[queue];
const mode = queue === "steer" ? current.settings.steeringMode : current.settings.followUpMode;
const ids = mode === "all" ? allIds : allIds.slice(0, 1);
if (ids.length === 0) return { kind: "return", result: { kind: "continue" } as const };
const readMessages = (queuedIds: readonly string[], description: string) =>
Promise.all(
queuedIds.map(async (id) => {
const value = await reader.getValue(pendingEntry(id), drive.context);
if (value?.value.type !== "message") {
throw new SessionInvariantError(`${description} ${id} is missing its message payload`);
}
return { entryId: id, message: value.value.payload };
}),
);
const messages = (await readMessages(ids, `Queued ${queue} entry`)).map(({ entryId, message }) => ({
id: entryId,
message,
}));
const remainingSteerIds = queue === "steer" ? allIds.slice(ids.length) : current.inbox.steer;
const remainingFollowUpIds = queue === "followUp" ? allIds.slice(ids.length) : current.inbox.followUp;
const [remainingSteer, remainingFollowUp, nextRun] = await Promise.all([
readMessages(remainingSteerIds, "Steer entry"),
readMessages(remainingFollowUpIds, "Follow-up entry"),
readMessages(state.pendingNextRun, "Pending next-run entry"),
]);
let parentId = state.leafId;
const entries = messages.map(({ id, message }) => {
const entry: NewEntry<MessageEntry> = { id, parentId, type: "message", message };
parentId = id;
return entry;
});
const triggerEntryId = entries.at(-1)?.id;
if (triggerEntryId === undefined) throw new SessionInvariantError("Queue drain produced no trigger entry");
const nextState: RunState = {
...current,
phase: {
kind: "checkpoint",
continuation: { kind: "need_assistant", overflowRecoveryUsed: false },
triggerEntryId,
skipInboxOnce: true,
},
inbox: { ...current.inbox, [queue]: allIds.slice(ids.length) },
};
return {
kind: "commit",
writes: [
...entries.map((entry) => insertEntry(entry)),
...ids.map((id) => deleteValue(pendingEntry(id))),
setValue(laneLeaf(lane.name), triggerEntryId),
setValue(operationStateValue(drive.operationId), nextState),
],
next: {
...state,
leafId: triggerEntryId,
operation: { meta: operation.meta, state: nextState },
},
materialize: () => ({ kind: "continue" }) as const,
events: (commit) => [
...entries.flatMap((entry, index) =>
lifecycleEvents(
{ ...entry, seq: commit.seqs[index]!, timestamp: commit.timestamp },
lane.name,
drive.operationId,
),
),
{
type: "queue_update",
lane: lane.name,
steer: remainingSteer,
followUp: remainingFollowUp,
nextRun,
},
],
};
},
drive.context,
);
}
async function finishRun<TContext extends object | undefined>(
lane: Lane<TContext>,
drive: Drive,
phase: CheckpointPhase,
): Promise<ProcedureResult> {
const context = await readRunContext(lane, drive, phase);
if (context.kind === "lost_ownership") return context;
if (context.kind === "state_changed") return { kind: "continue" };
const hook = await lane.hooks.runWithGate(
"before_run_end",
{ lane: lane.name, runId: drive.operationId, messages: context.value },
drive.gate,
drive.context,
);
const followUp = hook?.followUp;
const followUpId = followUp === undefined ? undefined : lane.session.idGenerator.next();
const followUpMessage: AgentMessage | undefined =
followUp === undefined ? undefined : { role: "user", content: followUp, timestamp: Date.now() };
return lane.commandDriveOwned<ProcedureResult>(
drive,
async (state, reader) => {
const operation = state.operation;
if (operation === null || operation.state.kind !== "run") {
throw new SessionInvariantError("Run finish reached a non-run operation");
}
const current = operation.state;
if (current.control.status === "cancel_requested") {
return { kind: "return", result: { kind: "continue" } as const };
}
if (current.phase.kind !== "checkpoint" || !sameCheckpoint(current.phase, phase)) {
throw new SessionInvariantError("Run finish lost its checkpoint boundary");
}
if (
current.phase.continuation.kind !== "may_finish" ||
current.inbox.steer.length !== 0 ||
current.inbox.followUp.length !== 0 ||
current.inbox.writes.length !== 0
) {
return { kind: "return", result: { kind: "continue" } as const };
}
if (followUpId !== undefined && followUpMessage !== undefined) {
const nextState: RunState = {
...current,
phase: {
kind: "checkpoint",
continuation: { kind: "need_assistant", overflowRecoveryUsed: false },
triggerEntryId: followUpId,
},
};
const entry: NewEntry<MessageEntry> = {
id: followUpId,
parentId: state.leafId,
type: "message",
message: followUpMessage,
};
return {
kind: "commit",
writes: [
insertEntry(entry),
setValue(laneLeaf(lane.name), followUpId),
setValue(operationStateValue(drive.operationId), nextState),
],
next: {
...state,
leafId: followUpId,
operation: { meta: operation.meta, state: nextState },
},
materialize: () => ({ kind: "continue" }) as const,
events: (commit) =>
lifecycleEvents(
{ ...entry, seq: commit.seqs[0]!, timestamp: commit.timestamp },
lane.name,
drive.operationId,
),
};
}
if (state.leafId === null) throw new SessionInvariantError("Completed run has no leaf");
const finalId = current.phase.continuation.includeFinalAssistant ? current.latestAssistantEntryId : null;
if (current.phase.continuation.includeFinalAssistant && finalId === null) {
throw new SessionInvariantError("Completed run is missing its final assistant");
}
let finalEntry: { id: string; message: SettledAssistantMessage } | undefined;
if (finalId !== null) {
const entry = (await reader.getEntries([finalId], drive.context)).get(finalId);
if (entry?.type !== "message") {
throw new SessionInvariantError(`Run finish references invalid assistant ${finalId}`);
}
const message = entry.message;
if (!isSettledAssistant(message)) {
throw new SessionInvariantError(`Run finish references invalid assistant ${finalId}`);
}
finalEntry = { id: finalId, message };
}
const final =
finalEntry === undefined ? {} : { finalEntryId: finalEntry.id, finalMessage: finalEntry.message };
const outcome: TerminalOperationOutcome = {
operation: "run",
runId: drive.operationId,
kind: "completed",
leafId: state.leafId,
...final,
};
const lastResult: LaneLastResult = {
operationId: drive.operationId,
kind: "run",
outcome: "completed",
leafId: state.leafId,
runCompletion: finalId === null ? "terminated_tools" : "assistant",
...(finalId === null ? {} : { finalAssistantEntryId: finalId }),
};
const cleanup = await operationCleanupWrites(reader, drive.operationId, current, drive.context);
return {
kind: "commit",
writes: [
...cleanup,
setValue(laneLastResultValue(lane.name), lastResult),
setValue(laneStateValue(lane.name), {
currentOperationId: null,
pendingNextRun: state.pendingNextRun,
}),
],
next: { ...state, lastResult, operation: null },
materialize: () => ({ kind: "settled", outcome }) as const,
events: () => [
{
type: "run_end",
lane: lane.name,
runId: drive.operationId,
leafId: state.leafId,
outcome: "completed",
...final,
},
],
};
},
drive.context,
);
}
/** Advance one durable run checkpoint by one visible transition. */
export async function runCheckpoint<TContext extends object | undefined>(
lane: Lane<TContext>,
drive: Drive,
run: RunState,
phase: CheckpointPhase,
): Promise<ProcedureResult> {
if (run.phase.kind !== "checkpoint" || !sameCheckpoint(run.phase, phase)) {
throw new SessionInvariantError("runCheckpoint requires its current checkpoint phase");
}
if (run.control.status === "cancel_requested") return { kind: "continue" };
if (phase.skipInboxOnce !== true && run.inbox.writes.length !== 0) {
return applyPendingWrites(lane, drive, phase);
}
if (phase.skipInboxOnce !== true && run.inbox.steer.length !== 0) {
return consumeQueuedMessages(lane, drive, phase, "steer");
}
if (phase.continuation.kind === "need_assistant") {
const config = lane.readConfig();
const retryPolicy = config.retryPolicy.enabled
? { maxAttempts: config.retryPolicy.maxRetries + 1, baseDelayMs: config.retryPolicy.baseDelayMs }
: { maxAttempts: 1, baseDelayMs: config.retryPolicy.baseDelayMs };
const stepId = lane.session.idGenerator.next();
return lane.commandDriveOwned<ProcedureResult>(
drive,
(state) => {
const operation = state.operation;
if (operation === null || operation.state.kind !== "run") {
throw new SessionInvariantError("Generation start reached a non-run operation");
}
const current = operation.state;
if (current.control.status === "cancel_requested") {
return { kind: "return", result: { kind: "continue" } as const };
}
if (
current.phase.kind !== "checkpoint" ||
!sameCheckpoint(current.phase, phase) ||
current.phase.continuation.kind !== "need_assistant"
) {
throw new SessionInvariantError("Generation start lost its checkpoint boundary");
}
if (
current.phase.skipInboxOnce !== true &&
(current.inbox.writes.length !== 0 || current.inbox.steer.length !== 0)
) {
return { kind: "return", result: { kind: "continue" } as const };
}
const nextState: RunState = {
...current,
phase: {
kind: "assistant",
generation: {
status: "ready",
context: {
stepId,
triggerEntryId: current.phase.triggerEntryId,
configuration: state.configuration,
streamOptions: config.streamOptions,
retryPolicy,
overflowRecoveryUsed: current.phase.continuation.overflowRecoveryUsed,
},
nextAttempt: 1,
},
},
};
return {
kind: "commit",
writes: [setValue(operationStateValue(drive.operationId), nextState)],
next: { ...state, operation: { meta: operation.meta, state: nextState } },
materialize: () => ({ kind: "continue" }) as const,
};
},
drive.context,
);
}
if (run.inbox.followUp.length !== 0) {
return consumeQueuedMessages(lane, drive, phase, "followUp");
}
return finishRun(lane, drive, phase);
}

View file

@ -0,0 +1,678 @@
import {
type AssistantMessage,
type AssistantMessageEvent,
assistantMessageEventToFrame,
isRetryableAssistantError,
type Tool,
} from "@earendil-works/pi-ai";
import type { AgentMessage } from "../../../types.ts";
import type { HarnessEvent } from "../../agent-harness.ts";
import { type Context, withAbortSignal } from "../../context.ts";
import { streamHarnessAssistant } from "../../execution/assistant.ts";
import { applyStreamOptionsPatch } from "../../hooks.ts";
import { insertEntry, insertUsage } from "../../session/commit.ts";
import { buildSessionContext } from "../../session/context.ts";
import { SessionInvariantError } from "../../session/session.ts";
import type {
Entry,
Generation,
GenerationContext,
JsonValue,
MessageEntry,
NewEntry,
OperationError,
RunState,
SettledAssistantMessage,
ToolCall,
UsageRow,
} from "../../session/types.ts";
import {
deleteList,
laneLeaf,
operationState as operationStateValue,
pendingAssistantFrames,
setValue,
} from "../../session/values.ts";
import type { AgentHarnessStreamOptions } from "../../types.ts";
import type { Lane } from "../lane.ts";
import { openFrameProgress } from "../progress.ts";
import type { Drive, LaneCommand, LostOwnership, ProcedureResult } from "../types.ts";
type LocalResult = { kind: "committed" } | { kind: "state_changed" } | LostOwnership;
type ReadResult<T> = { kind: "value"; value: T } | { kind: "state_changed" } | LostOwnership;
const ZERO_USAGE = {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
};
function sameReadyGeneration(current: Generation, expected: Extract<Generation, { status: "ready" }>): boolean {
return (
current.status === "ready" &&
current.context.stepId === expected.context.stepId &&
current.nextAttempt === expected.nextAttempt
);
}
function uuidV7Timestamp(id: string): number {
const timestamp = Number.parseInt(id.slice(0, 8) + id.slice(9, 13), 16);
if (!Number.isSafeInteger(timestamp)) throw new SessionInvariantError(`Invalid reserved UUIDv7 ${id}`);
return timestamp;
}
function configurationError(
code: "model_unavailable" | "configured_tools_unavailable",
details: JsonValue,
): OperationError {
return {
code,
message:
code === "model_unavailable"
? "The configured model is unavailable in this process"
: "One or more configured tools are unavailable in this process",
details,
};
}
function providerError(message: SettledAssistantMessage): OperationError {
return {
code: "assistant_error",
message: message.errorMessage ?? `Assistant request ended with ${message.stopReason}`,
};
}
function normalizeError(message: SettledAssistantMessage, errorMessage: string): SettledAssistantMessage {
return { ...message, stopReason: "error", errorMessage };
}
function normalizeAborted(message: SettledAssistantMessage): SettledAssistantMessage {
return {
...message,
stopReason: "aborted",
errorMessage: message.errorMessage ?? "Assistant request was cancelled",
};
}
function retryDelay(baseDelayMs: number, attempt: number): number {
const delay = baseDelayMs * 2 ** Math.max(0, attempt - 1);
return Number.isSafeInteger(delay) ? delay : Number.MAX_SAFE_INTEGER;
}
function saturatingAdd(left: number, right: number): number {
const sum = left + right;
return Number.isSafeInteger(sum) ? sum : Number.MAX_SAFE_INTEGER;
}
function deferredHandleIsValid(
message: SettledAssistantMessage,
identity: GenerationContext["configuration"]["model"],
): boolean {
const handle = message.deferred;
return (
message.stopReason === "deferred" &&
handle !== undefined &&
handle.id.length !== 0 &&
handle.provider === identity.provider &&
handle.modelId === identity.modelId &&
handle.api === message.api
);
}
async function resolveSystemPrompt<TContext extends object | undefined>(
lane: Lane<TContext>,
context: Context,
): Promise<string> {
const config = lane.readConfig();
if (config.systemPrompt === undefined) return "";
if (typeof config.systemPrompt === "string") return config.systemPrompt;
const source = config.toolContext;
const toolContext = typeof source === "function" ? await source(context) : source;
return config.systemPrompt(toolContext as TContext, context);
}
async function readRequestMessages<TContext extends object | undefined>(
lane: Lane<TContext>,
drive: Drive,
generation: Extract<Generation, { status: "ready" }>,
): Promise<ReadResult<AgentMessage[]>> {
const path = await lane.commandDriveOwned<ReadResult<Entry[]>>(
drive,
async (state, reader): Promise<LaneCommand<ReadResult<Entry[]>>> => {
const operation = state.operation;
if (operation === null || operation.state.kind !== "run") {
throw new SessionInvariantError("Generation context reached a non-run operation");
}
if (operation.state.control.status === "cancel_requested") {
return { kind: "return", result: { kind: "state_changed" } as const };
}
if (
operation.state.phase.kind !== "assistant" ||
!sameReadyGeneration(operation.state.phase.generation, generation)
) {
throw new SessionInvariantError("Generation context lost its ready boundary");
}
if (state.leafId === null) throw new SessionInvariantError("Assistant generation has no branch leaf");
const entries = (
await reader.scanBranch(
{ start: state.leafId, stopAtType: "compaction", order: "newestFirst" },
drive.context,
)
).reverse();
return { kind: "return", result: { kind: "value", value: entries } as const };
},
drive.context,
);
if (path.kind !== "value") return path;
return {
kind: "value",
value: await buildSessionContext(
path.value,
{ entryProjectors: lane.readConfig().entryProjectors },
drive.context,
),
};
}
async function commitGenerationIntent<TContext extends object | undefined>(
lane: Lane<TContext>,
drive: Drive,
expected: Extract<Generation, { status: "ready" }>,
generation: Extract<Generation, { status: "effect_pending" }>,
): Promise<LocalResult> {
return lane.commandDriveOwned<LocalResult>(
drive,
(state) => {
const operation = state.operation;
if (operation === null || operation.state.kind !== "run") {
throw new SessionInvariantError("Generation intent reached a non-run operation");
}
const run = operation.state;
if (run.control.status === "cancel_requested") {
return { kind: "return", result: { kind: "state_changed" } as const };
}
if (run.phase.kind !== "assistant" || !sameReadyGeneration(run.phase.generation, expected)) {
throw new SessionInvariantError("Generation intent lost its ready boundary");
}
const nextState: RunState = { ...run, phase: { kind: "assistant", generation } };
return {
kind: "commit",
writes: [setValue(operationStateValue(drive.operationId), nextState)],
next: { ...state, operation: { meta: operation.meta, state: nextState } },
materialize: () => ({ kind: "committed" }) as const,
events: () =>
expected.nextAttempt === 1
? [
{
type: "turn_start",
lane: lane.name,
runId: drive.operationId,
turnId: expected.context.stepId,
},
]
: [],
};
},
drive.context,
);
}
/** Commit a non-retryable request-configuration failure before reserving response ids. */
export function enterConfigurationFailure<TContext extends object | undefined>(
lane: Lane<TContext>,
drive: Drive,
expected: Extract<Generation, { status: "ready" }>,
error: OperationError,
): Promise<ProcedureResult> {
return lane.commandDriveOwned<ProcedureResult>(
drive,
(state) => {
const operation = state.operation;
if (operation === null || operation.state.kind !== "run") {
throw new SessionInvariantError("Configuration failure reached a non-run operation");
}
const run = operation.state;
if (run.control.status === "cancel_requested") {
return { kind: "return", result: { kind: "continue" } as const };
}
if (run.phase.kind !== "assistant" || !sameReadyGeneration(run.phase.generation, expected)) {
throw new SessionInvariantError("Configuration failure lost its ready boundary");
}
const nextState: RunState = {
...run,
phase: { kind: "failure_drain", error, provenance: { kind: "configuration" } },
};
return {
kind: "commit",
writes: [setValue(operationStateValue(drive.operationId), nextState)],
next: { ...state, operation: { meta: operation.meta, state: nextState } },
materialize: () => ({ kind: "continue" }) as const,
};
},
drive.context,
);
}
interface SettlementIdentity {
responseEntryId: string;
usageId: string;
stepId: string;
}
/** Classify and atomically settle one complete assistant response under its reserved ids. */
export function settleAssistant<TContext extends object | undefined>(
lane: Lane<TContext>,
drive: Drive,
identity: SettlementIdentity,
response: SettledAssistantMessage,
options: { recovery?: true } = {},
): Promise<ProcedureResult> {
return lane.commandDriveOwned<ProcedureResult>(
drive,
(state) => {
const operation = state.operation;
if (operation === null || operation.state.kind !== "run") {
throw new SessionInvariantError("Assistant settlement reached a non-run operation");
}
const run = operation.state;
if (run.phase.kind !== "assistant" || run.phase.generation.status !== "effect_pending") {
throw new SessionInvariantError("Assistant settlement lost its effect-pending boundary");
}
const generation = run.phase.generation;
if (
generation.context.stepId !== identity.stepId ||
generation.responseEntryId !== identity.responseEntryId ||
generation.usageId !== identity.usageId
) {
throw new SessionInvariantError("Assistant settlement ids contradict the durable request intent");
}
const modelIdentity = generation.context.configuration.model;
let committed = response;
let nextPhase: RunState["phase"];
if (run.control.status === "cancel_requested") {
committed = normalizeAborted(response);
nextPhase = {
kind: "checkpoint",
continuation: { kind: "may_finish", includeFinalAssistant: true },
triggerEntryId: identity.responseEntryId,
};
} else if (response.stopReason === "aborted") {
throw new SessionInvariantError("Assistant response is aborted while durable control is running");
} else if (response.stopReason === "deferred") {
if (deferredHandleIsValid(response, modelIdentity)) {
nextPhase = {
kind: "deferred",
deferred: {
status: "suspended",
stepId: generation.context.stepId,
sourceEntryId: identity.responseEntryId,
poll: 0,
configuration: generation.context.configuration,
streamOptions: generation.context.streamOptions,
},
};
} else {
committed = normalizeError(response, "Provider returned an invalid deferred handle");
nextPhase = {
kind: "failure_drain",
error: providerError(committed),
provenance: { kind: "response", entryId: identity.responseEntryId },
};
}
} else if (
response.stopReason === "error" &&
(options.recovery === true || isRetryableAssistantError(response)) &&
generation.attempt < generation.context.retryPolicy.maxAttempts
) {
const delayMs = retryDelay(generation.context.retryPolicy.baseDelayMs, generation.attempt);
nextPhase = {
kind: "assistant",
generation: {
status: "retry_wait",
context: generation.context,
nextAttempt: generation.attempt + 1,
notBefore: saturatingAdd(Date.now(), delayMs),
errorMessage: response.errorMessage ?? "Assistant request failed",
},
};
} else if (response.stopReason === "error") {
nextPhase = {
kind: "failure_drain",
error: providerError(response),
provenance: { kind: "response", entryId: identity.responseEntryId },
};
} else {
const calls = response.content.flatMap((content, sourceIndex) =>
content.type === "toolCall" ? [{ content, sourceIndex }] : [],
);
if (calls.length !== 0) {
const timestamp = uuidV7Timestamp(identity.responseEntryId);
const planned: ToolCall[] = calls.map(({ sourceIndex }) => ({
status: "planned",
sourceIndex,
resultEntryId: lane.session.idGenerator.next(timestamp),
}));
nextPhase = {
kind: "tools",
batch: {
assistantEntryId: identity.responseEntryId,
configuration: generation.context.configuration,
turnId: generation.context.stepId,
calls: planned,
},
};
} else if (response.stopReason === "toolUse") {
committed = normalizeError(response, "Provider reported tool use without any tool calls");
nextPhase = {
kind: "failure_drain",
error: providerError(committed),
provenance: { kind: "response", entryId: identity.responseEntryId },
};
} else {
nextPhase = {
kind: "checkpoint",
continuation: { kind: "may_finish", includeFinalAssistant: true },
triggerEntryId: identity.responseEntryId,
};
}
}
const nextState: RunState = {
...run,
phase: nextPhase,
latestAssistantEntryId: identity.responseEntryId,
};
const responseEntry: NewEntry<MessageEntry> = {
id: identity.responseEntryId,
parentId: state.leafId,
type: "message",
message: committed,
};
const usageRow: Omit<UsageRow, "seq"> = {
id: identity.usageId,
usage: committed.usage,
entryId: identity.responseEntryId,
adjustment: false,
};
return {
kind: "commit",
writes: [
insertEntry(responseEntry),
insertUsage(usageRow),
setValue(laneLeaf(lane.name), identity.responseEntryId),
deleteList(pendingAssistantFrames(drive.operationId, identity.responseEntryId)),
setValue(operationStateValue(drive.operationId), nextState),
],
next: {
...state,
leafId: identity.responseEntryId,
operation: { meta: operation.meta, state: nextState },
},
materialize: () => ({ kind: "continue" }) as const,
events: (commit) => {
const entry: MessageEntry = {
...responseEntry,
seq: commit.seqs[0]!,
timestamp: commit.timestamp,
};
const events: HarnessEvent[] = [
{ type: "entry_added", lane: lane.name, entry, ...options },
{
type: "usage",
lane: lane.name,
row: { ...usageRow, seq: commit.seqs[1]! },
totals: commit.stats.usage,
},
];
if (
options.recovery !== true &&
nextPhase.kind === "assistant" &&
nextPhase.generation.status === "retry_wait"
) {
events.push({
type: "retry_scheduled",
lane: lane.name,
runId: drive.operationId,
step: identity.stepId,
attempt: nextPhase.generation.nextAttempt,
maxAttempts: nextPhase.generation.context.retryPolicy.maxAttempts,
delayMs: retryDelay(generation.context.retryPolicy.baseDelayMs, generation.attempt),
errorMessage: nextPhase.generation.errorMessage,
});
}
if (
options.recovery !== true &&
nextPhase.kind !== "tools" &&
!(nextPhase.kind === "assistant" && nextPhase.generation.status === "retry_wait")
) {
events.push({
type: "turn_end",
lane: lane.name,
runId: drive.operationId,
turnId: identity.stepId,
message: committed,
toolResults: [],
...options,
});
}
return events;
},
};
},
drive.context,
);
}
/** Execute one ready assistant generation through intent, admitted stream, and settlement. */
export async function runGeneration<TContext extends object | undefined>(
lane: Lane<TContext>,
drive: Drive,
run: RunState,
generation: Generation,
): Promise<ProcedureResult> {
if (generation.status !== "ready") {
throw new SessionInvariantError("M3 runGeneration requires a ready generation");
}
if (run.phase.kind !== "assistant" || !sameReadyGeneration(run.phase.generation, generation)) {
throw new SessionInvariantError("runGeneration requires its current ready phase");
}
if (run.control.status === "cancel_requested") return { kind: "continue" };
const identity = generation.context.configuration.model;
const model = lane.models.getModel(identity.provider, identity.modelId);
if (model === undefined) {
return enterConfigurationFailure(lane, drive, generation, configurationError("model_unavailable", identity));
}
const config = lane.readConfig();
const toolsByName = new Map(config.tools.map((tool) => [tool.name, tool]));
const missingTools = generation.context.configuration.activeToolNames.filter((name) => !toolsByName.has(name));
if (missingTools.length !== 0) {
return enterConfigurationFailure(
lane,
drive,
generation,
configurationError("configured_tools_unavailable", { tools: missingTools }),
);
}
const tools: Tool[] = generation.context.configuration.activeToolNames.map((name) => {
const tool = toolsByName.get(name);
if (tool === undefined) throw new SessionInvariantError(`Configured tool ${name} disappeared during resolution`);
return {
name: tool.name,
description: tool.description,
parameters: tool.parameters,
...(tool.constrainedSampling === undefined ? {} : { constrainedSampling: tool.constrainedSampling }),
};
});
const messages = await readRequestMessages(lane, drive, generation);
if (messages.kind === "lost_ownership") return messages;
if (messages.kind === "state_changed") return { kind: "continue" };
const systemPrompt = await resolveSystemPrompt(lane, drive.context);
const beforeRequest = await lane.hooks.runWithGate(
"before_request",
{
lane: lane.name,
runId: drive.operationId,
model,
step: "assistant",
attempt: generation.nextAttempt,
streamOptions: generation.context.streamOptions,
},
drive.gate,
drive.context,
);
const streamOptions: AgentHarnessStreamOptions =
beforeRequest?.streamOptions === undefined
? generation.context.streamOptions
: applyStreamOptionsPatch(generation.context.streamOptions, beforeRequest.streamOptions);
const at = Date.now();
const responseEntryId = lane.session.idGenerator.next(at);
const usageId = lane.session.idGenerator.next(at);
const effectPending: Extract<Generation, { status: "effect_pending" }> = {
status: "effect_pending",
context: generation.context,
attempt: generation.nextAttempt,
responseEntryId,
usageId,
intendedOutputLimit: model.maxTokens,
contextWindow: model.contextWindow,
};
const intent = await commitGenerationIntent(lane, drive, generation, effectPending);
if (intent.kind === "lost_ownership") return intent;
if (intent.kind === "state_changed") return { kind: "continue" };
const progress = openFrameProgress(lane, drive, responseEntryId);
let drained = false;
const drainProgress = async () => {
if (drained) return;
progress.seal();
await progress.drain();
drained = true;
};
let response: SettledAssistantMessage;
try {
response = await streamHarnessAssistant(
messages.value,
{
model,
systemPrompt,
tools,
thinkingLevel: generation.context.configuration.thinkingLevel,
streamOptions,
transformContext: async (requestContext, context) => {
const result = await lane.hooks.runWithGate(
"transform_context",
{ lane: lane.name, runId: drive.operationId, ...requestContext },
drive.gate,
context,
);
return {
messages: result?.messages ?? requestContext.messages,
systemPrompt: result?.systemPrompt ?? requestContext.systemPrompt,
};
},
toProviderMessages: config.toProviderMessages,
beforePayload: async (payload, requestModel, context) => {
const result = await lane.hooks.runWithGate(
"before_payload",
{ lane: lane.name, runId: drive.operationId, model: requestModel, payload },
drive.gate,
context,
);
return result?.payload;
},
afterResponse: async (message, metadata, context) => {
await drainProgress();
const result = await lane.hooks.runWithGate(
"after_response",
{ lane: lane.name, runId: drive.operationId, ...metadata, message },
drive.gate,
context,
);
return result?.message ?? message;
},
request: (aiContext, options, context) => {
const admitted = withAbortSignal(drive.gate.signal, context);
return drive.gate.admit(() =>
lane.models.streamSimple(model, aiContext, {
...options,
signal: admitted.abortSignal,
telemetryContext: admitted.telemetryContext,
}),
);
},
observer: {
start(message, event, context) {
const frame = event === undefined ? undefined : assistantMessageEventToFrame(event);
if (frame !== undefined) progress.write(frame);
return lane.emitBatch(
[{ type: "message_start", lane: lane.name, runId: drive.operationId, message }],
context,
);
},
update(message, event: AssistantMessageEvent, context) {
const frame = assistantMessageEventToFrame(event);
if (frame !== undefined) progress.write(frame);
return lane.emitBatch(
[
{
type: "message_update",
lane: lane.name,
runId: drive.operationId,
message,
event,
},
],
context,
);
},
end(message, context) {
return lane.emitBatch(
[
{
type: "message_end",
lane: lane.name,
runId: drive.operationId,
message,
entryId: responseEntryId,
},
],
context,
);
},
},
},
drive.context,
);
} finally {
await drainProgress();
}
return settleAssistant(lane, drive, { responseEntryId, usageId, stepId: generation.context.stepId }, response);
}
export function interruptedAssistantMessage(
generation: Extract<Generation, { status: "effect_pending" }>,
partial: AssistantMessage | undefined,
): SettledAssistantMessage {
const warning =
"Assistant request was interrupted. The preceding content is the latest committed partial; newer live output may be missing and the external outcome is unknown.";
return partial === undefined
? {
role: "assistant",
content: [],
api: "unknown",
provider: generation.context.configuration.model.provider,
model: generation.context.configuration.model.modelId,
usage: ZERO_USAGE,
stopReason: "error",
errorMessage: warning,
timestamp: Date.now(),
}
: { ...partial, usage: ZERO_USAGE, stopReason: "error", errorMessage: warning };
}

View file

@ -0,0 +1,98 @@
import { type AssistantMessageFrame, reduceAssistantMessageFrames } from "@earendil-works/pi-ai";
import { SessionInvariantError } from "../../session/session.ts";
import type { Generation, RunState } from "../../session/types.ts";
import { pendingAssistantFrames } from "../../session/values.ts";
import type { Lane } from "../lane.ts";
import type { Drive, ProcedureResult } from "../types.ts";
import { interruptedAssistantMessage, settleAssistant } from "./generation.ts";
/** Settle an orphaned assistant request from its bounded committed frame prefix without another provider call. */
export async function recoverAssistantGeneration<TContext extends object | undefined>(
lane: Lane<TContext>,
drive: Drive,
run: RunState,
generation: Extract<Generation, { status: "effect_pending" }>,
): Promise<ProcedureResult> {
if (
run.phase.kind !== "assistant" ||
run.phase.generation.status !== "effect_pending" ||
run.phase.generation.responseEntryId !== generation.responseEntryId
) {
throw new SessionInvariantError("Assistant recovery requires its current effect-pending generation");
}
const read = await lane.commandDriveOwned<{ kind: "frames"; frames: AssistantMessageFrame[] }>(
drive,
async (state, reader) => {
const operation = state.operation;
if (
operation === null ||
operation.state.kind !== "run" ||
operation.state.phase.kind !== "assistant" ||
operation.state.phase.generation.status !== "effect_pending" ||
operation.state.phase.generation.responseEntryId !== generation.responseEntryId
) {
throw new SessionInvariantError("Assistant recovery lost its effect-pending generation");
}
const frames: AssistantMessageFrame[] = [];
let cursor: { seq: number } | undefined;
for (;;) {
const page = await reader.readList(
pendingAssistantFrames(drive.operationId, generation.responseEntryId),
{ order: "asc", limit: 1_000, ...(cursor === undefined ? {} : { cursor }) },
drive.context,
);
frames.push(...page.map(({ value }) => value));
if (page.length < 1_000) break;
cursor = { seq: page[page.length - 1]!.seq };
}
return { kind: "return", result: { kind: "frames", frames } as const };
},
drive.context,
);
if (read.kind === "lost_ownership") return read;
const frames = read.frames;
const current = lane.state.operation;
if (current?.meta.operationId !== drive.operationId || !lane.isDriveActive(drive)) {
return { kind: "lost_ownership" };
}
if (
current.state.kind !== "run" ||
current.state.phase.kind !== "assistant" ||
current.state.phase.generation.status !== "effect_pending" ||
current.state.phase.generation.responseEntryId !== generation.responseEntryId
) {
throw new SessionInvariantError("Assistant recovery lost its effect-pending generation");
}
const message = interruptedAssistantMessage(generation, reduceAssistantMessageFrames(frames));
await lane.emitBatch(
[
{
type: "message_start",
lane: lane.name,
runId: drive.operationId,
message,
recovery: true,
},
{
type: "message_end",
lane: lane.name,
runId: drive.operationId,
message,
entryId: generation.responseEntryId,
recovery: true,
},
],
drive.context,
);
return settleAssistant(
lane,
drive,
{
responseEntryId: generation.responseEntryId,
usageId: generation.usageId,
stepId: generation.context.stepId,
},
message,
{ recovery: true },
);
}

View file

@ -86,6 +86,7 @@ describe("streamHarnessAssistant", () => {
let receivedContext: { systemPrompt?: string; messages: Message[]; tools?: unknown[] } | undefined;
let receivedOptions: SimpleStreamOptions | undefined;
let responseMetadata: AssistantResponseMetadata | undefined;
let startEventType: string | undefined;
const result = await streamHarnessAssistant(
input,
@ -144,9 +145,10 @@ describe("streamHarnessAssistant", () => {
return stream;
},
observer: {
start(message) {
start(message, event) {
order.push("observer_start");
starts.push(message);
startEventType = event?.type;
},
update(message) {
order.push("observer_update");
@ -184,6 +186,7 @@ describe("streamHarnessAssistant", () => {
});
expect(responseMetadata).toEqual({ status: 201, headers: { "request-id": "r1" } });
expect(starts).toHaveLength(1);
expect(startEventType).toBe("start");
expect(starts[0]).not.toBe(updates[0]);
expect(updates).toHaveLength(1);
expect(ends[0]).toBe(result);
@ -244,6 +247,7 @@ describe("streamHarnessAssistant", () => {
const events: string[] = [];
const final = assistant("complete");
let options: SimpleStreamOptions | undefined;
let synthesizedStartEvent: unknown = "unset";
const result = await streamHarnessAssistant(
[user("prompt")],
{
@ -259,8 +263,9 @@ describe("streamHarnessAssistant", () => {
return stream;
},
observer: {
start(message) {
start(message, event) {
events.push(`start:${message.stopReason}`);
synthesizedStartEvent = event;
},
update() {
events.push("update");
@ -275,6 +280,7 @@ describe("streamHarnessAssistant", () => {
expect(result).toBe(final);
expect(events).toEqual(["start:stop", "end:stop"]);
expect(synthesizedStartEvent).toBeUndefined();
expect(options).not.toHaveProperty("reasoning");
});

View file

@ -0,0 +1,703 @@
import {
type AssistantMessage,
createAssistantMessageEventStream,
createModels,
fauxAssistantMessage,
fauxProvider,
type MutableModels,
type Provider,
} from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it } 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/runtime2/drive/checkpoint.ts";
import { runGeneration } from "../../../src/harness/runtime2/drive/generation.ts";
import { recoverAssistantGeneration } from "../../../src/harness/runtime2/drive/recovery.ts";
import { Lane } from "../../../src/harness/runtime2/lane.ts";
import { restoreLane } from "../../../src/harness/runtime2/restore.ts";
import { type Config, Drive } from "../../../src/harness/runtime2/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 {
Generation,
LaneConfiguration,
MessageEntry,
RunState,
Session,
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(
"main",
(mutator) =>
mutator.commit(
[
storedValues.setValue(storedValues.laneLeaf("main"), null),
storedValues.setValue(storedValues.laneConfig("main"), configuration),
storedValues.setValue(storedValues.laneState("main"), {
currentOperationId: null,
pendingNextRun: [],
}),
],
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>): RunState {
const operation = lane.state.operation;
if (operation?.state.kind !== "run") throw new Error("fixture has no run");
return operation.state;
}
function readyGeneration(lane: Lane<undefined>): Extract<Generation, { status: "ready" }> {
const run = currentRun(lane);
if (run.phase.kind !== "assistant" || run.phase.generation.status !== "ready") {
throw new Error("fixture has no ready generation");
}
return run.phase.generation;
}
async function advanceToReady(fixture: Fixture): Promise<Extract<Generation, { status: "ready" }>> {
let run = currentRun(fixture.lane);
await startRun(fixture.lane, fixture.drive, run);
run = currentRun(fixture.lane);
if (run.phase.kind !== "checkpoint") throw new Error("run did not reach checkpoint");
await runCheckpoint(fixture.lane, fixture.drive, run, run.phase);
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 &&
"phase" in write.value &&
typeof write.value.phase === "object" &&
write.value.phase !== null &&
"kind" in write.value.phase &&
write.value.phase.kind === "assistant" &&
"generation" in write.value.phase &&
typeof write.value.phase.generation === "object" &&
write.value.phase.generation !== null &&
"status" in write.value.phase.generation &&
write.value.phase.generation.status === status,
);
}
afterEach(async () => {
for (const session of sessions.splice(0)) await session.close(BACKGROUND_CONTEXT);
});
describe("runtime2 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 }],
}));
const starting = currentRun(fixture.lane);
expect(await startRun(fixture.lane, fixture.drive, starting)).toEqual({ kind: "continue" });
let run = currentRun(fixture.lane);
if (run.phase.kind !== "checkpoint") throw new Error("missing checkpoint");
expect(run.phase.triggerEntryId).toBe(fixture.lane.state.leafId);
expect(fixture.events.map((event) => event.type)).toContain("entry_added");
await expectProjectionRestores(fixture);
expect(await runCheckpoint(fixture.lane, fixture.drive, run, run.phase)).toEqual({ kind: "continue" });
run = currentRun(fixture.lane);
if (run.phase.kind !== "assistant" || run.phase.generation.status !== "ready") {
throw new Error("missing ready generation");
}
expect(run.phase.generation.context).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 startRun(fixture.lane, fixture.drive, currentRun(fixture.lane));
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?.state.kind !== "run") throw new Error("missing run");
const nextState: RunState = {
...operation.state,
inbox: { ...operation.state.inbox, writes: [messageId, customId] },
};
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.operationState(operationId), nextState),
],
next: { ...state, operation: { meta: operation.meta, state: nextState } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
const run = currentRun(fixture.lane);
if (run.phase.kind !== "checkpoint") throw new Error("missing checkpoint");
await runCheckpoint(fixture.lane, fixture.drive, run, run.phase);
const drained = currentRun(fixture.lane);
if (drained.phase.kind !== "checkpoint") throw new Error("missing drained checkpoint");
expect(fixture.lane.state.leafId).toBe(customId);
expect(drained.phase.triggerEntryId).toBe(messageId);
expect(drained.phase).toMatchObject({
continuation: { kind: "need_assistant", overflowRecoveryUsed: false },
skipInboxOnce: true,
});
expect(drained.inbox.writes).toEqual([]);
await expectProjectionRestores(fixture);
});
});
describe("runtime2 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.kind === "run" &&
state.value.phase.kind === "assistant" &&
state.value.phase.generation.status === "effect_pending";
return fauxAssistantMessage("answer", { timestamp: 5 });
},
]);
fixture.storage.clearCommitAttempts();
const generating = runGeneration(fixture.lane, fixture.drive, currentRun(fixture.lane), ready);
await hookStarted.promise;
const steerId = "01950000-0000-7000-8000-000000000020";
await fixture.lane.command((state) => {
const operation = state.operation;
if (operation?.state.kind !== "run") throw new Error("missing run");
const nextState: RunState = {
...operation.state,
inbox: { ...operation.state.inbox, steer: [steerId] },
};
return {
kind: "commit",
writes: [
storedValues.setValue(storedValues.pendingEntry(steerId), {
type: "message",
payload: { role: "user", content: "late steer", timestamp: 4 },
}),
storedValues.setValue(storedValues.operationState(operationId), nextState),
],
next: { ...state, operation: { meta: operation.meta, state: nextState } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
releaseHook.resolve();
expect(await generating).toEqual({ kind: "continue" });
expect(effectPendingAtProvider).toBe(true);
const settled = currentRun(fixture.lane);
expect(settled.inbox.steer).toEqual([steerId]);
if (settled.phase.kind !== "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.lane.leaf",
"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: "" };
void (async () => {
stream.push({ type: "start", partial });
await backend.frameStarted.promise;
partial.content.push(text);
stream.push({ type: "text_start", contentIndex: 0, partial });
await Promise.resolve();
text.text = "ab";
stream.push({ type: "text_delta", contentIndex: 0, delta: "ab", partial });
await Promise.resolve();
stream.push({ type: "text_end", contentIndex: 0, content: "ab", partial });
await Promise.resolve();
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, currentRun(fixture.lane), 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_delta", "text_end"]);
expect(frames[0]).toMatchObject({ partial: { content: [] } });
expect(frames[2]).toMatchObject({ delta: "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);
fixture.storage.clearCommitAttempts();
expect(await runGeneration(fixture.lane, fixture.drive, currentRun(fixture.lane), ready)).toEqual({
kind: "continue",
});
const run = currentRun(fixture.lane);
expect(run.phase).toMatchObject({
kind: "failure_drain",
error: { code: "configured_tools_unavailable", details: { tools: ["missing"] } },
provenance: { kind: "configuration" },
});
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, currentRun(fixture.lane), ready)).toEqual({
kind: "continue",
});
expect(currentRun(fixture.lane).phase).toMatchObject({
kind: "failure_drain",
error: { code: "model_unavailable" },
provenance: { kind: "configuration" },
});
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 or exact-owner replacement 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, currentRun(cancelled.lane), cancelledReady);
await cancelHookStarted.promise;
await cancelled.lane.command((state) => {
const operation = state.operation;
if (operation?.state.kind !== "run") throw new Error("missing run");
const nextState: RunState = {
...operation.state,
control: { status: "cancel_requested", requestedAt: 10, drainedSteer: [], drainedFollowUp: [] },
};
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);
const replaced = await createFixture();
const replacedReady = await advanceToReady(replaced);
const ownerHookStarted = deferred();
const releaseOwnerHook = deferred();
replaced.hooks.on("before_request", async () => {
ownerHookStarted.resolve();
await releaseOwnerHook.promise;
return undefined;
});
replaced.storage.clearCommitAttempts();
const losing = runGeneration(replaced.lane, replaced.drive, currentRun(replaced.lane), replacedReady);
await ownerHookStarted.promise;
replaced.lane.activeDrive = new Drive({ operationId }, BACKGROUND_CONTEXT);
releaseOwnerHook.resolve();
expect(await losing).toEqual({ kind: "lost_ownership" });
expect(replaced.faux.state.callCount).toBe(0);
expect(replaced.storage.getCommitAttempts()).toEqual([]);
});
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: Extract<Generation, { status: "effect_pending" }> = {
status: "effect_pending",
context: { ...ready.context, 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?.state.kind !== "run") throw new Error("missing run");
const nextState: RunState = {
...operation.state,
phase: { kind: "assistant", generation: pending },
};
return {
kind: "commit",
writes: [storedValues.setValue(storedValues.operationState(operationId), nextState)],
next: { ...state, operation: { meta: operation.meta, state: nextState } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
await recoverAssistantGeneration(fixture.lane, fixture.drive, currentRun(fixture.lane), pending);
const entry = (await fixture.session.getEntry(responseEntryId, BACKGROUND_CONTEXT)) as MessageEntry;
expect(entry.message).toMatchObject({
api: "unknown",
provider: ready.context.configuration.model.provider,
model: ready.context.configuration.model.modelId,
content: [],
stopReason: "error",
usage: { totalTokens: 0 },
});
expect(currentRun(fixture.lane).phase).toMatchObject({
kind: "assistant",
generation: { status: "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: Extract<Generation, { status: "effect_pending" }> = {
status: "effect_pending",
context: { ...ready.context, 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?.state.kind !== "run") throw new Error("missing run");
const nextState: RunState = {
...operation.state,
phase: { kind: "assistant", generation: pending },
};
return {
kind: "commit",
writes: [
storedValues.setValue(storedValues.operationState(operationId), nextState),
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: nextState } },
materialize: () => undefined,
};
}, BACKGROUND_CONTEXT);
let afterResponseCalls = 0;
fixture.hooks.on("after_response", () => {
afterResponseCalls++;
return undefined;
});
expect(await recoverAssistantGeneration(fixture.lane, fixture.drive, currentRun(fixture.lane), pending)).toEqual({
kind: "continue",
});
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(currentRun(fixture.lane).phase).toMatchObject({ kind: "failure_drain" });
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 a hydratable final assistant", async () => {
const fixture = await createFixture();
const ready = await advanceToReady(fixture);
fixture.faux.setResponses([fauxAssistantMessage("done", { timestamp: 7 })]);
await runGeneration(fixture.lane, fixture.drive, currentRun(fixture.lane), ready);
const run = currentRun(fixture.lane);
if (run.phase.kind !== "checkpoint") throw new Error("missing terminal checkpoint");
const result = await runCheckpoint(fixture.lane, fixture.drive, run, run.phase);
expect(result).toMatchObject({
kind: "settled",
outcome: { operation: "run", runId: operationId, kind: "completed", finalMessage: { role: "assistant" } },
});
expect(fixture.lane.state.operation).toBeNull();
expect(fixture.lane.state.lastResult).toMatchObject({
operationId,
kind: "run",
outcome: "completed",
runCompletion: "assistant",
});
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);
});
});