From f61b4fbc8b79f59914a7fee15b2bb1e2b2f985f7 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Tue, 18 Aug 2026 00:18:51 +0200 Subject: [PATCH] docs(agent): align WP02 contract with context baseline --- packages/agent/docs/harness.md | 112 +++++++++++------- .../work-packages/02-atomic-run-acceptance.md | 17 +-- 2 files changed, 77 insertions(+), 52 deletions(-) diff --git a/packages/agent/docs/harness.md b/packages/agent/docs/harness.md index 6a6f82732..7b1526eda 100644 --- a/packages/agent/docs/harness.md +++ b/packages/agent/docs/harness.md @@ -244,8 +244,8 @@ Had the tool declared `replay: "safe"` (a read, a query), the harness would have Source type provenance: - `AgentMessage`, `AgentTool`, `AgentToolResult`, `QueueMode`, and `ThinkingLevel`: `packages/agent/src/types.ts`. -- `Skill`, `PromptTemplate`, `AgentHarnessResources` (`Resources` below), `AgentHarnessTool`, `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`. +- `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`. - `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`. @@ -2097,12 +2097,24 @@ For a driven operation, the gate applies to starting ordinary hooks, provider re ```ts // WRONG: abort may close the gate while this preparation awaits. effectGate.assertOpen(); +const staleAdmittedContext = withAbortSignal(effectGate.signal, context); await prepareRequest(); -models.streamSimple(model, context, { ...options, signal: effectGate.signal }); +models.streamSimple(model, aiContext, { + ...options, + signal: staleAdmittedContext.abortSignal, + telemetryContext: staleAdmittedContext.telemetryContext, +}); // Correct: JavaScript cannot interleave another task between these statements. +await prepareRequest(); effectGate.assertOpen(); -models.streamSimple(model, context, { ...options, signal: effectGate.signal }); +const admittedContext = withAbortSignal(effectGate.signal, context); +admittedContext.abortSignal?.throwIfAborted(); +models.streamSimple(model, aiContext, { + ...options, + signal: admittedContext.abortSignal, + telemetryContext: admittedContext.telemetryContext, +}); ``` The boundary is the public invocation of the operation, not necessarily the eventual SDK/network syscall. `Models.streamSimple()` synchronously returns and starts a lazy stream; its later auth resolution and provider-module loading are part of that admitted request. If abort wins before the call, Models is never invoked. If start wins, abort later pulls the signal and the admitted setup/provider path must stop cooperatively. The same definition applies to a hook aggregate: the complete registered pipeline is one operation admitted by invoking the aggregate runner. @@ -2131,7 +2143,7 @@ Each item means one check before invoking the complete registered pipeline, not - each sequential provider operation inside a structural summary attempt; - one `Models.fetchDeferred(...)` operation for an explicit poll. -Request preparation, identity preflight, context transformation, and option composition finish before the provider check. Abort-first means `Models` is never called; admission-first means the complete Models request—including later auth resolution, lazy loading, and provider delegation—is already admitted and owns `effectGate.signal`. Those later stages are not new effect starts. Best-effort `Models.cancelDeferred(...)` is cancellation cleanup and uses the separate close-only gate/signal, not the operation gate. +Request preparation, identity preflight, context transformation, and option composition finish before the provider check. Abort-first means `Models` is never called; admission-first means the complete Models request—including later auth resolution, lazy loading, and provider delegation—is already admitted and owns `admittedContext.abortSignal`, which composes the invocation and effect-gate signals. Those later stages are not new effect starts. Best-effort `Models.cancelDeferred(...)` is cancellation cleanup and uses the separate close-only gate/signal, not the operation gate. **Other ordinary operations:** @@ -2147,7 +2159,7 @@ No other code calls `assertOpen()`. In particular, it is not used for: - cancellation reconciliation, except its separate close-only deferred-cancellation cleanup; - passive event listeners; -Each listed integration has abort-first/admission-first tests. The assistant block receives `effectGate.signal` for signal-aware preparation even though preparation itself does not call `assertOpen()`. +Each listed integration has abort-first/admission-first tests. The assistant block receives the invocation Context for signal-aware preparation; only the admitted request adapter synchronously composes `effectGate.signal` into a child Context immediately before Models invocation. `requestAbort` starts by synchronously changing a matching live gate to `aborting` and installing the abort-mutation promise. It then attempts to commit `cancel_requested`; only after that commit succeeds does it pull the controller. Thus the only orders are: @@ -2785,13 +2797,16 @@ type AgentHarnessTool = Omit, "execute"> & { execute(toolCallId: string, params: Static, - signal: AbortSignal | undefined, onUpdate: AgentHarnessToolUpdateCallback, - context: TContext, - invocation: AgentHarnessToolInvocation): - Promise>; + toolContext: TContext, + invocation: AgentHarnessToolInvocation, + context: Context): Promise>; }; +type AgentHarnessToolContextSource = + | TContext + | ((context: Context) => TContext | Promise); + /** AgentHarnessStreamOptions is the curated source type from §0.7. It excludes signal and provider lifecycle callbacks, which the harness owns. */ interface AgentHarnessOptions { @@ -2806,7 +2821,7 @@ interface AgentHarnessOptions[]; - toolContext?: TContext | ((context: Context) => TContext | Promise); + toolContext?: AgentHarnessToolContextSource; systemPrompt?: string | ((ctx: TContext, context: Context) => string | Promise); // per request resources?: Resources; // skills, prompt templates @@ -3270,7 +3285,7 @@ The harness owns purpose-built execution blocks under `packages/agent/src/harnes ### Assistant streaming -`assistant.ts` owns one already-approved provider request. Before the request intent commits, the assistant procedure verifies that the captured durable `{ provider, modelId }` resolves in the harness's `Models` registry and runs `before_request`. After that commit, the supplied request adapter resolves the same pair, calls `EffectGate.assertOpen()`, and immediately invokes `Models` with `EffectGate.signal`. The block itself receives only executable values: +`assistant.ts` owns one already-approved provider request. Before the request intent commits, the assistant procedure verifies that the captured durable `{ provider, modelId }` resolves in the harness's `Models` registry and runs `before_request`. After that commit, the supplied request adapter resolves the same pair, calls `EffectGate.assertOpen()`, derives the admitted Context synchronously, and immediately invokes `Models` under its composed abort signal and telemetry parent. The block itself receives only executable values: ```ts interface AssistantResponseMetadata { @@ -3279,72 +3294,82 @@ interface AssistantResponseMetadata { } interface AssistantStreamObserver { - start(message: AssistantMessage): void | Promise; - update(message: AssistantMessage, event: AssistantMessageEvent): void | Promise; - end(message: SettledAssistantMessage): void | Promise; + start(message: AssistantMessage, context: Context): void | Promise; + update(message: AssistantMessage, event: AssistantMessageEvent, + context: Context): void | Promise; + end(message: SettledAssistantMessage, context: Context): void | Promise; } interface HarnessAssistantStreamConfig { model: Model; - systemPrompt?: string; + systemPrompt: string; tools?: AgentTool[]; thinkingLevel: ThinkingLevel; streamOptions: AgentHarnessStreamOptions; - transformContext?: (messages: AgentMessage[], signal: AbortSignal) => - Promise; - toProviderMessages: (messages: AgentMessage[]) => Message[] | Promise; + transformContext?: ( + requestContext: { messages: AgentMessage[]; systemPrompt: string }, + context: Context, + ) => Promise<{ messages: AgentMessage[]; systemPrompt: string }>; + toProviderMessages: (messages: AgentMessage[], context: Context) => + Message[] | Promise; /** Adapter for the before_payload hook; undefined keeps the payload. */ - beforePayload?: (payload: unknown, model: Model) => + beforePayload?: (payload: unknown, model: Model, context: Context) => unknown | undefined | Promise; /** Always-installed harness adapter. It drains accepted frame writes, then runs the optional after_response hook pipeline, before observer.end. */ afterResponse: (message: SettledAssistantMessage, - metadata: AssistantResponseMetadata) => - Promise; + metadata: AssistantResponseMetadata, + context: Context) => Promise; /** Models-backed adapter. It resolves the captured identity, synchronously checks EffectGate, then immediately invokes Models (§4.2). */ - request(context: Context, options: SimpleStreamOptions): + request(aiContext: AiContext, options: SimpleStreamOptions, + context: Context): AssistantMessageEventStream | Promise; observer: AssistantStreamObserver; - telemetryContext: TelemetryContext; - signal: AbortSignal; } function streamHarnessAssistant(messages: AgentMessage[], - config: HarnessAssistantStreamConfig): + config: HarnessAssistantStreamConfig, + context: Context): Promise; ``` The block does, in order: ```text -transformContext -→ toProviderMessages -→ construct provider Context +transformContext(requestContext, context) +→ toProviderMessages(messages, context) +→ construct provider AiContext → map curated stream options + thinking level to SimpleStreamOptions -→ install signal, telemetry context, beforePayload, and response-metadata capture -→ request -→ observer.start -→ observer.update* +→ install context.abortSignal, context.telemetryContext, beforePayload, and metadata capture +→ request(aiContext, options, context) +→ observer.start(message, context) +→ observer.update(message, event, context)* → settle the stream completely -→ afterResponse(settled message, captured metadata) -→ observer.end +→ afterResponse(settled message, captured metadata, context) +→ observer.end(message, context) → return the final settled message ``` -It never mutates `messages`. 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` 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: ```ts -(context, options) => { +(aiContext, options, context) => { const model = resolveCapturedModel(); effectGate.assertOpen(); - return models.streamSimple(model, context, { ...options, signal: effectGate.signal }); + const admittedContext = withAbortSignal(effectGate.signal, context); + admittedContext.abortSignal?.throwIfAborted(); + return models.streamSimple(model, aiContext, { + ...options, + signal: admittedContext.abortSignal, + telemetryContext: admittedContext.telemetryContext, + }); } ``` -There is no yield between `assertOpen()` and `Models.streamSimple()`. Its asynchronous auth/lazy/provider work is part of the admitted request and owns the same signal (§4.2). A captured identity that disappears after intent becomes an in-band provider error; an identity missing at the earlier safe preflight suspends without burning an attempt (§4.4). Existing summary helpers keep their separate `Models`-based generation logic, but gate their `Models` operation invocation the same way. +There is no yield between `assertOpen()`, synchronous admitted-Context derivation, and `Models.streamSimple()`. Its asynchronous auth/lazy/provider work is part of the admitted request and owns `admittedContext.abortSignal` (§4.2). A captured identity that disappears after intent becomes an in-band provider error; an identity missing at the earlier safe preflight suspends without burning an attempt (§4.4). Existing summary helpers keep their separate `Models`-based generation logic, but gate their `Models` operation invocation the same way. ### Tool phases @@ -3403,8 +3428,7 @@ function applyBeforeToolDecision(prepared: PreparedToolCall, ClearedToolCall | ImmediateToolOutcome; function executeToolCall(call: ClearedToolCall, effectGate: EffectGate, onUpdate: AgentHarnessToolUpdateCallback, - telemetryContext: TelemetryContext): - Promise; + context: Context): Promise; function finalizeToolCall(call: ClearedToolCall, executed: ExecutedToolCall, patch: AfterToolPatch | undefined): FinalizedToolCall; function createToolResultMessage(call: FinalizedToolCall): ToolResultMessage; @@ -3428,9 +3452,9 @@ planned call → materialize source-ready outcomes as entries + usage ``` -Unknown tools, `prepareArguments` failures, invalid initial/replacement arguments, and blocked calls produce `ImmediateToolOutcome` and skip intent/execution before staging a synthetic `outcome_ready` result. `AgentTool.prepareArguments` remains deterministic/idempotent computation and may repeat before intent; effectful policy belongs in `before_tool`. At `tool.execute` operation admission, `executeToolCall` calls `EffectGate.assertOpen()` immediately before `tool.execute` and passes `EffectGate.signal`; it converts expected tool throws to an error result, stops accepting updates when the tool promise settles, and emits raw tool-effect telemetry. Every update emits live observation; `checkpoint:true` additionally enqueues one invocation-fenced `pi.pending.tool_output` replacement and replaces only the latest checkpoint-write promise reference (§3.8). The procedure retains only the latest event-delivery promise because the event bus serializes delivery, and only the latest checkpoint-write promise because the lane line is FIFO; it closes checkpoint admission at tool-promise settlement and awaits both before `after_tool`. `finalizeToolCall` applies the documented field-by-field patch semantics before `tool_end` and outcome staging. +Unknown tools, `prepareArguments` failures, invalid initial/replacement arguments, and blocked calls produce `ImmediateToolOutcome` and skip intent/execution before staging a synthetic `outcome_ready` result. `AgentTool.prepareArguments` remains deterministic/idempotent computation and may repeat before intent; effectful policy belongs in `before_tool`. At `tool.execute` operation admission, `executeToolCall` calls `EffectGate.assertOpen()`, synchronously derives `withAbortSignal(effectGate.signal, context)`, checks that admitted signal, and immediately invokes the neutral `AgentTool` adapter with it. The bound harness adapter passes that admitted Context to `AgentHarnessTool.execute` in its trailing position. The block converts expected tool throws to an error result, stops accepting updates when the tool promise settles, and emits raw tool-effect telemetry. Every update emits live observation; `checkpoint:true` additionally enqueues one invocation-fenced `pi.pending.tool_output` replacement and replaces only the latest checkpoint-write promise reference (§3.8). The procedure retains only the latest event-delivery promise because the event bus serializes delivery, and only the latest checkpoint-write promise because the lane line is FIFO; it closes checkpoint admission at tool-promise settlement and awaits both before `after_tool`. `finalizeToolCall` applies the documented field-by-field patch semantics before `tool_end` and outcome staging. -Before starting any call in a live batch, the tool-batch procedure resolves `toolContext` once and binds the complete captured active-tool set into ordinary `AgentTool` adapters retained in a procedure-local snapshot. Each adapter also binds `AgentHarnessToolInvocation { invocationId: resultEntryId, operationId, turnId, getMemo, setMemo }` and the harness-specific update callback. A missing captured active tool therefore suspends before any call starts; provider calls to names outside that active snapshot still become ordinary unknown-tool results. Every call in the batch observes the same application context and its own stable invocation identity. Safe replay creates a new code/context snapshot but passes the same invocation id and memos after deleting the stale progress checkpoint. `AgentTool.replay` defaults to `"never"` when omitted. +Before starting any call in a live batch, the tool-batch procedure resolves `toolContext` once and binds the complete captured active-tool set into ordinary `AgentTool` adapters retained in a procedure-local snapshot. Each adapter also binds `AgentHarnessToolInvocation { invocationId: resultEntryId, operationId, turnId, getMemo, setMemo }`, the harness-specific update callback, and the current invocation Context. A missing captured active tool therefore suspends before any call starts; provider calls to names outside that active snapshot still become ordinary unknown-tool results. Every call in the batch observes the same application context and its own stable invocation identity. Safe replay creates a new code/context snapshot but passes the same invocation id and memos after deleting the stale progress checkpoint. `AgentTool.replay` defaults to `"never"` when omitted. There is deliberately no harness `executeToolBatch`. In parallel mode the direct procedure makes one source-ordered start pass. Each position either starts a real promise or retains an immediate outcome until it can be staged. Effects/finalization then settle independently: each complete real or synthetic result commits `outcome_ready` immediately in completion order. A separate lane-line job materializes the contiguous ready prefix in source order. Durably, completed calls form a prefix while the remaining suffix may mix `planned`, `effect_pending`, and `outcome_ready`; for example `[effect_pending, outcome_ready, effect_pending]` is valid after the completed prefix. A crash discards only unstaged process-local outcomes. Recovery safely replays or interrupts orphaned effects, materializes already-ready outcomes without resolving tool code, and reruns ordinary clearance for planned positions. The same procedure owns cancellation and durable batch completion. Genuine-`length` calls bypass effects but stage their specified synthetic outcomes (§3.7). @@ -3450,7 +3474,7 @@ Invocation cancellation is not durable cancellation. An aborted `context.abortSi Shared `AgentHarness`, `AgentLane`, `Session`, and `SessionTree` receivers retain no caller Context and expose no receiver-level telemetry default. Process-local objects representing one invocation, such as a drive pass or event subscription, may retain their derived Context for that invocation only. Buffered events retain `{ event, context }` and bind recipients at enqueue so delayed local handlers and RPC event frames preserve source lineage. -WP02 preserves this propagation boundary but introduces no new telemetry spans, RPC transport, trace-carrier codec, distributed cancellation implementation, drive-ownership policy, or final RPC context-position policy. +WP02 preserves this propagation boundary but introduces no new telemetry spans, generic RPC transport, trace-carrier codec, distributed cancellation implementation, or drive-ownership policy. Required trailing Context position is already the resolved receiver contract; adapter normalization of omitted optional business arguments remains separate RPC work. Required spans remain: diff --git a/packages/agent/docs/work-packages/02-atomic-run-acceptance.md b/packages/agent/docs/work-packages/02-atomic-run-acceptance.md index 4d7e86ad9..dc885666d 100644 --- a/packages/agent/docs/work-packages/02-atomic-run-acceptance.md +++ b/packages/agent/docs/work-packages/02-atomic-run-acceptance.md @@ -167,14 +167,14 @@ Decision: - every current public harness/lane operation receives one explicit trailing `context: Context`; - `context.telemetryContext` supplies the invocation parent for harness/session spans, and derived child contexts preserve parentage across concurrent asynchronous branches without ambient state; -- remove `AgentHarnessOptions.telemetryContext`: a receiver-level parent cannot represent concurrent callers, and runtime configuration must not grow a replacement fallback; +- the current baseline has removed `AgentHarnessOptions.telemetryContext`: a receiver-level parent cannot represent concurrent callers, and runtime configuration must not grow a replacement fallback; - `context.abortSignal` is the explicit process-local invocation-cancellation channel used by local callers and reconstructed by RPC adapters from per-request cancel/disconnect control; it never implies durable `cancel_requested` and must not call `requestAbort()` implicitly; - each RPC request has an independent server `AbortController`; canceling one request or drive joiner must not abort unrelated callers or shared execution owned under a different policy; - shared harness, lane, Session, and SessionTree receivers never retain a caller context; - acceptance passes its invocation context to Session reads/mutation/commit, faults, and emitted events; buffered events retain `{ event, context }` so delayed local handlers and future RPC event frames preserve the source telemetry lineage; - RPC adapters remove Context from serialized business arguments, carry required trace/cancellation data as control-plane metadata, and reconstruct a fresh local Context at the receiving boundary; the Context object, `AbortSignal`, and `TelemetryContext` are never serialized as business arguments or stored durably; - whether specific adapter-managed typed values may cross as control-plane metadata remains an open RPC design decision; -- WP02 documents and preserves that boundary but does not implement RPC transport, carrier encoding, distributed cancellation, new telemetry spans, or final RPC parameter-position policy. +- WP02 documents and preserves that boundary but does not implement generic RPC transport, carrier encoding, distributed cancellation, or new telemetry spans; required trailing Context position is already the resolved receiver contract. ### 9. Rejected alternatives @@ -448,8 +448,9 @@ This phase is mandatory and has its own review stop. Do not edit runtime source - Specify the RPC boundary principle: the client maps `context.abortSignal` to `cancel(requestId)`; the server owns one `AbortController` per request, aborts it on matching cancellation or connection loss, and uses `withAbortSignal` plus extracted telemetry to construct a fresh local Context. The adapter strips/inserts Context outside serialized business arguments and never serializes the Context object, `AbortSignal`, or `TelemetryContext` as business arguments. Whether specific adapter-managed typed values cross as control-plane metadata remains open in `rpc.md`. - Require pre-aborted RPC calls to start no server work, while leaving that rejection in the adapter rather than the core harness. - Distinguish `context.abortSignal` from durable abort: invocation or RPC cancellation must not call `requestAbort()` or write `cancel_requested`; one canceled request/joiner must not cancel unrelated callers. -- Remove `AgentHarnessOptions.telemetryContext`. The explicit invocation Context is the sole telemetry parent because a harness-level default cannot represent concurrent callers; do not add a replacement receiver-level fallback to runtime configuration. -- Keep trailing parameter position and propagation behavior aligned with current source for WP02. Broader RPC transport, carrier encoding, and any future context-position migration remain open in non-normative `rpc.md` and are not decided by WP02. +- Document that the baseline has removed `AgentHarnessOptions.telemetryContext`. The explicit invocation Context is the sole telemetry parent because a harness-level default cannot represent concurrent callers; do not add a replacement receiver-level fallback to runtime configuration. +- Align §5.2 and §5.7 tool/assistant execution signatures with the baseline's context-threaded capabilities: no standalone signal or telemetry-parent parameters where Context now carries them, and invocation callbacks receive trailing Context. +- Keep required trailing parameter position and propagation behavior aligned with current source for WP02. Generic RPC transport, optional-argument normalization, carrier encoding, and cancellation remain separate work in non-normative `rpc.md`; context position is no longer open. ### §3.3 — Restore validation @@ -573,7 +574,7 @@ Modify `packages/agent/src/harness/agent-harness.ts`: - add `DriveOutcome.action_required`; - change `executeAction(context)` and `runToCompletion(context)` result signatures; - preserve every existing trailing `context: Context` parameter, including expected-id primitives, and do not persist `Context`; -- remove `telemetryContext` from `AgentHarnessOptions`. +- keep `AgentHarnessOptions.telemetryContext` absent and do not add a receiver-level replacement. Update public type tests before runtime code. @@ -593,9 +594,9 @@ Add focused modules rather than growing `lane.ts` into a generic reducer: Modify: -- `runtime2/types.ts` to hold the complete owned hydrated lane projection and remove `Config.telemetryContext`; +- `runtime2/types.ts` to hold the complete owned hydrated lane projection without a receiver-level telemetry field; - `runtime2/restore.ts` to keep projection restoration direct and separate from public hydration; -- `runtime2/harness.ts` to hydrate all lanes before constructing/publishing the harness, return `open`, and remove the receiver-level telemetry assignment/import; +- `runtime2/harness.ts` to hydrate all lanes before constructing/publishing the harness and return `open` without reintroducing receiver-level telemetry; - `runtime2/lane.ts` to implement acceptance, memory-only inspection, and memory-only `watch(context)`; - `runtime2/index.ts` exports only as needed. @@ -807,7 +808,7 @@ npm run check If subprocess tests fail only because ignored workspace `dist` artifacts are stale, obtain explicit user authorization before `npm run build`, then rerun `./test.sh`. -Report runtime2 source line counts. The rebased clean pre-WP02 baseline is 970 lines; treat growth above 1,900 runtime2 source lines as a design review trigger, not a compression target. +Report runtime2 source line counts. The synchronized clean pre-WP02 baseline is 967 lines; treat growth above 1,900 runtime2 source lines as a design review trigger, not a compression target. ## Review checkpoints