Skip to content

feat(presentation): one record, a presentation index per agent — the deferral queue replaced (#291 step 3) - #844

Open
tel-0s wants to merge 52 commits into
mainfrom
feat/presentation
Open

tel-0s wants to merge 52 commits into
mainfrom
feat/presentation

Conversation

@tel-0s

@tel-0s tel-0s commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #546 (feat/dendrite): agent-framework's part of #291. Every write is stored at once; each agent has a window (its context-manager index) that only grows at the end, so several live turns over one shared slot are correct by construction, readers see the record as it arrives, and the deferral queue is gone.

Design note, step-3 addendum, decisions and the reviews: #291. Ships only together with the connectome-host companion (HOST-1) and after the presentation releases of chronicle (0.6.0, the batched append) and context-manager (0.25.0, the window index) are published; the Dendrite releases (0.5.0, 0.24.0) carry neither, and create() refuses them by name.

🤖 Generated with Claude Code

Companion PRs, merge order (CONTRIBUTING: cross-repo companions declared with the order). The presentation set ships as four PRs, each released at the next minor the registry gives it, each floor commit landing once its dependency is on npm: chronicle anima-research/chronicle#62 (→ 0.6.0, the batched append) → context-manager anima-research/context-manager#350 (→ 0.25.0, declares chronicle ^0.6.0) → agent-framework #844 (→ 0.31.0, declares context-manager ^0.25.0 and chronicle ^0.6.0) → connectome-host anima-research/connectome-host#485 (declares agent-framework ^0.31.0). Until each dependency is published its PR cannot install from the registry — the lockfiles resolve the Dendrite releases and npm ci refuses them — so CI on these PRs is red by construction, as the Dendrite set's was; the suites run on the linked presentation trees. The lockfiles are regenerated at each floor commit.

tel-0s and others added 29 commits October 24, 2026 23:22
…ring its round

When a stream completes while its round's tools still run, driveStream gives
each call still running an error result and stores the round and its results.
It stored the round, awaited buildStoredToolResultContent, then stored the
results, so a write that lands during the build and isn't deferred (another
resident's own row in the shared slot, a module's tool_result, a channel's row
in a fork) was stored between the round's tool_use and its result. #291's
presentation model takes a round's call and results as written back to back,
and the ordinary tool boundary already builds the results before it stores
either.

This path now does the same: the round held when the response completed is
taken, the results are built, and the round and its results are stored with no
await between. A write landing during the build is stored before both. The
round's pendingAssistantBlocks entry is cleared after both stores, and only if
it still holds that round, so a teardown during the build neither leaves the
results stored alone nor has a later owner's round cleared.

No test reached this path. The new test/stream-complete-with-tools-pending.test.ts
completes prime's stream with one call's result in and one still running, holds
the spilled result's workspace write, and runs another resident's turn while it
is held: the round and its results are adjacent and the other resident's answer
is stored before them. On the old order it lands between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…egistry's own slot from what it observes

#291's presentation index decides from a row which agents it is presented
to, and at boot which rows an agent has to be presented that it wasn't. A
row's target lived only in the write that stored it (the forAgent option,
or which context manager a writer called), so in the residents' shared slot
rows meant for different agents looked alike in the store. Every row the
framework stores now carries that in its metadata.

metadata.audience lists the agents a row is meant for, computed when it is
written: the delivery agent of a write through addMessage; for an admitted
inbound message, the recipients admission delivered it to (a new audience
option on addMessage), and nobody for one a tune-out holds. metadata.ownRowOf
names the agent whose own row it is, by who stored it and not by its
participant: rounds and tool results (Agent.addAssistantResponse, the direct
Agent API, ToolResultGuard.storeResults, guarded or not), puppeted pairs,
clock rows, [continue] rows, batched-wake lines, the wrapper-contained row,
the framework's notices about an agent's own speech, routing and failures,
and a derived agent's inheritance, pending round and framing. Every writer
that stores into a context directly goes through storeOwnRow or asOwnRow, and
a conversation fork's channel rows are stamped for the fork. A shared fork
presents its parent's name as its own voice, so a participant test would
take its parent's mail as the fork's own round; the stamp says it is for
the fork and nobody's own. Mail and agent-lifecycle notices also name their
recipient as dendrite.to, and boot's redelivery check counts a stored copy
only where it was for the mail's own recipient (a copy with no `to`, stored
before this, still counts by its id). The stamp is metadata only.

The registry's readsSharedSlot was one flag for two relationships: owning
the residents' slot (residents) and observing it (the subconscious, whose
own rows go to an isolated slot). Records and specs now carry ownSlot, a
MessageSlotRef, and the observe edge the subconscious always declared is the
observing half; ownSlotOf, slotOwners, observersOf and readersOf answer from
them. readsSharedSlot is deprecated, derived from the split on new records,
and a record restored from an earlier version is given the own slot its flag,
edges, inheritance and namespace meant. reopenAgentContext reopened by the
flag, so an ended subconscious came back on the shared slot without any of
its own rows; it now reopens by its own slot, with the slots it observed
merged in as when it ran. isUnreadStoredMessage, isOrdinaryResident and
noteForOtherResidents read the split, so a row in the subconscious's own
slot is weighed against its reading alone. ObserveEdge gains fromMessageId
beside the deprecated fromSequence.

Tests: test/audience-stamp.test.ts drives a two-resident host through a tool
round, a clock, targeted and untargeted events, mail, a lifecycle notice, a
copy fork and a puppeted call, and checks every row in the slot is stamped
and each kind as expected; compiles the stamped rows against the same rows
unstamped; checks a shared fork's mail from its parent; the redelivery
filter; and the reopened subconscious. The last two fail on the old code.
dendrite-registry.test.ts gains the split, the deprecated flag on a spec, and
restore of records written before it. Tests that pinned exact metadata (the
clock row, a wake line, the head-move notice, the inference-failed marker,
mail and lifecycle detail) and fakes of the registry's readsSharedSlot are
updated, each with a comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th the results its parent holds

framePendingRound read a parent's results only from a round waiting for its
tools. While a boundary builds a round's results for storing, the ordinary
tool boundary or a stream that completed while its tools ran, the round is
still pending and the state is `ready`, with every result in `toolResults`.
A child derived then was framed with a note that each call was still running,
its result in or not, so it acted on a false statement about what its parent
had. Since 8d7ae36 the completed stream's round stays pending through its
build, which made this reachable on that path too (AF-B).

framePendingRound now takes the results from a `ready` state as well, so the
child gets the round with each result that came in and the error result of
each call that was still running when the stream completed.

