docs(agent): pico3: forbid AsyncLocalStorage; nested-line detection via Chord context key

This commit is contained in:
Mario Zechner 2026-09-13 23:02:38 +02:00
parent 09b031600d
commit 9b2aff2c5b
3 changed files with 5 additions and 2 deletions

View file

@ -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`.

View file

@ -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.

View file

@ -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