… startup
The prompt-settle live tail in SharedConversation subscribed to
`threads.streamStoredEvents`, which is the event sink's stream from
sequence 0. On every start it read and decoded the entire orchestration
event log; on a server with 1.1M events that exhausted the 4 GB heap
within ~2.5 minutes, so the server crash-looped.
The tail now starts at the event store's high-water mark, as the other
J5 daemons do, filters to settle events in the query, and resumes from
the last handled sequence. Settles from before the start remain
LifecycleService's, from its cursor.
The test harness's log now behaves like upstream's sink (history from 0,
then live), so a tail that reads history fails the new test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
Since nightly
0.0.52-nightly.20261110.16, the openclaw server crash-loops: it exits on a V8 heap OOM (4 GB) about every 2.5 minutes, and every client linked to it drops and reconnects each time. The cause is the prompt-settle live tail inSharedConversation. The v0.1.7 merge (dd89865d44) moved it fromthreads.streamDomainEvents(which starts at the newest event) tothreads.streamStoredEvents, which iseventSink.stream()with no start, i.e. a replay from sequence 0. So every server start reads and decodes the whole event log. openclaw's log has 1.1M events (about 2.5 GB of payload), which doesn't fit in the heap. Smaller installs survive, but still pay for a full replay on every start.What changed
readEventStoreHighWater, the start point every other J5 stream daemon uses). It reads throughstreamStoredEventsFromwitheventType: "runtime-request.updated", so the query filters to settle events, and it resumes from the last handled sequence after a stream failure. Settles from before the start are still marked byLifecycleServicefrom its cursor, as decision 2 intended.SharedConversation.layernow requiresEventSinkV2. The J5 runtime layer already provides it, and the two integration tests provide their harness's own orchestrator layer.streamStoredEventsreplays its history from 0, andstreamStoredEventsFromreplays what comes after the given sequence, then live. Its old live-onlyPubSubmock matched the wrong assumption and hid this bug. The new test seeds history and asserts the tail reads none of it. It fails onj5/main(expected 1 to equal +0) and passes here.Live testing
Server only, no visible surface. On a stood-down
migrate-dev-dbcopy of real data (124,232 orchestration events), I ranvp run dev:serverfor 60 s and counted theOrchestrationEventStore.rowToV2StoredEventspans in the trace:j5/mainNot exercised live: a real prompt settled through a shared conversation. The unit test covers the live path (a settle logged after start moves the thread's authors).
Checklist
apps/server/src/j5)Local note:
vp run check:changedand the pre-commit hook flagapps/mobile/src/j5/agents/PersonaSheet.tsxas unused (knip). That's a macOS case-insensitivity false positive:./PersonaSheetand./personaSheetboth resolve topersonaSheet.ts.j5/main's Linux CI is green, and this branch doesn't touch mobile, so I committed with--no-verifyafter format and lint passed.Claude Opus 5.5 in Claude Code (J5 Code).
🤖 Generated with Claude Code