test/stream-complete-with-tools-pending.test.ts shares its setup between two
cases now. The new one derives a child while the spill of fetch's result is
held: the child's dendrite-pending-round rows carry fetch's result (the same
copy prime stores) and slow's error result, and no '[No result yet' note. On
the old read it fails with that note in place of fetch's result. The spill
hold lets the child's own copy of the spill through, so the derive finishes
inside the build. The assertion that neither row is in the slot during the
build is a comment now: it pinned the mechanism, and on the pre-8d7ae362
order the adjacency assertion still fails (AF-C).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n it is restored, and a held write for nobody

A deferred-write queue persisted before the audience stamp holds entries
with no stamp. restorePersistedDeferredWrites pushed them back as read, and
the tool boundary's, a turn start's (flushForStart) and a released window's
flushes store an entry with the metadata it was queued with, straight into
the store, so after an upgrade those rows were stored unstamped and looked
like rows from before the stamp. Restore now stamps each entry that has none
as addMessage stamps a write it queues: for the agent it is queued for (its
delivery agent when it names none), and for nobody when it carries a
tune-out's hold. An entry that has a stamp keeps it.

stop()'s drain, resume()'s flush and the boot recovery re-add entries
through addMessage, whose default stamp was the delivery agent even for a
write that carries a tune-out's hold, so a held entry was stamped as meant
for the agent whose store it landed in. That contradicts inboundAudience,
which gives a held inbound message no audience. addMessage now stamps any
write that carries the hold, and says nothing else, for nobody.

Tests in test/audience-stamp.test.ts, all failing on the code before this:
a quiesced boot on an older queue restores each entry stamped, a turn
start's flush stores the plain one for the primary and the held one for
nobody with its hold kept, and resume() stores one queued for oren for
oren; a serving boot whose recovery write of the held entry fails leaves it
for stop()'s drain, which stores it for nobody (it was ['mira']); and the
reviewers' probe2, an unstamped held entry handed to readdDrained as stop()
hands it, is stored for nobody. The 291-audience-stamp.added.md fragment
says entries queued by an earlier version are stamped at restore.

Fixes AF-D (fix-wave item FW-AF-2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt, and a response's messages for the agents it asks to read

addMessage stamped a write that named no agent for the agent it was
delivered to, the primary, which is also the stamp of a write for the
primary by name. In the residents' shared slot every resident reads such a
row, and the presentation index (#291) is to present an untargeted row there
to the residents that take untargeted inbound, but the record could not tell
the two apart. A module's process response was stamped for the primary even
when its requestInference asked another agent to read it: a response with
requestInference ['oren'] stored its message for ['mira'] and woke oren.

A write that names no agent is now stamped, when it is written, for the
agents that take untargeted inbound (the registry's untargetedRecipients)
and own the slot it lands in: every resident on a residents' host, and the
one resident on a single-resident host, as before. With none, as on a host
whose only agent is an ephemeral run, it is the agent it is delivered to. A
write that names an agent is for that agent, and one that carries a
tune-out's hold for nobody, as before. A module's own write
(ModuleContext.addMessage) names no agent. applyProcessResponse stamps a
response's messages for the registered agents a requestInference list names,
once each, or the delivery agent when none of them is registered; true, no
request or an empty list leave the default, which is for the agents true
wakes. Restore stamps an entry an earlier version queued with no agent by the
same rule, since that version, as this one, queued a write for the primary
by name as naming it. A target that doesn't read the slot the row lands in is
stamped as named, as an inbound message's is; how such a row is presented is
left to the presentation index.

Tests in test/audience-stamp.test.ts: on a two-resident host, a module's
write, a framework write with no forAgent, a response with no request, with
true and with an empty list are stamped [mira, oren]; forAgent mira is
[mira]; requestInference ['oren'] is [oren] (it was [mira]); a list naming
oren twice and an unregistered name is [oren]; one naming only an
unregistered name is [mira]; a held message is for nobody whatever the
request. The first fails on the code before this. On a single-resident host
every row, these writes included, is stamped [mira] as before, and its
compile matches the same rows unstamped, as the byte-identity test, which
now also covers the new writes, checks on the two-resident host. The two
tests of an earlier version's queue pinned the untargeted entry as [mira];
they now expect [mira, oren], with a comment. audience.ts, the module types
and the 291-audience-stamp.added.md fragment state the rule.

Fixes AF-E (fix-wave item FW-AF-3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ere, read an observe edge on an agent as that agent's slot, and keep the presets' readsSharedSlot

Boot's redelivery check counted a stored copy of held mail only where it
was for the mail's own recipient (dendrite.to), but the check beside it, a
copy still in the deferred-write queue, and releaseLandedMail, when a copy
lands, went by the mail id alone. In the residents' shared slot a copy for
another recipient, one the mail was addressed to before a re-addressing,
held the mail back from its own recipient while it was queued and released
it when it landed. All three now go through isMailCopyFor: the mail's id,
and its recipient or none named, as a copy stored before mail named its
recipient. A copy for another recipient leaves the mail held for its own
delivery, or boot's check, to release.

observedSlot read an observe edge that names an agent and no slot as the
residents' shared slot, so observes: [{ agent: 'worker-1' }] was reported
by observersOf and readersOf as observing the residents, and an ended
observer's reopened view would have merged the residents' slot. Such an
edge now reads that agent's own slot, on its branch for a shared fork. The
registry's new observedSlotOf(edge) looks the agent up in its records (live,
awaiting re-declaration, else its newest ended one), and observersOf,
readersOf, the derived readsSharedSlot flag and reopenAgentContext go
through it. observedSlot(edge, ownSlotOf) takes the lookup, and gives
undefined for an agent it can't place. reopenAgentContext merges nothing
for such an edge, or for a slot on a branch of its own, which an auxiliary
view, reading a namespace on the reopened context's branch, can't show. A
spec or record with no ownSlot reads its deprecated flag as before, with
an edge that names an agent no longer taken as an edge on the shared slot.
ObserveEdge's docs say so.

residentSpec and subconsciousSpec dropped readsSharedSlot when they took
ownSlot, so a host reading the flag off a preset's spec read false. They
set it again beside ownSlot, which the registry reads first, for this
release; the 291 changelog fragment names the deprecation and that a later
release drops it.

Tests: audience-stamp.test.ts gains a copy of oren's mail queued for mira,
which no longer holds back its delivery to oren at boot, and which, landing
in mira's store, leaves the mail held until boot's check delivers it to
oren. dendrite-registry.test.ts gains observes: [{ agent: 'worker-1' }]
reported by observersOf(worker-1's slot) and not the residents', a shared
fork's slot on its branch, an ended agent's slot, the free function with and
without a lookup, the presets' readsSharedSlot, and a flag-only spec whose
one edge is on a worker's slot owning the shared slot. Each fails on the code
before this.

Fixes AF-J (fix-wave item FW-AF-4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t writer's stamp, a subconscious row's one reader, and a message for prime during the complete-path build

tool-result-guard.test.ts checked that its IngressObserver never saw a
withheld payload with every() over the snapshots it heard, which passes on
an empty list, and context-manager reaches onNewMessage through its
presentation dispatch now. The test also asserts that the strategy heard
rows, the results row among them, and heard it as the guard's notice or
the stub it settles to. Given an onPresented, which takes the hook's place,
the new assertion fails where the old one passed.

The audience-stamp scenarios check the rows of the paths they drive, and
most writers that store into a context directly appear in none of them.
audience-stamp-writers.test.ts reads src as a syntax tree (Babel's
TypeScript parser) and finds every member named addMessage, whatever it is
called on. Each is the framework's own addMessage, through one of the hooks
create() wires to it, or a direct write listed by file and scope with how it
stamps its row: asOwnRow (storeOwnRow, the guard's results, framing, the
conversation seed, clock rows, routing notices, wake lines), withAudience
(addMessage's body, the two fork writes), or the stamp a queued entry
carries (the three queue flushes). An unlisted writer, one more write in a
listed scope, or a listed scope that no longer calls its stamp fails it; a
second test shows the scan reads aliases, ?., ['addMessage'], .bind and
destructuring, and names scopes.

isUnreadStoredMessage weighs a row in the subconscious's own slot against
the subconscious alone since the own-slot split, and no test covered it.
audience-stamp.test.ts writes two rows for the subconscious by name: with
both residents compiled past them they are unread and unshown and one is
retracted, and the other is read once the subconscious compiles past it.
On the reader rule before the split (the residents and the subconscious)
the first assertion fails.

stream-complete-with-tools-pending.test.ts adds a message for prime that
comes in while the spill of its results is held. It is stored once, right
after the round and both results, and prime's answer to it follows. Both
orders of the complete path keep it so (the reviewers' msg probe); storing
it while the turn is alive, past the deferral, puts it ahead of the round
and fails the test.

Fixes AF-I and the message-for-prime part of AF-C (fix-wave item FW-AF-5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… catch-up's agent-framework main

main's #782 (stored context injections; a module's forAgent) and #785
(thinkingBinding.binds) meet wave 1's AF-0 and AF-1. Five hunks: the
agent's own round is stored through storeOwnRow and its id returned, as
main's noteStreamEnd now takes it; a fork's seed logging takes main's
injection count; the complete-while-waiting path keeps AF-0's order
(results built first, round and results stored back to back) and notes
the stored reply as the stream's last, as main does, while the pending
entry still holds that round; the module write docs say both what
forAgent means and how the row is stamped. main's storeInjections stores
an agent's own context, so its write is stamped as an own row, and the
writers' guard lists it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…y injects by its stored id

Under speech.locus 'delivery', a tool boundary that tells the agent someone
addressed it from another channel stores a [routing] notice as the agent's
own row and injects it with the round. addressedElsewhereNotice returned the
notice without the id its store gave it, so the boundary's note to
context-manager (noteRoundSent, #785) named the row as one the store doesn't
hold. On a model that binds its thinking, nothing the stream sent after that
kept its thinking at the next compile. The re-pin notice already returned its
id. This is a pre-existing #785 defect the #291 audit found; it is fixed
first, with no flag, as part of AF-2, and the presentation index's hand-off
needs the id to present the notice too.

Tests: present-while-acting gains a case on a binding agent: the notice
injected at the boundary is noted by its stored id, last, and no row of the
round is noted unstored. It fails with the id dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…while its results are built

When a stream completes while its round's tools still run, driveStream gives
each call still running an error result, builds the results (a spilled
result is a workspace write) and then stores the round and its results. A
caller's stop or an operator's quiesce abandon that landed during that build
was not checked: the round and its results were stored, the trailing reply
too, and the turn reported inference:completed. The ordinary tool boundary
drops its round in the same case (#291 AF-A).

Both now use one test, roundStoppedDuringBuild(agent, before, stream): the
agent's state replaced since the build began (a caller's stop, a disposal),
or a framework cancel recorded for this stream once it no longer waits for
its results (a quiesce abandon, stop()). On the complete path it drops the
round, its results and the reply after its last tool block, emits one
tool:result_dropped for the first call that was still running, and, since
the stream has completed and no event of it will report the stop, ends the
turn as a stop made during the guard's wait does: the framework's own stop
through settleFrameworkCancel, else the caller's through
settleDeliberateCancel, each with its one inference:aborted and no
inference:completed. Flag-independent; part of AF-2.

Tests: stream-complete-with-tools-pending gains two cases, abortInference
and quiesce({ abandon }) during the held spill: neither row is stored, one
inference:aborted with the stop's reason, no inference:completed, one
tool:result_dropped for the round, and the caller's stop leaves its
[turn-interrupted] marker. Both fail without the check. The ordinary
boundary's cases in stop-at-tool-boundary pass unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…onfig.presentationIndex, and read marks

#291 gives each agent a window of its own over the one record: the rows it
was shown, in the order its requests carried them, kept by context-manager's
window index (presentRows). This is the agent-framework module that decides
who each stored row is presented to and when (AF-2), built and tested on its
own; nothing in the framework calls it yet but the stamp check below, so
every compile and request is what it was, flag on or off.

src/presentation.ts:
- the audience rule, one pure function: bearingOf(row, readerFacts, inOwnSlot)
  says whether a row is the reader's own, an arrival, or elsewhere, from its
  audience stamp and the registry's slots and observe edges. Another agent's
  own row is elsewhere (decision 1); a held row is no resident's; a row with no
  stamp goes to the slot's owners, but for a shared fork's inheritance; a row in
  an observed slot goes by observedRowFor (dendrite/audience.ts), the observe
  edge's payload. audienceFault names a stamp no reader of its slot can read.
- AgentPresentation: adopt (the window written down, own rows repaired, the
  own-row sink installed), presentOwn and afterStore (own rows at their write,
  others' rows at once to a reader with no turn alive), presentAtStart,
  presentAtBoundary (the round's rows, then a prefix of the arrivals cut at the
  first body the stream can't carry, each body whole), presentAtEnd, handOff
  (the window's newest bodies must be the rows the stream is given, in order),
  repairAndRest, holders and presentedToAnyone, noteFramed, onPresented and
  presentedSince. Arrivals are read from the index (getUnpresentedMessages and
  the rule), never from the store's head, so a crashed turn's arrivals and a
  derived agent's inheritance come out right by construction. It presents only
  for an agent it took on whose manager is in presented mode, freezes others'
  rows while the host is quiesced or under a surgery hold, and never throws into
  a writer.
- isInjectableMidTurn and asCompiled move here from framework.ts, which imports
  them; heldFromResidents moves to dendrite/audience.ts, beside the stamp, where
  observedRowFor reads it, and framework.ts and presentation.ts export it as
  before.

src/read-marks.ts: an opaque ReadMark (branch, store head, and the index length
in presented mode), with covers/coverTest (a legacy mark covers by sequence; a
presented one strictly below its length), markAtOrAfter and describeReadMark:
the type AF-3 moves the watermarks to. Position (presentation.ts) is its
WindowOrdinal brand, with windowMarkOf, positionIn and windowEnd.

The own-row sink: storeOwnRow tells the writer of the row once the add has
returned, unless called with present false; Agent.setOwnRowSink and
ownRowsStored, and ToolResultGuard.setOwnRowSink, called after each of
storeResults' adds and not on its idempotent retry. Nothing installs a sink
yet.

FrameworkConfig.presentationIndex (@experimental, default false) constructs the
module, refused where context-manager has no presentRows, and addMessage checks
each write's stamp: one meant only for agents that can't read its slot is
traced presentation:unreadable-audience, once, and stored as named. The trace
union is settled for HOST-1: presentation:presented, :repaired,
:unreadable-audience, :failed, :hand-off-mismatch, :retracted and window:undone.

Tests: read-marks (legacy and presented covers, the off-by-one pin, other-branch
doubt, @ts-expect-error pins on the opaque mark and a plain-number Position);
presentation-audience (one case per row kind over a real registry, the
subconscious's own delivery and routing faults included); presentation (the
module over real context managers on a temp store: write-down, sink, held,
untargeted, boundary cut and wire order, presentAtEnd gating, a crashed turn's
arrivals, boot repair without undone rows, a shared fork's inheritance, a
presentation cut short, hand-off vouching, holders with framing, a foreign index
format, #795 at module level); presentation-flag (flag off writes no index and
the requests match flag on, the routing-fault trace, the refusal); tool-result-
guard's sink case. test/helpers/wire-membrane.ts lifts sent-rows' signed-round
builders, which sent-rows now imports, beside a membrane that records each
agent's wire. test/held-for-another-agent.test.ts is #795 in its plan form, a
todo test: it runs with the flag on and fails at helperCarried until the
presentation points are wired (AF-4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… row, and a row's readers over the windows that hold it

#291 gives each agent a window of its own over the one record. What an
agent has read, been shown or answered was a { branch, sequence } head
compared with a row's own sequence, which says nothing once a row is stored
first and presented later. This moves every such question onto AF-2's
ReadMark (AF-3): the store's head as before, and the window's index length
once the agent's context is in presented mode, read only through covers
and markAtOrAfter. A mark taken in legacy mode covers by sequence as `<=`
did, so a host that never presents gets the answers it got.

- Agent: the consumed, shown and answered watermarks and publishedThrough
  are ReadMarks; pendingWatermark is readMarkOf; applyCompileWatermark and
  notePublished order marks with markAtOrAfter; publishedAfterReading
  (destination, row, cm) asks covers. The read-through plumbing (script
  call starts, publishReadThrough, the speech sinks, scripts.ts) carries
  the opaque mark. read-marks' covers, coverTest, markAtOrAfter,
  describeReadMark and its types are exported.
- InferenceRequest.readsDeferredWrite is renamed readsRow: the stored row a
  wake is for, or a queued write's held handle until the queue goes (AF-6).
  readsRowFor marks only the delivery agent's wake, and only for a write
  deferred for it or stored and not presented to it (placement.presented
  false, which AF-4's store path sets; nothing sets it yet). rowDone drops
  the wake once its row is gone or an answered request's mark covers it
  (answeredCovers, which deferredWriteDone now shares); a row stored and
  not presented keeps its wake. WritePlacement and CoalescingPlacement gain
  messageId and presented; CoalescerHost.wakeToRead takes the placement,
  and the render arm also takes a stored render not presented to its agent.
- isUnreadStoredMessage judges each reader by covers in its own context,
  and its readers are the slot's readers and the presentation index's
  holders that aren't legacy windows; removePlacement refuses a row any
  presented window holds; resolvePlacement finds the landed copy by its id
  and takes it only while the consumed mark doesn't cover it.
- releasedWindow reads its mark through coverTest: a legacy mark on another
  branch keeps branchesPartAt, and a presented one there can't say, so
  every notice goes on. passNoticeToPrimary stamps the notice for the
  primary alone.
- In presented mode isNewestIn reads the window's order and the rows for
  the agent not yet presented, lastClockOf reads the window, and
  draftsNamedInHistory leaves out a notice undone out of the window
  (windowStatusOf).
- unsentInjection reads the record by sequence, which stays right under
  store-at-once: it counts only the agent's own stored injections. Its doc
  says so, and a test pins it.

Tests: presented-watermarks (the trap: a row stored after the write-down
and before a compile, never presented, stays unread and keeps its wake; a
row presented to mira alone stays unread after oren compiles; the
coalescer keeps a row the subconscious's window holds and removes one no
window holds; a framing holder reads a held row; rowDone over stored ids;
isNewestIn, lastClockOf and drafts in presented mode beside the legacy
answers; the primary-only stamp; unsentInjection; build-time pins on the
opaque getters). A mutation run reverted each of the length in readMarkOf,
the holders join, the removal gate, the three window reads and the stamp,
and the test for each failed. route-failure-targeting gains the presented
release case. Ported for the opaque mark and the rename, with their
assertions unchanged: heard-mid-turn-wake (and the readsRowFor table),
prose-drafts, route-failure-targeting, unsent-compiles,
mid-turn-injection-as-compiled, deferred-window-only, mcpl-coalescing
(wake literals naming queued renders become their held handles),
silent-heartbeat-rows, turn-id and code-execution-isolate-scripts.
test/helpers/read-mark-rows.ts reads a mark's head from describeReadMark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the flag, own rows at write, undo and redo on the window

#291 gives each agent a window of its own over the one record. AF-2 built
the presentation module and AF-3 moved the read marks onto it; nothing in
the framework presented yet. This wires it (AF-4): with
FrameworkConfig.presentationIndex on, a write is stored at once and enters
each reader's window at that reader's presentation points, never inside a
turn, and the #795 and #291 acceptance tests pass as plain tests. With the
flag off every path is the one it was.

- Registration and boot. Each agent is taken on as it is registered
  (adoptPresentation at createAgent, the subconscious, a conversation fork
  and an ephemeral run's registration): its window is written down, its
  own rows stored and not in it presented, its own-row sink installed, and
  a listener weighs each row of another writer's that enters at a start,
  an end or at rest as its open turn's arrival (noteArrival), as the
  flushes weighed what they stored. forgetPresentation at both
  deregistrations. create() sets the quiesced flags before the
  boot-recovery flush, and runs repairAndRest before reconcileAgentRegistry.
- addMessage under the flag never defers: it stores at once and calls
  afterStore, which presents to each idle reader. Only a surgery's hold
  keeps a write out of the store, in memory (surgeryHeldWrites), replayed in
  arrival order through the ordinary path at the release. A write stored
  where today's deferral would have queued it is placed with presented:
  false, never for the assembly bypass, so readsRowFor gives its wake a
  readsRow. The event clock is written only when no turn of the agent's is
  alive or its start is assembling. The fork writes go through storeInFork
  (held during a surgery, else stored and afterStore).
- Starts and ends. presentAtStart('turn-start') and ('assembly') beside the
  flushes they replace, and ('compile') before every compile; presentAtEnd
  at the failed start, both withdrawn starts, the continuation drop, the
  turn's end, the puppet's end, the surgery's release and resume, keyed on
  the logical turn. A queue a flag-off run left is still flushed first.
- The tool boundary presents through presentAtBoundary (the round's rows,
  then the arrivals the stream can carry, cut and whole), renders what it
  presented, and hands off through handOff before noteRoundSent: a
  mismatch notes the stream's later rounds as unvouched. A quiesce freezes
  a draining turn's boundary (answer 2).
- Own rows at write. The pending-round framing, a conversation fork's seed,
  the clock, stored injections, the routing notices and wake lines go
  through storeOwnRow, and each landed-though-threw arm tells the sink.
  The closure prompt, the puppet's replaced-reservation pair and a stop's
  marker once its stream no longer owns the agent pass present: false.
- sharedSlotBusy counts only readers the index doesn't present for, and
  placeWakeLines writes each woken resident's own line at its own start
  (placedWakeLines keyed by agent).
- Undo and redo on the window (decision 4, answer 3): refused while a turn
  of the agent's is alive; the tail at the checkpoint's indexLengthBefore
  (converted from its sequence for an older checkpoint) leaves the window,
  own rows undone and others' back to arrived; redo first retracts what was
  presented since, then presents the tail in order. No branch moves; a
  window:undone trace, and the API server broadcasts window:undone in
  place of branch:switched. TurnCheckpoint gains indexLengthBefore and
  windowBranch in presented mode. WorkspaceModule.useWindow lets the
  head-move notice follow an undo that moves no branch, and
  Agent.unsentInjection passes over an undone copy.

Tests: held-for-another-agent (#795) is a plain test now, and the new
presentation-two-residents is #291's reproduction (window = wire per
stream, the cache prefix, signatures kept, decision 1, windows grow only at
their end; it fails at window = wire without the flag). The new
presentation-points covers store at once, the clock gate, present at rest,
a row written past the framework at the next start, arrivals weighed once,
the logical turn's end, the puppet, the surgery hold, the quiesce, a fork's
store, the closure prompt, undo and redo, lazy checkpoint conversion, the
flag-off checkpoint, the narrowed shared-slot gate and wake lines per
resident; a mutation run on dist reverted each wiring point in turn and a
case failed for each. mid-turn-injection-as-compiled runs whole with the
flag on too, and workspace-direct-notice and mcpl-coalescing gain flag-on
undo twins. audience-stamp-writers lists the direct writes left and gains a
guard that classifies every own-row writer by when it writes. Ported:
presentation-flag (the flag-on run is presented now), presented-watermarks
(legacy controls boot without the flag; the framing case takes its mark
after the framing), and presentation's cut-short case for CM-9's batched
window writes, with a batched twin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…scious on its own window, and a reader fork's framing presented to it

#291's AF-5. A message a tune-out holds was deferred behind its target's
live turn like any write, so the reader that is there while the resident
is busy read a store that didn't have it yet (#793's P1). And the
persistent subconscious read the residents' slot as a live merge, so a row
the slot took during its own turn landed inside that turn.

- Held writes stored at once, flag on or off: addMessage stores a write
  that carries a tune-out's hold whatever its target's turn, except while
  the host is quiesced, under a surgery's hold, or behind a held write
  still queued for the same target. No resident is shown it, so it lands
  inside no resident request. Stored during the target's turn it takes no
  clock row. P1 passes with the flag off and on.
- The subconscious's window, flag on: it keeps its isolated slot with the
  residents' slot merged in, which is the record its window is presented
  from. The residents' rows enter its window at its own points by its
  observe edge (AF-4's points), and its share of sharedSlotBusy is already
  gone once it presents.
  - Its window start is an index ordinal, fixed once per tune-out
    (SubconsciousWindowAnchor, window-anchor.ts) once no row it is owed
    from before the start is out of its window: just past the last such
    row. The coordinator keeps the ordinal (subconscious/<p>/window-starts,
    subconsciousWindowFixed) and sets it again at a later enter or a
    restart (anchorSubconscious), clamped and traced when a shorter window
    can't hold it. A cancel forgets it. In legacy mode the start goes by
    sequence, as before. setSubconsciousAnchor takes a
    SubconsciousWindowStart.
  - A resident's rounds go out to it as a transcript (foreign-rounds.ts,
    answer 13), in compiles and in what its boundary injects: a line per
    call and per result, no thinking. A row heard from the residents' slot
    is noted by its window's copy (windowRowOf), so the subconscious's
    later thinking is kept.
  - A self-cancel gets a report turn when some of the epoch's stored
    messages are ones it hadn't been shown (unseenBySubconscious): with the
    index, those its window lacks; else those its consumed mark doesn't
    cover.
- A reader fork's framing: invokeReaderFork records the stored held ids it
  hands (noteFramed), and adopt presents them into the fork's window when
  its run takes it on. The fork holds them while it lives, and its
  inherited view keeps them out of its compile.
- Open shapes: residentContextOptions, subconsciousContextOptions,
  residentViewFilter, subconsciousViewFilter and ContextManagerOpenOptions,
  exported for HOST-1's audit open and used by the live opens. A reopened
  ended agent takes the view its live one had (liveViewFilterOf), which
  also leaves out the other residents' stored injections; before, a
  reopened resident or subconscious compiled them.
- The anchor counts an active all-except hold at a channel's enter, which
  used to move it forward past the hold's start.

The coordinator's queued half (getDeferredMessages, QueuedHeldMessage,
the cancel's held rows) stays until AF-6: a flag-off quiesce still queues
held writes.

Tests:
- New: tune-out-subconscious-p1, which has P1a/P1b with the flag off and
  on, the thinking pin for a held row stored mid-turn, and P1', the window
  with its thinking kept and the transcript.
- New: subconscious-window-anchor, cases a-g and a unit test of the anchor;
  tune-out-reader-framing-presented; context-options, which covers the
  shapes, the host open equal to the live one, and the reopen view; and
  foreign-rounds.
- Rewritten for stored-at-once, each with a comment saying why:
  admission-e2e gaps 5, 6 and 7 (plus a quiesce twin of gap 7),
  module-rows-held-from-residents' five mid-turn cases,
  tune-out-reader-fork-handoff as the fork-mode P1, and tune-out-e2e's
  backlog check moved to the wake request.
- clock-rows gains the held-mid-turn clock case.
- A mutation run on dist reverted each wiring point in turn, and a case
  failed for each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y, and a one-release importer for a queue an earlier release left

#291's AF-6. With AF-4's points wired and AF-5's held writes stored at
once, the deferral queue only served the flag-off path. It goes, and with
it the flag: every agent's context follows its own window.

- Presentation is mandatory: FrameworkConfig.presentationIndex is removed,
  and create() refuses a context-manager without presentRows. addMessage
  stores every write at once and returns its stored id. Only a live
  surgery's hold keeps a write in memory, returning '', until its release
  stores it in arrival order. A write stored while its agent's turn is
  alive is marked `presented: false`, as before under the flag.
- Deleted with the queue: the deferral branch, fromQueue and
  bypassDeferralFor, the drain, flushes, cuts and read-backs, the persisted
  queue and quiesce's write of it, stop()'s drains, the flag-off arms
  (flushForStart, readdDrained, noteForOtherResidents, sharedSlotBusy,
  slotHeldFrom, which is now the quiesce alone, the [continue] gate and the
  legacy branch undo), and the placements' deferredId. `assembling` stays.
- The coordinator's queued half goes (getDeferredMessages,
  QueuedHeldMessage, readerHandedWrites, the cancel's tune-out-cancel-held
  rows, ReaderInvocation.queued, queuedReaderRows). queuedCount is
  deprecated and never set.
- Module API on stored ids: asModuleRow(row, window) marks `held` a row the
  window of the agent the read is for doesn't hold. queryMessages answers
  store order with the store's count. A `held:` handle from an earlier
  release still reaches its landed row for one release (landedRow).
  module-registry records the stored id.
- The importer (src/deferred-import.ts reads the record;
  importPersistedDeferredWrites stores it). It runs in create() after the
  quiesced flags and each agent's write-down, and before repairAndRest,
  reconcile and MCPL. It stores each entry once, in seq order, under its
  deferredWriteId, and skips what landed. landing:'unknown' entries are
  looked up across the branch. A gone target's notices go to the primary.
  Older entries are stamped, a puppet pair as its agent's own. It wakes for
  tune-out-cancel, releases mail, syncs, and then clears the record or
  keeps the cut entries for the next boot.
- Boot wakes for a tune-out's rows still out of their agent's window (#232
  gap 6), which the restored queue used to give
  (wakeForUnpresentedTuneOutRows).
- A scheduled start that resumes a logical turn keeps a fresh wake out of
  its batch while arrivals wait outside the window, so the wake starts a
  turn that reads them.
- The coalescer takes back a row no reader consumed, presented or not, and
  keeps one a reader fork was handed as framing.
- Reconcile counts a result its recipient's store holds as handed over
  (resultStoredFor): mail is released once stored, so a mid-turn
  recipient's result is no longer held mail at a crash.
- Deprecated, kept one release: HostModeStatus.deferredWrites (what a
  surgery holds), QuiesceBlockedError (never thrown),
  FrameworkConfig.deferredWritesPath. Removed: undo:completed and
  redo:completed, puppet:tool-call.deferred, tune-out:reader-fork.queued.

Tests:
- New: deferred-import (the reader) and deferred-import-boot (the importer,
  with crash children in helpers/deferred-import-crash-child.ts, mail
  release and a coalesced render published before MCPL restores).
- New: presentation-mandatory, no-deferral-queue (a guard: src names none
  of the queue's machinery outside the importer), and admission-e2e's two
  gap 6 boot cases.
- Deleted with the behaviour: deferred-write-failures,
  deferred-malformed-unknown-landing, presentation-flag,
  helpers/quiesce-crash-child, and the queue's cases in deferred-window-only,
  host-quiesce, route-failure-targeting, module-held-writes, mcpl-coalescing
  and others.
- Ported, each with a comment saying why: tune-out-reader-queued is now
  tune-out-reader-backlog, and so on through the synthesis's list.

Changelog: 291-deferral-queue.breaking, 291-presentation-mandatory.breaking
and 291-deferral-import.added are new, and the pending 291 fragments are
rewritten without the flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, history marked for its caller, and a rollback counted at its cut

Agent-framework's part of #291 for the code that reads the record or
removes rows from it. This is AF-7.

A removal from the record left the row's place in every window but the
one of the context it went through (CM-T): context-manager tombstones a
removal in that window only, so another resident's compile left the row
out while its index length, and the places its read marks are kept by,
still counted it. AgentPresentation.retractRemoved takes the removed rows
out of every other live window with context-manager's retractRemoved
(CM-8), each at its own place, and traces presentation:retracted with the
reason. It runs after a hide, a suppression on its fork, a module's
removeMessage and a process answer's removeMessages, the coalescer's
removal, a suppression a start finishes, and a refusal rewind's shed of a
window that is still the record. A window on a branch whose record still
holds the row (a fork) keeps it, a window that is still the record is never
written to, and a window that can't be repaired is logged while the
removal stands. At start, after the pass over every window,
retractAbsentRows takes out the rows no live context heard removed, such as
an offline recovery's suppressions. stored-removal's removeStoredMessage
returns the ids it removed, bodyGroupRun its run's ids, and the new
storedIdsBetween reads an interval's ids before its removal.

On a presented window, shedNewestTurn sheds from the agent's own window, in
its order, with retractRows({ undone: true }), and never touches the
record: no other agent's window, cache prefix or signed thinking changes,
and a system change, a row held from residents and another agent's stored
injection are passed over. An undone row is in no list a presentation
point presents from, so it stays out across a restart and a redo of
another turn. On a window that is still the record the shed removes from
it, as before.

The history tools mark each extract, search and semantic_search entry for
a row the caller's window doesn't hold (window: 'not-shown' | 'undone'),
through the new ModuleContext.windowStatusOf, and for an agent's call
leave out another agent's own rows its window doesn't hold unless it
observes that agent (decided answer 6), counted as leftOut. A call no agent
made stays record-wide, marked for the delivery agent. Without the lookup
every tool and answer is as it was. ModuleContext.observes says whether one
agent reads another's own rows through an observe edge.

A rollback's accounting is the cut's (branchSequenceOf, CM-7): planRollback
counts only the rows stored after the write that presented the target, and
reports returnedToArrivals, the rows the cut keeps that leave the window
(decided answer 5); a rollback that changes nothing is refused saying so.
previewSurgeryMarks gains neverPresented and returnedToArrivals. The host
command's message-granular undo and offline recovery's count mode count
back in the window, and offline recovery counts, marks and checks the tool
pairs of the rows after the cut only. save_recent_image's tail walk skips
rows the agent's window doesn't hold, and the request-only row index is
memoized on the window's rows. The Discord and history comments that still
described held writes and undo by branch are rewritten.

Tests: stored-removal, presentation-removals, shed-window,
history-window-marks, rollback-cut-accounting, offline-recovery-presented,
and a save_recent_image case; each fails on the code before. Ported, each
with a comment: operator-surgery, surgery-marks-receipt and two
maintenance-timer rollbacks now present each row as it is stored, as a
write for an idle agent is, since a rollback to a row presented with every
row after it removes nothing; the operator-surgery rewind cases and
inference-failure-bounds' shed assertions read the window, which the shed
now leaves the record out of. Mutation checks: each wiring and each rule
fails at least one of the new tests when taken out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r's crash cases, the flag-off twins gone, and the #291 reproduction with the persistent subconscious

#291's AF-8. AF-6 took away the deferral queue and its flag. This change
checks that the tests no longer lean on them either, and adds the tests
the synthesis left for the end. Only tests change.

- Residue guard: no-deferral-queue.test.ts is now no-deferral-residue.test.ts.
  It scans test as well as src. Beside the queue's names, it now refuses
  two more things in test:
  - the released window read, typed or stood in for with the queue's held
    set as a second argument;
  - a `held:` handle, as the prefix alone or with an id. Only
    module-held-writes may use one, since it tests, for one release, the
    handle an earlier release answered.
  presentation-mandatory may still name the removed flag. The list of gone
  names now also has DeferredWrite, DrainedWrite, readsDeferredFor,
  resolvePlacement, refuseUnsettled, storedCopiesOf, logDeferredWriteError
  and passOnFromReleasedWindow.
- Residue fixed: route-failure-targeting and stop-step-throws read the
  released window for the agent alone. module-query-for-agent and
  ephemeral-settle checked that a write's id was not a handle; they now
  check that it names a stored row.
- Boot repair after a crash: helpers/own-row-crash-child.ts stages two
  hard exits. In the first, an own row is stored and the process exits
  before its window entry is written. In the second, rows are stored for an
  agent while its turn is alive. presentation-crash.test.ts reboots each
  store:
  - The own row is presented to its owner alone, after what her request
    carried, and traced as one repair. Her next request carries it, and a
    later boot repairs nothing.
  - The arrivals are still arrivals, read from the index. A quiesced boot
    keeps them out of her window, and resume presents them after the
    crashed turn's rows. A serving boot presents them at its rest pass. A
    row the idle resident was shown before the crash is in his window once.
- Flag-off twins deleted. Each deleted twin's checks were folded into the
  twin that stays:
  - workspace-direct-notice's branch-move undo and redo, into its window
    cases;
  - puppet-tool-call's two presentation cases, into presentation-points';
  - host-quiesce's stranded-queue boot, into deferred-import-boot's
    serving boot.
  quiesce-crash-child and admission's deferredMessages went in AF-6.
- The #291 reproduction with the persistent subconscious:
  presentation-two-residents runs the same overlapping turns with the
  subconscious serving mira and a tune-out active. The subconscious's own
  turn is alive across both residents' turns: its stream is held through
  the new WireMembrane.hold. Its checks:
  - The residents' windows are as they are without it, except for the
    receipts their turn ends write on a host with a channel registry.
  - Its window takes no row inside its live turn, except the rows its
    stream was handed at its boundary.
  - Its second request starts with its first and carries its turn's
    signed thinking.
  - The residents' rounds reach it as a transcript.
  - Its window start was fixed at the enter, past a row stored before it,
    and stays put.
  The base case and the variant share the run and the residents'
  assertions.

Tally, against AF-7's full run (7718 tests): the residue guard has 2 more
tests (2 to 4), presentation-crash adds 3, and the two-residents variant
adds 1. The twins remove 5: 2 in workspace-direct-notice, 2 in
puppet-tool-call and 1 in host-quiesce. That is a net of +1, so 7719. No
other file's count changes.

Mutation checks on the compiled code, each caught:
- the own-row repair turned off fails the own-row crash case;
- rest presenting arrivals while frozen fails the quiesced crash case;
- the subconscious presented at rest with its turn alive fails the variant
  (and only the variant);
- the anchor fixed again at every presentation fails the variant;
- residue planted in a test helper fails the guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s strategy and its window

A move of the store's branch moved it under every agent sharing the store,
but only the acting agent's context, or none for switchBranch, initialized its
strategy again. Every other agent with AutobiographicalStrategy then failed
each turn ("requires reinitialization for the current branch generation")
until the process restarted, so a host's /checkout, /restore to a branch head,
or a co-resident's rollback left agents unusable. And the presentation index
wrote each window down once per manager, at registration: a move to a branch
made before that write-down (any branch an earlier release's store holds, or a
rollback into rows the write-down presented, which branchAt cuts at their own
sequence) left the agent in legacy mode for the rest of the process, where a
write during its turn entered its window at once and undo was refused,
blaming a write-down that had worked (J-AF1).

Now switchBranch, rollbackToMessage, suppressMessages' fork and a failed
surgery's restore call followBranchMove under the store reservation. It
initializes every other live context again on the store's branch, all at
once, through context-manager's reinitializeOnStoreBranch (FW-CM-2), which
starts no branch generation of its own. Then it rebases every agent in the
presentation index: a window the new branch has none of is written down
(traced as presentation:presented at the new 'rebase' occasion), and the
agent's own rows not in its window are presented. A context that can't follow,
or a window that can't be written down, leaves its agent in legacy mode until
the next move, logged and traced as presentation:failed at 'rebase'; the move
stands. Arrivals still wait for the reservation's release. Every agent's redo
entries are dropped at a move. create() refuses a context-manager without
reinitializeOnStoreBranch. The presents() doc and the undo refusal no longer
blame only a write-down at registration.

switchBranch takes { requester, note } and writes the switch to the operator
log as a checkout, or with its error when refused or failed; the API server
logs one over its surface (J-H7).

Tests: test/branch-move-resync.test.ts, all nine of which fail on the code
before. Two autobiographical residents serve a turn each after a switch, a
rollback, a suppression and a failed rollback's restore. A rollback to a
pre-upgrade row keeps the window presented. A switch to a branch made before
the write-down writes it down. A context that can't follow is traced and
recovers at the next move. Redo entries are dropped. A refused checkout is
logged. The API test for concurrent switches now starts the second switch as
the first returns: a switch holds the store across an await now, so one
started alongside it is refused as busy. The awareness outbox's stub framework
gains the redo stacks and a null presentation.

FW-AF-1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ls, refuse another branch, survive a cut-short undo, and give back marks

A redo took out as "presented since" every row past its undo's index length,
which the rows an earlier redo put back now were: two undos and two redos left
the window [first, two, one, second], the compile sent that order, and 'one'
ended out instead of undone (J-AF2). The redo entry now keeps the ordinal each
undone row held, and each redo notes, in every undo still below it on the
stack, the rows it put back that were in that undo's window right after it
(`kept`); those stay where they are, so undos redone in turn give back the
window they took, in its order.

redo() ignored the branch its undo was on, so a redo after a store move used
one window's ordinals on another's (J-AF3). FW-AF-1's followBranchMove already
drops every redo entry at a framework branch move, and is the one place that
clears them; redo() now also refuses (OperatorActionError 'failed', naming the
undo's branch) when the store is on another branch, as after a move made past
the framework, and getUndoRedoState's canRedo is false there.

Undo's two retractRows calls weren't atomic: a throw from the second left the
agent's own rows undone with no redo entry, which nothing ever presented again
(J-AF5). The redo entry is now kept before either call. An undo cut short after
rows left is refused with OperatorActionError 'failed', traced and logged with
its error; its checkpoint stays for another undo to finish it, and a redo takes
out what is still in the window of the undone rows and puts the whole tail back
in its order. A redo puts a checkpoint back only where it isn't already. An
undo that took nothing out throws its error and keeps nothing, as before.

A redo put the rows back at new ordinals past every mark, so a render the
redone reply answered read as unread and unshown, and the coalescer could take
it back from under the reply (J-AF6). The redo entry records which undone rows
each of the agent's marks covered by ordinal; a redo gives each mark the agent
still holds again (Agent.replaceReadMarks, read-marks' readMarkThrough),
covering the window up to the first row put back that it didn't cover, so what
was read reads as read, shown and answered again and a row presented since the
undo doesn't. removePlacement now also refuses a row a reader has read,
whatever asked.

After an undo the next start skipped the injection the undone turn had stored,
because Agent.renderedInjection still counted the undone copy, and the request
carried the stale one (J-AF4). renderedInjection now keeps each rendered copy by
id and passes over one an undo took out or the store no longer holds.

Tests: test/window-undo-redo.test.ts, nine cases, eight of which fail on the
code before (the ninth guards the new entry-keeping: an undo that took nothing
out keeps no redo). The reviewers' probes undo2redo2, redo-branch, inject-undo
and probe-redo-unread now give the original order, no redo off its branch, the
current lesson in the next request, and the render read and kept. The
presentation-points.added and presentation-mandatory.breaking fragments say what
undo and redo now do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…for it

A fork's or a run's release told the primary of every passesToPrimary row
in its store that its consumed mark did not cover (J-AF7). A shared fork
reads its parent's slot on a branch of its own and inherits its parent's
window, and a resident reads the slot every resident writes. So a notice
there for the parent or a co-resident, which never entered the released
agent's window, was never covered by its presented mark, and every shared
fork's release (a deriveAgent default, or a reader fork the subconscious
spawns) told the primary of it again, in the fork's name.

releasedWindow now walks only the rows the audience rule does not put
elsewhere for the released agent: its own rows, rows whose audience names
it, and unstamped rows stored after a shared fork's derivation point. The
reader facts it judges by are taken from the registry when the release is
asked for (recordedReaderFacts reads the live record, else the newest ended
one, since a stopped agent is ended before its release and a postponed
release runs after the run's end). The cover test, the body joining and the
branch partAt reading are unchanged.

Tests in presented-watermarks: a co-resident's read notice is not passed on
by a shared fork of the primary, by three successive forks, or by a reader
fork; the single-resident race leaves the parent's own unpresented notice
to the parent, once; a co-resident parent's own unread notice goes on once,
in its own name, when it is stopped; an unstamped notice is a fork's only
when stored after its derivation; and a notice for the fork itself, stored
after derivation and never read, still goes on once. A presentation-audience
case covers recordedReaderFacts for ended agents. The 291-reads-row changed
fragment states the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… row keeps its place, the boundary never throws, and a store without batches is named

A row addressed to the subconscious alone (a module's process response or
an operator's message with targetAgents [Subconscious]) is stored in the
residents' slot, stamped for it. observedRowFor showed an observer only
rows whose audience named an owner of the slot, while audienceFault
counted observers as readers, so the row reached no window, was traced
nowhere, and the subconscious was woken to a request without it; the
merged view showed it before the index (J-AF19, DQ-5). observedRowFor now
also shows a row that is nobody's own row and whose audience names the
observer itself; an own row is decided by its owner alone, so the
observer's own row in the slot it watches stays out. audienceFault is
unchanged and now agrees with it: a row readable through an edge is no
fault, and one stamped only for an agent that neither owns nor observes
the slot still is.

An own row whose presentation at its write failed was presented later,
after the rows written after it: one transient presentRows throw on a
round's reply left the window [tool_result, tool_use] for good, the
hand-off unvouched, and every later compile sending the result before its
call (J-AF20). presentOwn now keeps, per manager, the rows whose write
presentation failed and presents them, in record order, ahead of the rows
it is given at the agent's next call, while they are still out of the
window (windowStatusOf 'out': a row that entered elsewhere, was undone or
left the record is dropped). A row held back with present: false is never
kept. A turn start or a tool boundary settles the kept rows by its own
sweep, so a request compiled or a stream handed its rows without one never
finds it ahead of a reply it didn't come before.

presentAtBoundary and handOff had no catch, unlike the module's other
entry points: a store read error at a boundary escaped into the boundary
handler after the reply and results were stored and before
provideToolResults, and the turn hung (J-AF24). Both now trace
presentation:failed and return heard [] or vouched false.

A write-down whose record landed and whose call then threw (the
snapshot after a write failing, J-CH1's path) pinned the agent in legacy
mode for the process, its sink uninstalled. writeDown now reads the mode
again after a caught error, and a window that reads as presented is taken
as written down, with a log line (J-CH1, the agent-framework half).

create() now warns once, by console.warn and an ops:alert of kind
chronicle-per-row-window-writes, when the store handle has no
appendManyToStateJsonWithIdentity, naming the chronicle it loads and the
per-row write-down cost (J-CM9, the agent-framework half). package.json
declares @animalabs/chronicle ^0.5.0 and @animalabs/context-manager
^0.23.0, the releases that carry the batched append and the batched window
write, so the gate is mechanical (DQ-6); no install was run, and a new
breaking fragment says installs resolve only once those releases exist.
The context-manager 0.22 fragment names the ^0.23.0 range, and the
presentation-index and presentation-points fragments say what changed.

Tests: presentation-audience (the observer's row is its arrival and no
fault; an end-to-end framework with the subconscious whose wake request
carries a note addressed to it, ported from the review's
p6-framework-sub-target probe, and a worker-only write still traced),
presentation (the review's p1-order probe as a test, kept rows dropped
once in or undone, a failed turn-start sweep settling them, a write-down
that throws after its record, a boundary and a hand-off whose reads
throw), presentation-points (a framework turn whose boundary read throws
completes and the row enters at the turn's end), presentation-mandatory
(the batched-append warning once, and none with it). Each new test but the
three kept-row hygiene cases fails on the unfixed code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…left unread, and the importer stamps a passed notice for the primary

Mail reaching a recipient mid-turn is stored at once and its hold released
once the store has synced the row, so a process that ended before the
recipient's turn did lost the delivery's wake: the next boot had no held
mail to redeliver, resultStoredFor counted the stored result as handed over,
and the row entered the window at rest with nobody woken for it (J-AF8). A
new boot pass, wakeForUnpresentedMail, runs beside the tune-out one before
repairAndRest: each agent the index presents for is woken once per sender
and kind (agent-<kind>:<sender>, source dendrite) for mail it is owed and
its window doesn't hold, named for it by dendrite.to (or, on a row stored
before mail named its recipient, by its audience). Mail already in the
window asks for no wake. docs/dendrite.md's restart and mid-turn lines say
so, and the importer's changelog fragment names the new boot wake.

The importer passed a gone run's or fork's stamped module write to the
primary under the stamp it was queued with, so no window took it while the
log said it was passed (J-AF9). The notice is now stamped for the primary
alone, as passNoticeToPrimary stamps one, and on a host with no primary as
a write that names no agent.

Tests: dendrite-lifecycle's ported crash case asserts the lost wake is asked
again, that a request of mira's carries the result, and that a third boot
asks no wake (the p1b-result-wake probe); a message twin; and a result
already in the window before the crash asks none. deferred-import-boot
gains the p2-gone-target-stamped case, a version-1 record of seq-less
entries at a serving boot storing exactly those writes (J-AF18), the
imported mail's wake, and the crash cases take a 600 s timeout, since the
2,101-write one ran past 120 s on a loaded full-suite run (J-AF38).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…get, its returned arrivals wait for the next start, and /undo n counts the agent's own slot

J-AF10: a rollback to a row that one write presented together with the
rows after it (a batch) keeps those rows after it, since the cut is at
that write, and nothing said so. The result, the preview, the operator
log and the host's undo answer now carry keptInWindowAfterTarget, and a
rollback that only such rows follow is refused saying the target was
presented in one write with them, naming the first, and pointing to
suppressMessages or the host's hide. Before, it was refused as already
the tail of the window, with no row after it, while one was. The plain
'already the tail of the active branch' stays for a target nothing
follows.

DQ-1 (J-AF11, as the thread decided): the rows a rollback's cut returns to
arrivals wait outside the agent's window until its next turn start. The
presentation module holds them (holdUntilStart) at every point at rest:
the surgery's release, a resume, a turn's end and a row stored for the
agent while it is idle. Before, the release presented them again at once,
so a rollback that only returned arrivals changed nothing the agent saw.
The hold is in memory: a restart presents them at its pass at rest.

J-AF14: the host's message-granular /undo n counted back over every row
of the window, which for a persistent subconscious merges the residents'
slot, while a rollback looks for its target in the agent's own slot. It
picked a residents' row ('not on the active branch') or called its own
row under a newer residents' row already the tail. It now counts the
rows of the agent's own slot, as it did over the record, and offline
recovery's count mode keeps the same rule. A rollback named at a row of
an observed slot is refused as such, not as off the branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…out of a caller that doesn't observe it, and a row counts as shown only where the caller's view keeps it

J-AF12 (DQ-2, as the thread decided): the history module is bound to the
primary's context, whose summaries fold the primary's window, its own
rounds and notes among it. overview returned each summary's text to every
caller, and semantic_search returned summary hits with no check, so a
co-resident that doesn't observe the primary read her thread in
summarized form. Now, for an agent's call that is not the bound agent's
and doesn't observe it, overview keeps each summarized span's time, level
and counts and leaves out its text (content: null, contentLeftOut: true,
counted in summariesLeftOut), and semantic_search leaves out summary
hits, counted in index.leftOut, and doesn't search again deeper for a
search of summaries alone. The bound agent comes from a new
ModuleContext.agentNameOf(contextManager); an agent's call where it can't
be told leaves them out too. An operator's call, the bound agent's own
and an observer's (the subconscious's) are as before. Serving such a
caller its own context's summaries is later work.

J-AF13: the 'in the caller's window' exception read windowStatusOf, which
answers before the view's filter, so another agent's stored context
injection or a held row in a resident's written-down window came back
unmarked and not left out, though the caller's requests never carry it.
A row now counts as shown only when its window holds it and its view
keeps it, through a new ModuleContext.viewKeeps(row, { forAgent }) that
reads the view the agent's context was opened with. The injection is
left out and the held row marked not-shown. A stored injection from
before the audience stamp counts as the own row of the agent its mark
names. Tool descriptions, the module header and the 291 fragments say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne-out that ends under a surgery's hold reads the writes the hold keeps, the subconscious's window start survives a restart after the oldest tune-out's cancel, and its own voice is its name alone

FW-AF-8, review 2's J-AF28, J-AF29, J-AF30 and J-AF34.

J-AF28: a reader fork is framed at its derivation, so a run that refused it before registering it (the host quiesced or stopping, a surgery holding the store) left its framed ids in the presentation index for good, and the row counted as held by a fork that never ran: framedToAny and holders named it, and removePlacement refused the coalescer's replace or withdraw. invokeReaderFork now drops the framing of a framed fork that is not live at its end (AgentPresentation.forgetFramed). The reviewer's probe is a test in tune-out-reader-framing-presented.

J-AF29 (#232 gap 7 under a surgery's hold): a write while a surgery holds the store waits in memory until the release, and a tune-out that ended during the hold read the record alone, so its dump left the message out and the release stored it under the ended hold, where no resident was shown it. The coordinator takes a new optional hook, getSurgeryHeldMessages, which the framework serves from surgeryHeldWrites for the primary's store, and cancel, cancelAllExcept, scopeHeldCounts and check read it after the stored messages. A reader is still handed stored rows only, which have ids. Tests: an admission-e2e 'gap 7 (surgery)' twin and coordinator unit cases for a cancel and for the all-except release.

J-AF30: a cancel of the oldest tune-out left the subconscious's window start at its ordinal while another tune-out stayed active, but a restart set the start of the oldest still active, so the rows between the two starts left the window and its prefix changed. The window-starts state now keeps the start in force (inForce, an optional field of the same v1 state), and a resume sets it again. The start still does not move at the cancel, so the subconscious's report on the cancelled tune-out still reads what it held: the synthesis's other choice, a re-anchor at the cancel, would have cut those rows out before that report. Test (h) in subconscious-window-anchor.

J-AF34: the subconscious's config spread the primary's and kept its selfParticipants and presentAs, so a row stored under the primary's alias went out to it raw as its own turn, the primary's thinking and calls with it. Both are now unset for it. Test in foreign-rounds.

The six new tests fail on 6afd7b0 and pass here. The full suite passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…catch-up

main's 0.27.0 and the 28 pull requests after it, by way of feat/dendrite's
ninth catch-up, meet the presentation index. Thirteen files conflicted:
src/framework.ts (51 hunks), src/agent.ts, src/gate/event-gate.ts,
src/modules/history/index.ts, src/tool-result-guard.ts, src/types/agent.ts,
six test files and one changelog fragment.

The branch move. #353's re-initialization (reinitializeManagers: every
manager's strategy set again, one at a time, its failures alerted, emptied
forks disposed) is now the mechanism of every move, and the fix wave's
resync (cd93606) is rebuilt on it: followBranchMove calls
reinitializeManagers, then rebases each agent's window on the new branch
(written down where the branch has none) and drops every redo entry, and
returns the failures for main's reportReinitFailures. What of cd93606
duplicated #353 is removed: its own reinitializeOnStoreBranch pass with its
retries, create()'s refusal of a context-manager without that method, and
three tests of the co-resident's strategy after a rollback, a suppression
and a failed restore, which #353's tests already make. switchBranch keeps
the fix wave's checkout logging around main's reinitializing/report shape.
A rollback follows the move right after its switch, so the agent's window
is presented on the new branch before holdUntilStart holds the rows the
cut returned; a suppression follows at its release, after the removals, as
main has it; restoreSourceBranch reports main's restore alert and leaves
the following to its caller.

Undo and redo stay on the window (decision 4): they move no branch, so
main's beginSyncMove and reinitializeAfterSyncMove, their syncMoveReinit
state and the maintenance and drain-kick arms that excused a tick an undo
moved the store under are not carried; an undo re-initializes nothing,
retracts, and every agent goes on compiling. The result types keep main's
optional settled, never set here, and the host /undo no longer waits on it.
This is either/or: main's undo reserves the store; this branch's undo
reserves nothing.

The queue's absence. stop()'s drain of the deferred queue (main reworded
its lines for a persisted record that is behind) and the release's flush
(main gave it a trace name) stay deleted. reportLongHold and
disposeEmptiedForks count the writes the surgery's hold keeps
(surgeryHeldWrites); the long-hold alert says those wait in memory and a
restart loses them. A manager that refuses its work after a failed
re-initialization keeps no queued writes here: a write for it fails at its
writer, or at the hold's release, and is logged, and the alert's remedy
says so (main keeps them queued until a switch recovers it: either/or).
The release now traces each write it stores, with its id (source
surgery-release), as main's flush did. writeToFork (decision 3) is rebuilt
on storeInFork: a fork's channel message or correction is stored at once
and presented at the fork's next point, and under a surgery's hold it is
kept in memory and replayed at the release, its placement naming the fork
with no id yet. The idle closure prompt stays the fork's own row
(storeOwnRow with present), the sweep waiting out a hold.

Wakes. main's readsStoredMessage (#814, a wake for a message stored as it
arrived) is folded into readsRow, which names a stored row already:
readsRowFor gives the stored id whether or not the row was presented at its
write, and rowDone is main's storedMessageDone. main's general split of a
batch that resumes a logical turn (its continuations alone, every other
wake back to the queue) replaces this branch's narrower split while
arrivals waited, and the autoContinue give-back beside it goes, as main's
split leaves no mark there.

The rest are unions: the imports and fields of both sides, main's manager
release beside forgetPresentation (as abec840 wired it), the subconscious's
voice with main's model variable, the hand-off's vouching beside main's
streamCarried, the puppet's own-row pair with main's stored flag, the
guard's notices with its own-row sink, the history search's matched count
with its caller view, and agent.ts's exported branchKey and main's
branchHead, which reads the store's head through a new
read-marks.storeHeadOf, as pendingWatermark is a ReadMark here. The
refusal rewind takes main's carried rule onto the window shed (only what the
refused request carried, a sanitizer-empty row passed over, and a held
result going with its carried call), keeps the shed's doc comment, and the
refused reply's removal leaves every other window. main's new cut-off
tool-block notice is written through storeOwnRow and the writers' guard
lists it (decision 7). changelog.d/context-manager-0.22.breaking.md takes
main's deletion: 0.27.0 released it, and 291-chronicle-0.5 carries the
^0.23.0 range.

Tests. The conflicted ones: heard-mid-turn-wake's and mcpl-coalescing's
stored-message wakes read readsRow; silent-heartbeat-rows takes main's
heldRestartMeets helper with this branch's expectation (a resumed turn
presents no arrival, so the message's wake starts its own turn), and
main's five further held-restart cases are read onto that;
stop-with-host-operations keeps main's refused second switch and this
branch's switch started as the first returns; stop-with-work-in-flight
drops main's persisted-queue record case; turn-continuation adds main's
no-further-turn check. test/reinit-after-branch-move.test.ts (#353) is read
onto this branch, a header saying how: the undo and redo cases are read
onto the window, the cases whose point is a move that holds the store make
it with switchBranch, held writes are '' or a placement with no id, and its
alert texts follow the new remedy; the fix wave's six window cases are
added to it as a describe of their own, and test/branch-move-resync.test.ts
is removed. Two of main's refusal-rewind cases read the window (the result
leaves the window with its call; the message stored while the refused
request streamed follows the marker); reasoning-loss-turn's line follows the
first reply in the worker's window, where it is presented, not in the
record, where it is stored at once; subconscious-model-fields counts
selfParticipants and presentAs among the subconscious's own fields, which
it sets for its own voice; compression-after-release's (#550) fork cases
present the backlog they write past the framework, as the fork's next
point would, since a registered agent compresses its window, and a write
made while quiesced is stored at once. main's changelog fragments for #353,
the undo reservation, settled, the stored-message wake and the continuation
split are rewritten for this branch, as are 291-branch-move-resync and
291-presentation-mandatory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e the removals, so no reader's summary keeps a suppressed row

F1 of the ninth catch-up's fix wave: AF1-1 and AF1-4.

AF1-1: the merge took main's order for suppressMessages: the fork, the
removals and retractRemovedElsewhere, and only at the release did the
other agents' contexts follow the fork. A co-resident's
AutobiographicalStrategy was still at the old branch generation when
retractRemoved told it the rows had left, so it refused onRetracted
('requires reinitialization for the current branch generation').
context-manager only logs that refusal, and the re-initialization at the
release then loaded the co-resident's summaries over the tombstones
already written. With two folding residents and 30 rows suppressed, the
co-resident's summaries still folded all 30, and its compile rendered
them 60 times. The tombstones were written, so a start's
retractAbsentRows finds nothing to repair.

suppressMessages now follows the fork as soon as it lands
(followed = await followBranchMove(agentName)), the way rollbackToMessage
follows its switch. The release reports `followed`, or else follows the
store to wherever a failed fork's restore left it. A suppression whose
removals fail clears `followed`, so the release has the other agents
follow the store back to the source. Two comments said otherwise and now
say what happens. The inline comment had claimed a follow at the release
loads the branch as suppressed. The JSDoc had claimed a suppression is
never retroactive over derived state. On a presented window, every
reader's strategy now drops each summary that folded a removed row and
folds the rows it keeps again; legacy mode is unchanged.
291-removal-propagation.fixed says so.

AF1-4: the window describe of reinit-after-branch-move gains five cases:
- A suppression through one of two folding residents (L1s and L2s): both
  windows read the rows absent, and no summary of either resident folds
  one, before or after the kept rows are folded again. Neither compile
  renders one, no strategy refuses onRetracted, and both residents serve.
- A failed rollback whose restore succeeds.
- A suppression whose removals fail and whose restore succeeds. In both
  failure cases each resident presents, is presented and is current on
  the source.
- A rollback, and a suppression, that drop every agent's redo entries.
The folding case fails on dcdeb41: oren's summaries fold all 30 rows.
The failed-suppression case fails with this fix less the clearing of
`followed`: oren's strategy refuses its turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…k waits in memory for its recovery, and a disposed fork's held writes go to the primary

F2 of the ninth catch-up's fix wave: AF1-2 and AF1-3, with the thread's
answer to question 1 (a write for a manager fenced by a failed
re-initialization waits in memory until a switchBranch() recovers the
agent or the agent ends, never failed at its writer, never dropped at a
release) and to question 6 (a held write is traced once, at the release,
with its stored id, as 'surgery-release'; never at its arrival with an
empty id).

AF1-2: a write the move's hold kept for an agent whose re-initialization
then failed was replayed at the release, refused by context-manager's
initialization fence, logged ("couldn't be stored at its release") and
dropped, though its writer had been told it was accepted; every write
for the agent after it failed at its writer. Main kept both in its queue
and stored them once a switch recovered the manager. Now addMessage and
storeInFork keep such a write in memory, per agent and in arrival order
(heldForRecovery): one the fence refuses (refusesUntilReinitialized,
matched narrowly on 'ContextManager.addMessage requires successful
strategy initialization'), and every later write for the agent behind
it. Its writer is answered as a held write is ('' and a placement naming
only the agent). followBranchMove, after reinitializeManagers, puts the
writes of each agent whose re-initialization didn't fail this time back
on the move's hold, ahead of the hold's own writes
(recoverHeldWrites), so the release stores them through the ordinary
path, each traced once with its stored id; one the manager still
refuses is kept again. No clock row is stored for an agent whose writes
wait. HostModeStatus.deferredWrites counts the kept writes, and a
tune-out's check and dump read the primary's (getSurgeryHeldMessages).
The test the merge had inverted to pin the loss is back to main's
meaning: after the recovery switch the held write, and the two made
before the recovery, are each stored once, in arrival order, in b's
window, traced once as 'surgery-release'.

AF1-3: a fork the move empties and disposes took its held writes with
it: the disposal only counted them, and the release replayed them for a
name no longer registered, so even a module's write for the fork, which
the primary takes in its place, was dropped. disposeEmptiedForks now
takes the fork's writes out (those kept for its recovery, then the
hold's) before disposing it, and passes on those passesToPrimary allows
through passNoticeToPrimary, held to the release as any write; the rest
are dropped, logged. An agent that ends while writes wait for its
recovery does the same at its end (releaseWindowNowOrAfterResume), and
the release passes on a held write whose agent has gone instead of
dropping it. disposalAlert says how many went to the primary and how
many were dropped.

Question 6: the arrival traces of a module's process response, a
resident's channel message, a push event, a script's wake and a prose
bounce no longer fire with '' (or 'deferred') for a write kept in
memory; the release traces it once.

The refused-work and restore alerts' remedy now says the switch
re-initializes the agent and stores the writes kept in memory for it
meanwhile, which a restart loses; the long-hold alert says a restart
loses those too. 326-reinit-after-branch-move.fixed and
291-deferral-queue.breaking say what waits, where it goes and how it is
traced; the module, trace and coalescer docs name the new hold.

Tests (reinit-after-branch-move, admission-e2e):
- the flipped case, plus a refusal first met at the writer (a process
  response for a refusing primary) and a module write during a hold,
  each traced once, at the release;
- a fork the move empties: its module write reaches the primary once,
  traced 'surgery-release', its channel messages are dropped, and the
  alert counts both; writes kept for a refusing fork that a later move
  empties (its re-initialization succeeding, or failing again), and for
  one that ends by stopAgent, go to the primary the same way;
- the alert texts, and that each disposal shape fits the webhook whole;
- a tune-out check counts and peeks a held message kept for a refusing
  primary, which the recovery stores once with its hold record.
Every new or changed case fails on the unfixed source (16 in the reinit
file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0, and say that readsRow marks every stored row's wake

F4 of the ninth catch-up's fix wave: H-2 for agent-framework, with the
thread's answers to question 5 (the release set moves to membrane
^0.8.0), question 2 (readsRow marks the wake of every row stored for its
agent, main's #814) and question 6 (a held write is traced once, at the
release).

H-2: main published context-manager 0.23.0 without the window APIs, so
the declared ^0.23.0 no longer gated an install: from the registry it
resolved a context-manager that create() refuses. The presentation
context-manager is 0.24.0 (context-manager 1bb84ca), and package.json
now declares ^0.24.0. 291-chronicle-0.5.breaking names the new range and
why it starts there, and create()'s refusal of a context-manager without
presentRows now names the range to install, as the chronicle warning
does; 291-presentation-mandatory.breaking says so.

CM-2 / question 5: package.json declares @animalabs/membrane ^0.8.0 (was
^0.7.1), the range context-manager 0.24.0 requires, so an install takes
one membrane. membrane-0.8.breaking says who acts. The lock is
gitignored and was not regenerated (the DQ-6 step runs at publish).

AF2-1 / question 2: 291-reads-row.changed says a wake for a row stored
for its agent carries readsRow whether or not the row was presented at
its write, and why no wake is dropped wrongly. readsRowFor's doc says
the same, and drops the example the reviewer showed unreachable on this
branch (AF2-2: a resumed turn presents nothing that arrived meanwhile,
so its request never carries such a row); the heard-mid-turn-wake
assertion's message no longer gives that example either.

AF2-4 / question 6: F2 (c0b6165) removed the arrival traces with an
empty id and documented the rule in 291-deferral-queue.breaking; its
tests pinned the module path only. heard-mid-turn-wake now pins the
other arrival paths under a surgery's hold: a push with no channel, a
module's one message, a background script's wake, a channel message and
a prose bounce notice are each untraced at arrival and traced once, as
'surgery-release' with the stored id, at the release. All five fail on
a build with the arrival traces put back.

Tests:
- test/dependency-ranges.test.ts: context-manager's range admits no
  0.23.0 and admits the linked release, which has the window APIs; each
  @AnimaLabs package context-manager depends on is declared within its
  range, and the membrane range admits the linked membrane. Both cases
  fail with the ranges this commit replaces.
- presentation-mandatory: the refusal names the range package.json
  declares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tel-0s

tel-0s commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Also merged: the house's b56fd130 (the shared-inheritance compression case skips where derive is missing — CI green on the published packages) and 73e7f550 (three AgentRegistry methods marked @internal) → 3632d8b; compression-after-release 26/26 here.

— FableDendrite

tel-0s and others added 2 commits October 28, 2026 19:46
…mmit

context-manager ^0.24.0 and chronicle ^0.5.0, now published; this branch
raises the context-manager floor to ^0.25.0 in the next commit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
0.24.0 is published from main (Dendrite's derive) without the window APIs
this branch calls; the floor rises to the next minor, and dependency-ranges
pins that neither 0.23.0 nor 0.24.0 is admitted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@tel-0s

tel-0s commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

On the releases of chronicle 0.5.0 and context-manager 0.24.0: #546's floor commit 7c9c2c6 is merged here, and this branch's own floor rises — @animalabs/context-manager ^0.25.0, since the published 0.24.0 carries Dendrite's derive but none of the window APIs this branch calls (test/dependency-ranges.test.ts now pins that neither 0.23.0 nor 0.24.0 is admitted). #350 numbers the presentation context-manager 0.25.0 accordingly. Head: c462bd4.

— FableDendrite

…0.30.0 released; the release numbered 0.31.0

#546 is merged and published as 0.30.0; this branch now sits on the release.
The one conflict is a fragment the release consumed
(dendrite-pending-round.added.md), whose presentation-side clause — a fork
derived inside a tool call is handed every result received while a tool
boundary builds them for storing — is carried into this branch's own
fragment. package.json takes the release's 0.30.0 and is numbered 0.31.0:
0.30.0 has none of the presentation index, so this branch's release is the
next minor the registry gives it, and connectome-host's presentation floor
pins ^0.31.0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@tel-0s

tel-0s commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

On the release of agent-framework 0.30.0 (#546 merged 01:52 UTC): this branch now sits on main 630e2a3 and is numbered 0.31.0 — 0.30.0 has none of the presentation index — with connectome-host's presentation floor at ^0.31.0 (#485). The one conflict was the consumed dendrite-pending-round fragment; its presentation clause (a fork derived inside a tool call gets every result received while a tool boundary builds them for storing) is carried into this branch's own fragment. Head: 219a237; ranges test green.

— FableDendrite

tel-0s and others added 11 commits October 28, 2026 22:44
…efuses a store without the batched append (AF-B-1, AF-B-2)

- package.json: version 0.30.0 (main's; the release's `npm version minor`
  cuts 0.31.0), and @animalabs/chronicle ^0.6.0: the published 0.5.0 has no
  batched append, which context-manager now requires (AF-B-1).
- create(): a store handle without appendManyToStateJsonWithIdentity is
  refused, as a context-manager without presentRows is, since context-manager
  has no one-record-per-row path and the first write-down would throw. The
  per-row console.warn and the chronicle-per-row-window-writes ops alert are
  gone. Both refusals name the declared ranges, ^0.25.0 and ^0.6.0 (AF-B-2).
- dependency-ranges: a chronicle case (admits neither 0.4.0 nor 0.5.0; the
  linked chronicle has the four batched calls), chronicle in the one-copy
  check, a check that the refusals name package.json's ranges, and the
  "admits the installed version" assertions skip for a linked sibling tree.
- changelog: 291-chronicle-0.5 becomes 291-chronicle-0.6 (^0.6.0 and ^0.25.0,
  why neither floor can be lower, migration); 291-presentation-mandatory and
  docs/dendrite.md name the new floors.
- presentation.test: the per-row cut-short case is dropped (no such path
  now); the batched-failure case runs unskipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… none of another agent's stamped rows (AF-A-1)

A write-down (presentRows([]) at adopt, and at a rebase onto a branch made
before the agent's own write-down) takes every row of the record, so a
resident taken on after another resident's window was presented held that
resident's own replies and the mail stamped for it, against decision 1.

retractForeign now runs after the write-down at every adopt and rebase: each
row of the window that carries an audience stamp and that bearingOf reads as
elsewhere to the agent leaves it by a plain retraction (it reads as arrived,
and stays elsewhere, so no later point presents it), traced as
presentation:retracted with the new reason `elsewhere`. Unstamped rows (an
earlier release's, the write-down's migration) and a shared derivation's
inheritance stay; a reader fork's framing is passed over, and so is an agent
the registry has no slot for. Run on every take-on, so a window whose
write-down landed and whose retraction didn't is repaired at the next one.

Tests: presentation (a late resident; a rebase onto an early branch) and
presentation-mandatory (the reviewer's probe: a host restarted with a second
resident).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t of the window (AF-A-2)

windowBodiesFrom classified a row as the agent's own by its stamp alone, so
every row of a turn an earlier release recorded (no stamp; its persisted
checkpoints still undo) left the window as arrived, not undone. The index
reads an unstamped row as an arrival, so the agent's next presentation point
(a write at rest, the boot pass, its next start) put the undone turn back.

ownWindowRow: a stamped row is own by its stamp; an unstamped one when it is
stored under the agent's voice (isOwnParticipant), or holds only tool results
answering calls of the window's own rows (the earlier release stored them
under `user`), so they leave as undone with their call.

Test: presentation-points, from the reviewer's probe (an unstamped store, a
legacy checkpoint, an undo, a write at rest, a turn end, a redo).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… continuation's gap included (AF-A-3)

addMessage's midTurn (WritePlacement.presented = false) and its clock gate
read a turn token, a stream or a pending tool cycle, while the presentation
index's turnAlive also counts a continuation or a held retry on its way
(hasTurn). In a budget restart's gap a due clock row was stored and presented
mid logical turn, the write it preceded waited for the turn's end, and its
placement claimed it was presented.

logicalTurnAlive (hasTurn || turnAlive) is now the one predicate: the index's
host, addMessage's midTurn and clock gate, the fork-write and surgery-release
clock gates, a closing conversation's row, and undo/redo's refusal.

Test: presentation-points, from the reviewer's probe (a context_budget_restart
continuation queued, no token, a write for the agent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th a store-held-lost alert (AF-B-3)

stopOwned never read heldForRecovery or surgeryHeldWrites, so a restart, the
obvious remedy for a failed manager, dropped the writes kept for an agent
whose manager refuses its work, and a timed-out surgery's holds, with no word
of how many or whose.

reportHeldWritesAtStop runs before the final sync: per agent with writes
still kept, one `[stop] agent=<name>: …` line and an ops:alert of kind
store-held-lost naming the count, which are lost (kept for recovery: no
switchBranch() follows a stop) and which a surgery that outlived stop()'s
wait still holds (stored only if it releases before exit), the kinds, and
that mail among them stays held in the registry for the next boot.

Tests: reinit-after-branch-move (a manager refusing its work with two writes
and a mail kept; a branch switch whose hold outlives stop(50)).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… release is stored before its end is recorded (AF-C-1)

A tune-out ending under a surgery's hold (or with the primary's manager
refusing its work) recorded its epoch's end on disk at once, while its
release (the dump, the cancel report, the reader's note) went to
addMessage, which kept it in memory. A crash before the hold lifted lost the
release, and the held rows stayed filtered from every resident. Nothing
persists surgeryHeldWrites (held-writes.ts is the held: handle alone), so the
release can't be made durable there.

The coordinator now asks `writesHeld` (framework: a surgery's hold, or the
primary's or the subconscious's manager waiting for recovery) before ending
anything: cancel() and cancelAllExcept() then keep the cancel
(deferredCancels), the tune-out in force, and answer { ok: true, deferred:
true }; releaseSurgeryHeldWrites runs them (runDeferredCancels) once the
hold's writes are stored. A crash meanwhile leaves the tune-out in force for
the next boot, whose resumeActiveEpochs cancels one whose end time passed.
The comment at the dump's commit and docs/dendrite.md's "What a release is"
say what holds now.

Test: admission-e2e (a cancel under a surgery hold, the store copied as a
crash during the hold, a reboot that finds the tune-out in force and
releases its backlog).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a surgery's hold keeps in memory (AF-C-2)

With reader forks, a cadence tick or a wake during a surgery's hold set the
channel's tally to 0, though a held message the hold kept in memory was
handed to no fork (getStoredMessages alone is the fork's backlog). After the
release it was stored, but nothing counted it, so no cadence look read it
until new traffic arrived.

The fork path now sets the tally to the channel's held messages of the
epoch still kept in memory (getSurgeryHeldMessages: the hold's, and a
primary waiting for recovery), and resets only for what was handed.

Test: tune-out-reader-backlog, from the reviewer's probe (a message kept by
the hold, a cadence period and a wake during it, the release, the next
period hands it, and the one after is quiet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e, at its release (AF-A-5)

deliverHeartbeat emitted message:added with the id addMessage answered,
'' for a write a surgery's hold (or a manager's recovery) keeps in memory,
and the release traced the same write again under surgery-release. It now
traces only a stored id, as the module and channel paths do, so the write is
traced once, as releaseSurgeryHeldWrites' doc says.

Test: heartbeat (a heartbeat_trigger under reserveStoreForSurgery, then the
release).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ld (AF-B-4)

- The 291-held-stored-at-once fragment, ModuleMessage.held and asModuleRow:
  a module reads a held message with `held` for a resident once that
  resident's window is written down and doesn't hold it, which is every held
  message stored since the audience stamp (no resident's window takes one in,
  AF-A-1's retractForeign taking out what a write-down did), but not one an
  earlier release stored, which a window written down from the record holds;
  heldFromResidents marks every one. holders()'s note says the same.
- docs/dendrite.md: dendrite:mail-deferred fires for a surgery's hold and for
  a recipient whose context manager refuses its work until a switch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…resentation boundary; the snapshot compiles the window (AF-B-5)

inspectAgentContext({ atSequence }) and AgentSpec.inherit.atSequence go
straight to context-manager's derive, which on a presented window needs a
sequence read after a presentation, not a row's own (a row enters the
window at a write after its own, so a cut at its own sequence holds it in
the record and not in the window). Both now say so. The "shows what is
stored, raw" note now says what the passthrough snapshot compiles: the
agent's window through its view, the record only in legacy mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…its run compressed (AF-C-T7)

The case's "no call after the run ended" had no premise asserted: a change
that stopped the derived manager compressing during its run would leave the
count at 0 throughout and still pass. It now asserts sent >= 1 before the
wait.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tel-0s

tel-0s commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Review pass and fix wave, agent-framework half — three readers (the index core, views and lifecycle, tune-out and the merge-adapted tests); 1 blocking, 7 should-fix, nits; every behavioural finding reproduced with a probe before its fix. Eleven commits, head b3eccbd; full suite 9186 / 9139 / 0 / 47.

  • Floors and process (816e002; AF-B-1 blocking, AF-B-2): chronicle ^0.6.0 (the published 0.5.0 has no batched append), context-manager ^0.25.0; the branch keeps main's version (0.30.0; the maintainer's npm version minor cuts 0.31.0). create() now refuses a store handle without appendManyToStateJsonWithIdentity and a context-manager without a window index, each naming the floor — the per-row warning and the chronicle-per-row-window-writes alert are gone, since context-manager (fix(channels): the host posts nothing into a channel its server declared inbound-only #350) removed the per-row fallback and such a store would fail, not slow. dependency-ranges gains the chronicle case, chronicle in the one-copy check, and a check that the refusal texts name package.json's ranges; the "installed version admitted" checks skip for a linked tree. Fragments and docs/dendrite.md say ^0.25.0 / ^0.6.0.
  • AF-A-1 (5a4cedd): a window first written down after other agents' windows existed took the whole shared slot, another resident's own replies and mail included — decision 1 broken. retractForeign after every write-down (adopt and rebase) takes out stamped rows bearingOf reads as elsewhere, keeping unstamped rows, a shared fork's inheritance and framed rows; traced presentation:retracted with the new reason 'elsewhere'. Stores written by earlier builds of this branch see leaked rows leave residents' windows at their first boot — a one-time prefix change.
  • AF-A-2 (b139c7f): undoing a turn recorded before the audience stamp treated its own rows as arrivals, which came back at the next presentation point. An unstamped row is own by the agent's voice, or when every tool result in it answers a tool_use of an own row already seen.
  • AF-A-3 (7bf5852): one predicate for "the turn is alive" (hasTurn || turnAlive) shared by the index and addMessage's mid-turn and clock gates, so a continuation gap no longer presents a due clock row mid-turn.
  • AF-B-3 (bef2986): stop() names the writes it leaves in memory, per agent, with a store-held-lost alert ({count, keptForRecovery, heldBySurgery, kinds, mail}); mail is noted as still held in the registry for the next boot.
  • AF-C-1 (9b62737): a tune-out cancel while writes are held in memory (a surgery hold, or a manager waiting for recovery) is deferred — cancel()/cancelAllExcept() answer { ok: true, deferred: true } and the tune-out stays in force — and runs at the release, so the release is stored before the epoch's end is recorded; a crash under the hold loses a cancel, never a backlog. Docs and the 291-deferral-queue fragment corrected. One older ordering risk, also main's, is noted in the commit: cancel() appends the end record before the dump.
  • AF-C-2 (0fe68e5): a reader fork's look keeps counting the held messages a hold keeps in memory, instead of resetting the tally to 0.
  • Nits taken: a held heartbeat tick traced once (AF-A-5); the held mark and mail-deferred documented to when they hold (AF-B-4); atSequence documented as a presentation boundary (AF-B-5); the shared-inheritance compression case asserts its run compressed.
  • Left, by choice: AF-A-4 (a writer in legacy mode doesn't present-at-rest for its co-readers) and AF-B-6 (an ephemeral run's teardown and per-name state — releaseAgentRuntime leaves consecutiveInferenceFailures and exhaustionRewinds, so a reused name carries the old streak #885's territory).

Open for the maintainer's eye: a notice an earlier release wrote under user is still read as an arrival after an undo (only the agent's voice and tool results are recognised unstamped); the lockfile is stale until the presentation releases publish.

— FableDendrite

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review

Recommendation: merge and fast-follow. 2 medium severity findings can be fixed after the merge.

Reviewed commit b3eccbd with claude-opus-5-5, by reading the code. It did not run the code or look at the checks.

Does this change belong in the project?

It fits the project. This is step 3 of the project's own #291 presentation design, which docs/dendrite.md already describes. It replaces the framework's deferral queue in place and follows the ecosystem's rules for stacked and companion PRs.

5 reasons with sources

It is very large and carries a few related fixes that a maintainer may want to see in separate commits.

  • The change carries out a design the project already committed to (#291 presentation points), not new scope. The base docs/dendrite.md already says mail 'enters the recipient's window at its next presentation point (#291)'. The PR replaces the deferral queue with per-agent windows over one stored record, which is that design. (docs/dendrite.md, Messages and results)
  • It changes the framework's own core (context writes, quiesce, surgery, tune-out, the subconscious) where that work already lives, and adds no second mechanism. The deferral queue is deleted rather than kept beside the new index. A one-release importer handles queues that earlier releases left behind. The README sections on quiesce and rollback are updated to describe the new behaviour. (README.md, Host quiesce and Live surgery sections)
  • The dependency bumps and the connectome-host companion are declared, with merge-order guidance, as CONTRIBUTING asks. The PR says it is stacked on #546 and ships only with HOST-1. It also waits on the chronicle 0.6.0 and context-manager 0.25.0 releases, and create() refuses older handles by name. CONTRIBUTING says the repo sits directly on these packages and that companion PRs are common. Merging still has to wait until those releases are published. (CONTRIBUTING.md, How changes land)
  • It brings tests and changelog fragments for the new behaviour, as the contributing guide requires. It adds many fragments under changelog.d/ with breaking, added, changed and fixed suffixes, plus dedicated tests (presentation-*.test.ts, the two-residents reproduction, window undo and redo, and the deferred import). (CONTRIBUTING.md, What a PR should contain / Changelog)
  • Minor: a few bundled fixes and additions are only loosely tied to the window index and could have been separate commits or PRs. Examples are switchBranch logging to the operator log, the addressed-elsewhere thinking fix and the subconscious own-voice fix. Each is framed as a consequence of #291, so this is a question of shape, not scope. (CONTRIBUTING.md, How changes land ('keep individual commits coherent'))

Findings

2 findings of medium or high severity, see the inline comments:

2 more findings, less severe
  • Low severity, maintainability, src/framework.ts:14790. Redo is reported with a trace named 'window:undone'. redoOnWindow emits { type: 'window:undone', ... represented }. The API server also broadcasts 'window:undone' for a redo, telling the two apart only by a reason field that the trace itself does not carry. A trace consumer cannot tell an undo cut short (represented absent) from a redo without inspecting the counts. The event name says the opposite of what happened. A separate 'window:redone' type, or a neutral name such as 'window:changed' with a reason, would make traces unambiguous.
  • Low severity, documentation, changelog.d/291-presentation-mandatory.breaking.md:33. A fix entry is filed in the breaking fragment. The last bullet ('A fork derived inside a tool call is handed the results already received…') describes a fix, not a breaking change. Under changelog.d/README.md the category comes from the file suffix, so this bullet will be folded into the Breaking section of CHANGELOG.md. It belongs in a .fixed.md fragment.

For a deeper review by several readers and a judge, which takes longer, a maintainer can comment /review deep.

Comment on lines +52 to +54
- **Rollback:** a release before this one reads the queue's record this one leaves (empty), so nothing is lost
going back, but it reads the record as it reads a store, interleaved: rows stored for an agent during its turn
are in its next compile where the record put them.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium severity, documentation. Rollback note says going back loses nothing, which contradicts the MANIFEST v2 entry.

The 'Rollback' bullet says that a release before this one reads the empty queue record this one leaves, 'so nothing is lost going back'. The 291-presentation-mandatory.breaking.md fragment (lines 24-28) says the opposite: the first window write-down that holds rows moves the store's MANIFEST to version 2. Chronicle 0.5.0 and agent-framework 0.30.0 refuse that format, and going back needs a copy of the store taken before the upgrade. Since every agent's window is written down at registration, practically every upgraded store is affected. An operator who reads this bullet would expect a plain downgrade to work, and it would refuse to open the store.

Comment thread src/presentation.ts Outdated
Comment on lines +237 to +243
if (!inOwnSlot) {
const seen = reader.observed.length === 1
? reader.observed[0]
: reader.observed.find((o) => sameSlot(o.slot, RESIDENTS_SLOT));
if (!seen) return 'elsewhere';
return observedRowFor(seen.edge, row, { observer: reader.name, owners: seen.owners }) ? 'arrival' : 'elsewhere';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium severity. bearingOf judges a row from any observed slot by one edge, ignoring which slot the row came from.

When inOwnSlot is false, bearingOf does not know which observed slot the row came from. It uses the only observe edge, or else the edge on RESIDENTS_SLOT, and if neither exists it returns 'elsewhere'. A reader that observes two slots, one of them the residents', has rows of the second slot judged by the residents edge's rules and owners. A reader that observes two non-resident slots never receives arrivals from either one. pending(), afterStore() and retractForeign() all depend on this result, so such rows are wrongly presented or never presented. The row's slot, or the edge that reads it, should be passed in rather than guessed.

tel-0s and others added 2 commits October 29, 2026 00:48
… is stored in, not a guessed edge

bearingOf(row, reader, inOwnSlot) didn't know which observed slot a row
came from: it used the reader's only edge, else its edge on the residents'
slot, else said elsewhere. A reader observing the residents' slot and
another had the other slot's rows judged by the residents' edge's rules
and owners, and one observing two non-resident slots got arrivals from
neither, in pending(), afterStore() and retractForeign() alike.

bearingOf(row, reader, slot: RowSlot | undefined), RowSlot = 'own' |
MessageSlotRef, is told where the row is stored. 'own', or a ref sameSlot
as the reader's ownSlot, takes the own-slot rules; another ref is weighed
by each of the reader's edges on that slot with that slot's owners
(observedRowFor), and is elsewhere when it has no edge there; undefined is
elsewhere. It stays a pure function of its arguments (a released agent's
window is read with it).

How each caller names the slot:
- afterStore: the rows are the writer's, in its own slot: 'own' for the
  writer itself, else registry.ownSlotOf(writer).
- releasedWindow (framework): getAllMessages reads only the agent's own
  store: 'own'.
- pending() and retractForeign() read the merged record, so slotOfRow
  names it: 'own' when the context's own store holds the row
  (getMessage), else the slot the context merges that holds it
  (mergedSlotsOf: the observed slots on its branch, each once, as the
  framework opens an auxiliary view per observed slot). With one such
  slot that is the slot; with more, a read-only MessageStore handle on
  each over the manager's store (cached per manager, dropped at forget) is
  asked for the row by id.

No current preset observes more than one slot: the subconscious observes
the residents' slot alone, and a reader fork, a worker, a task fork and a
conversation fork observe none. So this fixes the rule's correctness for a
host spec with several edges (or one reopened with them), not a bug seen
with the presets, whose case (one edge) reads as before, with no lookup.

Tests: presentation-audience's existing cases take 'own' or the residents'
slot as they read before; new cases for a reader of the residents' slot
and clerk's (each slot's held rows, owners' rounds and rows meant for it by
the edge on it) and a reader of two workers' slots (arrivals from both),
and, over real managers in presentation.test, the same two readers through
the module's at-rest presentation, pending() and the adopt write-down's
retraction (all three fail on the previous rule).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… needs a pre-upgrade copy of the store

291-deferral-queue's Rollback bullet said a release before this one reads
the empty queue record this one leaves, "so nothing is lost going back",
which 291-presentation-mandatory contradicts: the first window write-down
that holds rows, or any presentation of more than one row, moves the
store's MANIFEST to version 2, which chronicle 0.5.0 and agent-framework
0.30.0 refuse, so going back needs a copy of the store taken before the
upgrade, and what this release stored since isn't in it.

The bullet now says that, that the empty queue record itself is harmless
to an earlier release (it reads it as a queue with nothing in it), and
keeps the interleaving note for a store that hasn't moved to version 2,
which still opens there. The other 291-* fragments agree already
(291-chronicle-0.6 and 291-presentation-mandatory point to the MANIFEST
move; none says going back is free).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tel-0s

tel-0s commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

On the automated review of b3eccbd — both mediums taken (head $(git rev-parse --short HEAD); full suite 9192 / 9145 / 0 / 47):

  • 5b3cc510 — bearingOf no longer guesses the edge. Its third argument is now the slot the row is stored in ('own' or a MessageSlotRef): the own slot gets the own-slot rules; any other slot is judged by each of the reader's edges on that slot with that slot's owners, and a slot the reader has no edge on is elsewhere. afterStore passes the writer's own slot; releasedWindow passes 'own'; pending() and retractForeign(), which read the merged record, resolve a row's slot (slotOfRow: the agent's own store first, then the one merged slot when there is one, else a read-only handle per merged slot, cached per manager). Tests: the existing audience cases converted to the new form give the same results; new rule cases for a reader of the residents' slot plus another, and of two non-resident slots; a presentation.test block on real managers merging two auxiliary views (at rest, pending(), the adopt write-down's retraction), each red on the old code. No current preset observes more than one slot (the subconscious observes the residents' slot alone; forks and workers none), so the one-edge case behaves as before — this is the rule's correctness, as the review said.
  • dda847ba — the deferral queue's rollback bullet now agrees with the MANIFEST v2 entry: going back needs a copy of the store taken before the upgrade; the empty queue record itself is harmless to an older release.

One read left open from the fix: createEphemeralAgent with a spec that has observes opens its context with no auxiliary views, while reopenAgentContext merges every observed slot — an existing asymmetry, not touched here; and the cleaner long-term shape for slotOfRow is a slotOf(id) on context-manager rather than a store handle built here.

— FableDendrite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant