Skip to content

The Unified Event System has two writers and no reader, and the docs describe an emitter that has never shipped #1756

Description

@anikinsasha

What I found

MEMORY/STATE/events.jsonl is documented as the Unified Event System. Two
shipped handlers write it:

  • hooks/handlers/MemoryDirIntegrity.ts — path constant :36, append :42
  • hooks/handlers/KnowledgeConformance.ts — path constant :47, append :53
    (new this release)

Programmatic readers: zero, in every release including this one. The write
side gained an emitter at 7.28.3; the read side is still empty.

Around that gap the documentation describes a system that does not exist.

appendEvent() — the hook-facing emitter — has never shipped.
HookSystem.md:1555 says "Hooks call appendEvent() as a secondary write
alongside their existing state writes", :1559-1560 shows the call, :1566
describes a field it auto-injects. No file under hooks/ imports or defines it.
The one shipped function with that exact name is LIFEOS/PULSE/Conduit/store.ts:20,
which writes a per-day Conduit log; LIFEOS/TOOLS/Conveyor/Ledger.ts:142 exports
appendEvents (plural) and writes STATE/content-pipeline/events.jsonl. Neither
is this file.

HookSystem.md:1550 ### Components is a heading with an empty body — two
blank lines, then the next heading.

The event-type table names emitters that do not emit.
HookSystem.md:1575-1589 maps 13 event categories to 11 named hooks. Checked
against the payload:

  • RebuildSkill (DocRebuild handler) (:1584) has no file anywhere in the
    tree
    . git grep RebuildSkill returns exactly one hit: that table row.
  • VoiceNotification (:1581) is a handler with no registration of its own; it
    is reached through VoiceCompletion.hook.ts, and it writes
    MEMORY/VOICE/voice-events.jsonl.
  • None of the 11 writes STATE/events.jsonl. The two handlers that actually
    write it appear nowhere in the table.

Two documents contradict each other about the channel, and one contradicts
itself.

  • HookSystem.md:1616 still says "EventLogger.hook.ts is now the single
    observability writer" — EventLogger writes MEMORY/OBSERVABILITY/*.jsonl, a
    different store — while :1555, in the same file, describes hooks calling
    appendEvent() into this one.
  • NotificationSystem.md:299 heads a section "Event Log Channel (events.jsonl)"
    whose body at :302 describes MEMORY/OBSERVABILITY/*.jsonl written "directly
    from each hook via fs.appendFileSync — … no shared transport library", which
    is the opposite of the appendEvent() API described one document over.
  • MemorySystem.md:647 asserts "All hooks listed above also emit typed events to
    STATE/events.jsonl via appendEvent()". The table it annotates (:625-645)
    contains no writer of this file, and four of its rows name hooks that do not
    exist under hooks/ at all (RelationshipMemory, TeammateIdle,
    SecurityPipeline, PreCompact).

A phantom hook is presented as installable. ULWorkSync.hook.ts ships in no
release, is in no hooks.json, and Work/WorkSystem.md:69 says so plainly
("Private component — NOT in the public release payload"). Three other places
still present it as shipped: HookSystem.md:121 and :1401 list it in hook
inventories, and HookSystem.md:107 puts it inside a copy-pasteable
hooks.json configuration example
, with a timeout. A reader following that
example registers a file they do not have.

Adjacent, same class. TOOLS/IntegrityCheck.ts:5 names /ic as its invoker;
the payload ships four commands (context-search, cs, isdown, lu) and /ic
is not among them. TOOLS/DocCheck.ts has zero invokers while its docstring
(:8-9) claims it replaces DocCrossRefIntegrity and the
IntegrityCheck/SystemIntegrity handlers — which are the handlers still wired
at SessionEnd.

Nothing regressed here. The loop-closing pieces were never built in the public
artifact.

For corroboration from a fork running this code with local instrumentation: 83
DocIntegrity events and 229 memory_dir rows accumulated over 23 days; a finding
set decayed 19 → 16 across that window; none of it was ever seen through this
channel, and the one finding that closed, closed via a commit.

What I propose

There are two coherent answers and I would rather put both to you than assume.

Option A — complete the loop (PR-A implements this).

  1. hooks/lib/events.ts — the appendEvent() your docs describe, plus an
    emitFindingSet() and a reader. The emission contract stated normatively: one
    event per completed check carrying the full finding set including the empty
    set, no event on a skipped check, finding keys free of counts and timestamps.
    Without that, a fixed finding can never leave the digest.
  2. hooks/lib/advisory-readback.ts + a fourth LoadContext loader
    SessionStart is where injection works, and the next session's start is the
    actionable moment for a finding produced at the previous session's end. Emits
    only when the finding set changed, plus a slow re-announce so a real finding
    cannot go silent after one impression. Steady state is zero characters.
  3. The Pulse feedobservability.ts has no STATE/events.jsonl constant
    (its constant block is :76-84), and /api/events/recent (:911, routed at
    :4333) merges four other JSONLs. Adding the constant and merging it in is a
    drop-in change.
  4. Doc repair — fill ### Components, cut the event-type table to real
    emitters, reconcile :1555 with :1616, fix NotificationSystem.md:299-302
    and MemorySystem.md:647, and stop presenting ULWorkSync as installable.

Option B — excise it. Delete the two writers and the Unified Event System
section. If nothing is ever going to read this file, a documented channel that
does not exist costs more than no channel.

I have written A because the machinery is already almost all there and the fix is
small. If B is your answer, say so and I will cut that PR instead — it is
strictly less work.

One thing that changed this release and is worth naming, because it removes an
argument I would otherwise have made: the Pulse pane that promised this file's
contents is gone. LiveEvents.tsx — which said "Events from events.jsonl will
appear here in real-time" — was deleted in the Observability churn, and the string
survives nowhere in the tree, build output included. The current pane
(src/components/activity/ObservabilityDashboard.tsx, on the Agents ACTIVITY tab)
names no file. So there is no broken user-visible surface here any more. What is
left is a write-only file and four documents describing machinery that was never
built, which is a smaller problem than I would have reported a week ago.

Questions I could not answer from the public tree

  • (Q-a) Does your private tree have a live STATE/events.jsonl consumer? That
    answer decides Option A vs Option B, and nothing else in this issue depends on it.
  • (Q-b) Rotation policy. hooks/README.md:460 says observability JSONLs are
    not auto-rotated; under Option A this file gains readers. PR-A's reader scans
    backwards from EOF in bounded windows so growth costs the SessionStart path
    nothing, but the file still grows forever.
  • (Q-c) Is TOOLS/DocCheck.ts the intended deterministic replacement for this
    family? Its docstring says so, it has no invoker, and the handlers it claims to
    replace are the wired ones. If DocCheck is the future, PR-A shrinks to the
    emitter plus readback as a stopgap.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions