The event model
The append-only event log is the source of truth. Everything else — the conversation, the costs, the diffs, the phases — is a fold over it.
The envelope#
{"v":1,"id":"01J8…","sessionId":"01J8…","seq":42,"ts":1765400000000,
"type":"tool.completed","payload":{…}}
seqis gapless and assigned at append time; one complete line per write, never interleaved.- A torn final line (crash mid-write) is quarantined to a
.tornfile and truncated away on open — corruption anywhere else refuses to resume rather than silently repairing. tool.requestedinputs are canonicalized (sorted keys) so a replayed history reproduces identical bytes on every rebuild.
The 40 durable types, by family#
| Family | Types |
|---|---|
| Session | started · resumed · renamed · model-changed · git.baseline-recorded |
| User | message-created · steering-queued · steering-injected · interrupted |
| Model | request-started · text/thinking-block-completed · response-completed · request-failed |
| Tools | requested · approved · started · completed · failed · cancelled |
| Permissions | requested · granted · denied |
| Files & processes | file.read · file.modified · process.started · process.exited |
| Context | pruned · compacted |
| Completion | verification-requested · task.completed · task.failed |
| Phases (v2) | phase.entered · phase.artifact · phase.approved · phase.rejected |
| Verify / memory / subtasks (v2) | verify.result · memory.written · subtask.created · subtask.completed |
Six ephemeral types (streaming deltas, live tool output) flow on the bus but are never persisted — the log records what settled, not the typing noise.
Pairing safety#
Every tool_use must meet its tool_result — across interruptions,
crashes, and compactions. Resume closes dangling calls with synthetic cancellations; a compaction
cut is validated so it can never orphan a pair. This single invariant is why the API replay of any
KHAELOR session is always well-formed.
Everything is a fold#
| Question | Fold |
|---|---|
| What does the model see? | buildConversation(events) — deterministic, byte-stable, prune/compaction re-applied from their recorded payloads. |
| What did this cost? | buildUsageTotals(events) — sums only response-completed usage. Nothing else may contribute. |
| What changed on disk? | buildFileChangeSet(events) — baseline + file.modified, attribution-aware. |
| What phase am I in? | foldPhaseState(events). |
| What should happen next? | foldTurnState(events) → deriveNext — the kernel itself. |
Why audit falls out for free#
Because state is never anywhere else, explaining any behavior — including a daemon
run at 3 AM — is reading a file. /fork is a file-prefix copy. /replay is
re-driving the user turns. /sdiff is two folds side by side. None of these are
features bolted on; they are corollaries of the event model.