packages/agent/docs/work-packages/02-atomic-run-acceptance.md
Complete. Phase A established minimal attachment, open-operation inventory, Session-line inspection/watch capture, commit-continuation recipient binding, and in-band identity-failure vocabulary. Phase B landed in beac75ecc with focused, monorepo-check, full-suite, and final Fable review passing.
Implementation also updated downstream protocol/coding-agent wire projections required by the changed public contract. No execution owner, provider/tool effect, timer, retry, polling, cancellation procedure, manual action, or terminal settlement was added.
Deliver two effect-free boundaries:
idle lane
→ atomic prompt/skill/template acceptance
→ durable open operation in payload-free starting
open or running lane
→ Session-line watch capture
→ complete snapshot plus gap-free subsequent events
After AgentHarness.create(options, context) succeeds:
open inventories every durable current operation without predicting model/tool availability;inspectExecution(context) observes the small projection and local owner on the Session line;watch(context) registers buffering, clones live presentation, and performs bounded snapshot reads in one no-write lane job;WP02 does not implement drive, provider generation, hooks, tools, retries, deferred polling, cancellation procedures, manual action execution, or terminal settlement.
Passing the open Session to AgentHarness.create() transfers orchestration ownership until create rejects or the harness closes. Direct Session.mutate, Session.createLane, reserved-address writes, and second-harness construction are prohibited during that interval, so lane inventory cannot race an out-of-band lane creation.
Attachment reads only:
branchTip, laneConfig, laneState, optional laneLastResult;operationMeta and operationState for a current operation.It validates required existence, operation id/lane ownership, and intent/state kind compatibility. Projection corruption faults create().
Attachment does not read transcript, queues, pending writes, drained payloads, deferred sources, frames, tool calls, arguments, checkpoints, preparations, memos, or staged outcomes. Those references are checked by watch() or drive when consumed. Missing or contradictory required payload data is terminal storage corruption and faults that consumer. Optional frame/checkpoint absence is legal.
One no-write Session mutation job defines the watch boundary:
enter after all earlier lane jobs
→ synchronously register buffering watcher
→ synchronously clone live presentation state
→ perform bounded durable reads while later lane jobs are excluded
→ assemble snapshot
→ release Session line
→ return handle
There is no special first-watch cache. The same path handles immediate post-attachment watch, reconnect, and watch during live execution.
The bounded read set is:
pendingEntry(id) reads for next-run, steer, follow-up, writes, and abort drains;effect_pending calls, using batch.turnId as the args step id;ToolCall.sourceIndex is the index in the assistant message's full content array, not a filtered tool-call ordinal. A represented call must index a tool-call block.
A successful committing lane job performs:
commit
→ publish small owned projection
→ synchronously bind recipients and append the complete `{ event, context }` batch
→ return from the mutation without awaiting delivery
→ await delivery before the public operation resolves
WP02 initially implemented this with enqueue() plus caller-operated start(). WP04 replaces that gate with one immediate emitBatch() call in the same commit-observation continuation. Recipient binding and delivery awaiting remain unchanged. A listener or watcher registered after emitBatch() cannot receive that historical event.
The only watcher/publication orders are:
watcher first
→ snapshot-before + complete buffered event batch
publication/`emitBatch` first
→ snapshot-after + no old event
A live provider/tool presentation update follows the same synchronous publish-plus-emitBatch discipline. Frame/checkpoint commits are lane jobs and queue behind watch capture.
inspectExecution(context) observes:
{ provider, modelId } strings;running, open, or durable aborting;It does not resolve model/tool registries and does not read transcript or presentation payloads.
There is no blocked, missing-identity suspension, predictive classifier, or acceptance registry preflight.
At the actual execution boundary:
effect_pending settles uncertainty under its existing reserved ids before any later configuration failure;effect_pending abandonment, including deletion of its exact old assistant-frame list while dropping the reserved response/usage strings without fabricating settlement;isError ToolResultMessage and continues;Synthetic harness tool results omit details. A tool owns the type of its details contract; the harness must not invent {} or a diagnostic object. isError and human-readable content carry the tool-level diagnosis. Run-level configuration failure remains machine-readable through OperationError and laneLastResult.
Stable configuration error codes:
model_unavailable, details { provider, modelId };configured_tools_unavailable, details { tools: string[] }.failure_drain gains { kind: "configuration" } provenance. Actual transitions land with their owning execution packages; WP02 lands the normative/source vocabulary only.
Acceptance validates durable caller input and lane state, not current model/tool registrations. This avoids a time-of-check/time-of-use check and permits acceptance in one process followed by execution in another.
A misconfigured convenience prompt eventually returns a durable failed run from drive. Explicit hosted acceptance remains durable even before an execution worker loads implementations.
Every current public harness/lane operation receives trailing context: Context. Acceptance, attachment, watch capture, Session reads/commit, faults, and event publication preserve it. Shared harness/lane/Session receivers retain no caller Context. Context and its signal/telemetry values are never durable business data.
Buffered events retain the exact emitting Context. Invocation cancellation remains distinct from durable requestAbort().
export interface ModelIdentity {
provider: string;
modelId: string;
}
export type OperationStatus = "running" | "open" | "aborting";
export interface OpenOperation {
lane: string;
operationId: string;
kind: "run" | "compaction" | "navigation";
startedAt: number;
aborting?: true;
}
export interface CurrentOperationInfo {
id: string;
kind: "run" | "compaction" | "navigation";
startedAt: number;
status: OperationStatus;
capturedModel?: ModelIdentity;
}
export interface LaneExecutionInfo {
lane: string;
tipId: string | null;
configuredModel: ModelIdentity;
current: CurrentOperationInfo | null;
lastResult?: LaneLastResult;
}
export interface LaneInfo {
name: string;
tipId: string | null;
operation: CurrentOperationInfo | null;
}
export interface AgentHarnessConstructor {
create<TContext extends object | undefined = object | undefined>(
options: AgentHarnessOptions<TContext>,
context: Context,
): Promise<{ harness: AgentHarness<TContext>; open: OpenOperation[] }>;
}
Rules:
open has exactly one item per durable current operation and omits idle lanes;aborting:true comes only from durable cancel_requested;open is inventory, not scheduling or identity advice;resume(context);drive fencing;Delete MissingIdentitySuspension, MissingIdentities, missing-identity drive waiting, and missing-identity suspension events.
Keep deferred suspension as provider semantics:
{ kind: "suspended"; reason: "deferred"; ... }
WP05 removes the withdrawn action outcome before execution is enabled. Convenience operation outcomes remain operation-tagged branches of ResumeOutcome.
export interface LaneSnapshot {
lane: string;
transcript: Entry[];
tipId: string | null;
lastResult?: LaneLastResult;
operation: null | {
id: string;
kind: "run" | "compaction" | "navigation";
startedAt: number;
status: OperationStatus;
action?: ActionInfo;
retry?: { attempt: number; maxAttempts: number; nextAttemptAt: number };
deferred?: { handle: DeferredHandle; poll: number };
drained?: { steer: QueuedItem[]; followUp: QueuedItem[] };
streamingMessage?: AssistantMessage;
runningTools: {
toolCallId: string;
toolName: string;
args: unknown;
partialResult?: AgentToolResult<unknown>;
}[];
};
queues: { steer: QueuedItem[]; followUp: QueuedItem[]; nextRun: QueuedItem[] };
pendingWrites: {
entryId: string;
type: EntryType;
customType?: string;
message?: AgentMessage;
data?: JsonValue;
}[];
faulted: boolean;
}
Configuration is not duplicated in snapshots. inspectExecution() exposes configured/captured model identities; getters expose current configuration.
WP02 implements accept() for prompt, skill, and prompt-template requests. Compaction/navigation acceptance remains with their execution packages.
State-independent normalization occurs before Lane.command(plan, context):
Public prompt convenience overloads remain exactly [text, images | undefined, context] and [messageOrMessages, context], but convenience implementations remain SliceNotImplemented until R2.
Inside one lane command:
pendingNextRun ids;emitBatch with the acceptance event batch and accepting Context;accept resolves.Exact writes:
insert captured nextRun message entries
insert request prompt entries
delete captured pendingEntry values
set branchTip
set operationMeta
set operationState(run starting)
set laneState(current operation, pendingNextRun=[])
Exact event order:
run_start
for each placed message:
message_start
message_end
entry_added
queue_update if nextRun was captured
Acceptance starts no drive or effect and writes no Context.
Update harness.md before runtime source:
Review stop:
git diff --check;Modify agent-harness.ts:
SuspendedOperation, MissingIdentityInfo, MissingIdentities, and missing-identity outcome/event branches;ModelIdentity, OperationStatus, OpenOperation, and corrected inspection/snapshot types;suspended to open;executeAction/runToCompletion signatures;Modify session types:
failure_drain configuration provenance;ToolCall.outcome_ready vocabulary without producers;sourceIndex as full assistant-content index;scanBranch to SessionReader; inheritance by SessionMutator and Session is intentional;StorageBackedSession and its mutator and MemorySessionFacade; Session mutation authority remains process-local and has no remote Session facade.WP02 initially added synchronous recipient binding with a reserved delivery gate. WP04 supersedes only that mechanism:
HarnessEventBus.emitBatch() snapshots ordinary and watcher recipients synchronously;{ event, context };emitBatch receives nothing from that event.LaneCommand commit decisions retain a synchronous post-commit event batch. After commit succeeds, Lane.command publishes next, calls emitBatch as its final mutation action, and carries the delivery promise outside Session.mutate before awaiting it. Every existing event-producing commit uses this path, including direct idle/pending appends, lane configuration setters, and session-name/entry-label setters.
WP04 also moves harness lane publication into Session.createLane's committed-publication callback. Session commits, Harness publishes lanesByName and calls emitBatch(lane_created) in that continuation, and Session awaits the retained delivery promise after releasing the line.
Do not execute listeners on the line.
Keep restore.ts projection-only. Validate lane/operation ownership and kind compatibility. Remove describeSuspension and all payload hydration from createAgentHarness. Construct lanes and return open inventory without resolving registries.
Implement inspectExecution(context) as a no-write Lane.command observation. Derive captured model identity from the current durable phase. Read no storage payloads and resolve no registry identities.
Implement watch(context) as one no-write lane job:
A focused internal snapshot helper is allowed. Do not create a persistent hydrated presentation cache or generic reducer.
drive, resume, prompt convenience, compaction/navigation acceptance, abort/queues, executeAction, and runToCompletion remain SliceNotImplemented where not already implemented.
Provider/tool/configuration-failure transitions are specified now but implemented by R2/R3/R4/R7/R8. WP02 adds no effect, active operation, timer, hook, provider request, tool execution, retry, deferred fetch, cancellation reconciliation, or terminal transaction.
SuspendedOperation, MissingIdentities, and missing-identity status/outcomes/events are absent;AgentHarnessOptions has no receiver telemetry default.aborting:true;emitBatch: a watcher registered after publication but before delivery receives nothing;value_update and lane_created bind recipients in their commit continuation, so later listeners receive neither historical event;Type/direct-state tests prove:
MissingIdentities path;ToolResultMessage may omit details;No execution transition is added in WP02.
packages/agent/test/harness/runtime2/accept.test.tspackages/agent/docs/harness.mdpackages/agent/docs/work-packages/02-atomic-run-acceptance.mdpackages/agent/src/harness/agent-harness.tspackages/agent/src/harness/events.tspackages/agent/src/harness/session/types.tspackages/agent/src/harness/session/session.tspackages/agent/src/harness/session/memory.tspackages/agent/src/harness/session/remote.tspackages/server/src/remote-session-manager.tspackages/agent/src/harness/runtime2/harness.tspackages/agent/src/harness/runtime2/lane.tspackages/agent/src/harness/runtime2/restore.tspackages/agent/src/harness/runtime2/types.tspackages/agent/test/harness/runtime2/harness.test.tspackages/agent/test/harness/runtime2/lane.test.tspackages/agent/test/harness/runtime2/restore.test.tspackages/agent/test/harness/types.test.tspackages/agent/test/harness/storage-backed-session.test.tspackages/agent/test/harness/memory-session-repo.test.tspackages/server/test/conformance.test.tsNo backend schema, telemetry schema, coding-agent, or changelog change is expected. Stop for boundary review if one becomes necessary. On dev, defer changelog entries.
After Phase A:
git diff --check -- \
packages/agent/docs/harness.md \
packages/agent/docs/work-packages/02-atomic-run-acceptance.md
After Phase B:
cd packages/agent
node "$(git rev-parse --show-toplevel)/node_modules/vitest/dist/cli.js" --run \
test/harness/runtime2/accept.test.ts \
test/harness/runtime2/harness.test.ts \
test/harness/runtime2/lane.test.ts \
test/harness/runtime2/restore.test.ts \
test/harness/types.test.ts
cd "$(git rev-parse --show-toplevel)"
git diff --check
npm run check
./test.sh
Report runtime2 source line counts. The synchronized pre-WP02 runtime2 baseline is 967 lines; treat growth above 1,900 source lines as a design review trigger, not a target.
Stop when:
starting without registry preflight;emitBatch recipient binding occur in the commit continuation while the mutation never awaits delivery;npm run check, and full tests pass;Do not begin the first real drive package.