Skip to main content

docs/adr/0004-unified-event-log-and-envelope.md

# 4. Unified event log; tagged-map Event envelope

Date: 2026-05-29
Status: Accepted

## Context

The event bus (the D-06 "spine") is the seam between the core and every front-end, and
the per-Session **Log** is the single source of truth (ADR 0003). We need one Event
shape that works for both live display and durable replay. The Kimojo reference keeps
two things — durable `messages` and a separate `ui_event` stream — which is the source
of its documented partial-vs-final `call_id` duplication.

## Decision

There is **one** Event type, a plain tagged map built via a constructor module, with a
common envelope: `{id, session_id, seq, ts, type, data}`.

- **Canonical** events (`user_message`, `assistant_message`, `tool_call`,
  `tool_result`; later `permission_decision`) are assigned a per-Session monotonic
  `seq`, appended to the Log, and define **History** (a fold over them).
- **Ephemeral** events (`text_delta`, `reasoning_delta`, `status`) are broadcast on
  the bus for live display and **never persisted**.

The Log is a per-Session append-only NDJSON file; the envelope serializes 1:1 to a
line. Streaming partials are never written as canonical events — only the final
`assistant_message` is.

## Consequences

- **One contract** for live UI, persistence, replay, and fork — no two-store sync.
- **Deterministic replay/fold** via `seq` (and file append order as backstop).
- **Avoids Kimojo's dedupe bug** by construction (partials are ephemeral).
- **Front-ends stay thin** — a renderer is just a subscriber that pattern-matches on
  `type`; new front-ends need no core changes.
- **Cost:** the canonical type set is a small shared vocabulary that must be curated
  deliberately; adding a canonical type is a schema change to the Log.