From 9b2aff2c5bf5e6c11a79ba0e6fd1b36a9e03eef1 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Sun, 13 Sep 2026 23:02:38 +0200 Subject: [PATCH] docs(agent): pico3: forbid AsyncLocalStorage; nested-line detection via Chord context key --- packages/agent/docs/pico/v3/hardening-handoff.md | 2 +- packages/agent/docs/pico/v3/plugins.md | 3 ++- packages/agent/docs/pico/v3/view-and-events.md | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/agent/docs/pico/v3/hardening-handoff.md b/packages/agent/docs/pico/v3/hardening-handoff.md index 83b01eec7..8d74ca0f1 100644 --- a/packages/agent/docs/pico/v3/hardening-handoff.md +++ b/packages/agent/docs/pico/v3/hardening-handoff.md @@ -238,7 +238,7 @@ Retain and expand the existing recovery tests for generation, tool, collapse, jo ### 7. Session line, failure, and cancellation - Every storage interaction, waiter registration, watch capture, registry mutation, reservation, and commit enters one FIFO Session line. -- Known nested Session-line entry rejects immediately. +- **No `AsyncLocalStorage` / `node:async_hooks`, anywhere.** Nested-line detection uses Chord contexts only: the Session derives a line context for every callback it runs (`withContextValue(LINE_KEY, marker, ctx)` with a private `createContextKey`), passes it to transaction builders (`commit((tx, ctx) => …)`; task builders receive `(tx, current, ctx)`), and `commit`/`read`/`onLine` reject `NestedLineOperation` when the supplied `ctx` carries `LINE_KEY`. Inside a builder, authors use the builder's `ctx` for everything; calling the Session from inside a builder with an outer ctx is a documented, undetectable self-wait, not something the kernel prevents. - Check caller cancellation before queuing and again before invoking a transaction callback. - Once persistence starts, call `Storage.commit` exactly once with `withoutAbortSignal(ctx)`. - A storage commit rejection faults the Session, closes storage with a non-cancellable context, and makes already-queued/later operations reject `Faulted`. diff --git a/packages/agent/docs/pico/v3/plugins.md b/packages/agent/docs/pico/v3/plugins.md index 51078af43..ffe501071 100644 --- a/packages/agent/docs/pico/v3/plugins.md +++ b/packages/agent/docs/pico/v3/plugins.md @@ -220,7 +220,8 @@ that task, no cleanup code); state that outlives tasks goes in a **namespace**. There is no hook invocation id: nothing durable identifies "the same handler invocation" across a crash or a reload, and the rerun must find the earlier decision. Hooks commit through `h.conversation(api.conversationId, ctx)` -handles and may wait as long as they like; `beforeTool` handlers rerun after a +handles (never from inside a transaction builder: use the builder's own `ctx` +there, see `hardening-handoff.md` §7) and may wait as long as they like; `beforeTool` handlers rerun after a crash or a suspend/reopen (they run before `started`), which is why their decision must be a memo. diff --git a/packages/agent/docs/pico/v3/view-and-events.md b/packages/agent/docs/pico/v3/view-and-events.md index 1f37b04b2..983256a88 100644 --- a/packages/agent/docs/pico/v3/view-and-events.md +++ b/packages/agent/docs/pico/v3/view-and-events.md @@ -496,6 +496,8 @@ rules are: `model`, so context derivation needs no special case. - Envelope assembly happens on the line; listener dispatch off the line, synchronous and in order; a listener throw closes that watch only. +- No `AsyncLocalStorage`: line re-entry is detected through the Chord context + the Session hands to its callbacks (`hardening-handoff.md` §7). - Chord bridge (`plugins.md` §6.1): a bounded ordered adapter applies one raw envelope's ops to the Chord `MutableReplicatedState` and calls `publish()` in the same synchronous block (Chord's `subscribe()` publishes pending mutations