Skip to content

feat: event-log kernel — durable Event sidecar + replay-capable consumer#2

Merged
esengine merged 2 commits intomainfrom
feat/event-log-kernel
Apr 29, 2026
Merged

feat: event-log kernel — durable Event sidecar + replay-capable consumer#2
esengine merged 2 commits intomainfrom
feat/event-log-kernel

Conversation

@esengine
Copy link
Copy Markdown
Owner

Summary

Builds the event-log kernel as a new durable artifact alongside the existing LoopEvent stream, NOT as a migration target. Future replay / projection / multi-agent consumers read kernel events from the sidecar; the working LoopEvent path stays untouched.

This is the D approach chosen after evaluating the big-bang LoopEvent removal: instead of ~250 mechanical edits across 29 files (and the regression risk that comes with it), we add 7 new files and a 40-line additive wire-up. Architecture value is tangible immediately (an event log exists and is reducible) without forcing a migration.

  • Commit 1 (f0ca5bc): Eventizer translator + JsonlEventSink + App.tsx wire-up. Every session writes typed Events to ~/.reasonix/sessions/<name>.events.jsonl as a sidecar.
  • Commit 2 (f50de58): JsonlEventSource reader + round-trip test proving that synthetic LoopEvents → Eventizer → sink → disk → source → reducers → ProjectionSet reconstruct the conversation correctly.

Architectural rationale

Reframed the question from "how do we migrate off LoopEvent" to "what's the smallest correctness-preserving change that builds the kernel artifact and lets new consumers exist":

  • LoopEvent stays as the internal protocol between loop and immediate consumers — it works, no need to break it
  • events.jsonl is the canonical durable artifact for new capabilities
  • Eventizer is the permanent translation layer at the boundary, not a shim
  • No commitment to ever delete LoopEvent

Files

New (7):

  • src/core/eventize.ts — translator state machine
  • src/adapters/event-sink-jsonl.tsEventSink port impl (write side)
  • src/adapters/event-source-jsonl.tsEventSource port impl (read side)
  • tests/eventize.test.ts — 11 mapper tests
  • tests/event-sink-jsonl.test.ts — 5 sink mechanics tests
  • tests/event-replay.test.ts — 5 round-trip + projection tests

Modified (1, additive only):

  • src/cli/ui/App.tsx — ~40 lines: refs for sink + eventizer, open on session mount, pipe inside the existing for-await consumer alongside writeTranscript, close on unmount

Untouched: loop.ts, transcript.ts, replay.ts, EventLog.tsx, log-frame.tsx, LiveRows.tsx, dashboard server, subagent — and every existing test file.

Test plan

  • npm run verify — 1747 tests pass (was 1726 + 21 new)
  • npm run lint — clean
  • npm run typecheck — clean
  • Manual smoke: reasonix code for a session, confirm ~/.reasonix/sessions/<name>.events.jsonl appears with parseable JSONL — one event per line, monotonic ids, correct turn numbers, model.turn.started at every turn boundary, tool.intenttool.dispatchedtool.result chain with matching callIds
  • Confirm zero behavior change in the live TUI — chrome / prompt / scroll / copy / ctx footer all work exactly as before

What this unlocks (future PRs, not this one)

  • Time-travel UI reading from events.jsonl
  • Projection-driven dashboard (web frontend reads Event stream instead of LoopEvent)
  • Multi-agent event trees with parent-child correlation
  • Deterministic replay for debugging / regression tests

Adds a new durable artifact: every session now writes a typed Event
log to ~/.reasonix/sessions/<name>.events.jsonl as a sidecar to the
existing ChatMessage / transcript files. Foundation for future
replay / projection / multi-agent consumers reading kernel events
instead of LoopEvent.

Architecturally a pure addition: no edits to loop.ts, transcript.ts,
EventLog, log-frame, replay, dashboard server, or any existing test.
LoopEvent stays as the internal protocol between loop and immediate
consumers — events.jsonl is a parallel kernel artifact, not a
migration target.

- src/core/eventize.ts: pure stateful translator. Holds monotonic
  event id seq, last-turn tracker (synthesizes model.turn.started),
  and a callId stack (correlates tool.intent → tool.dispatched →
  tool.result chains since LoopEvent.tool_start doesn't carry
  call.id).
- src/adapters/event-sink-jsonl.ts: concrete EventSink port impl —
  append-only JSONL with append/flush/close lifecycle. eventLogPath
  helper derives the sidecar path from a session name (mirrors
  src/session.ts:sessionPath).
- src/cli/ui/App.tsx: ~30 lines additive — refs for sink + eventizer
  opened on session mount (skipped for ephemeral sessions), pipe
  attached inside the existing for-await consumer alongside
  writeTranscript, sink.close on unmount, session.opened emitted at
  open time.
- tests/eventize.test.ts: 11 unit tests pinning the mapper —
  turn-start synthesis, delta channel split, tool callId
  correlation, error detection, warning categorization, control-
  marker passthrough.

1737 tests pass (was 1726 + 11 new). Zero existing-test edits.
Closes the loop on the v0.14 kernel architecture: the events.jsonl
sidecar from the prior commit is now demonstrably reduceable into a
useful projection. Synthetic LoopEvents → Eventizer → JsonlEventSink
→ disk → JsonlEventSource → reducers.replay() → ProjectionSet round-
trips correctly, conversation messages reconstructed in order, tool
result correlation preserved, replay() deterministic across runs.

This is the architecture-value tipping point: events are no longer
just write-only artifacts, they're the source of truth a consumer
can rebuild state from. Future work — UI re-rendering from a
projection, time-travel, multi-agent event trees — has a working
foundation.

- src/adapters/event-source-jsonl.ts: EventSource port impl. Pure
  read side: parses jsonl, skips malformed lines silently (best-
  effort replay over a possibly-truncated live writer).
- tests/event-sink-jsonl.test.ts: 5 tests pinning the sink mechanics
  (append-per-line round-trip, re-open on existing file, parent dir
  creation, flush no-op, port-shape conformance).
- tests/event-replay.test.ts: 5 tests pinning the architecture
  promise — synthetic LoopEvents reduce to expected projections,
  error-shaped tool results land with ok=false, replay determinism,
  missing-file safety, malformed-line tolerance.

1747 tests pass (was 1737 + 10 new). Zero touches to existing
production code.
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