Event Pipeline Deep Dive
Event Pipeline Deep Dive
The event pipeline is the core data path in Athena. Understanding it helps when debugging, diagnosing performance issues, or building new harness integrations.
The hook-forwarder Binary
athena-hook-forwarder is a minimal, standalone binary that bridges Claude Code's hook system to Athena's socket listener.
Claude Code calls it synchronously — the agent pauses and waits for the binary to exit before continuing. The forwarder does the minimum possible:
- Read the hook event data from stdin (Claude Code sends it as JSON)
- Open a connection to the Unix Domain Socket
- Write the data as a single NDJSON line
- Close the connection and exit
No parsing, no processing, no blocking. The forwarder completes in single-digit milliseconds.
Unix Domain Socket Transport
Athena uses a Unix Domain Socket (UDS) for the event transport:
- Faster — No network stack overhead, no port management
- Local-only — Events never leave the machine
- Per-instance — Each Athena instance has its own socket path, preventing collisions between projects
The socket path is:
<projectDir>/.claude/run/ink-<PID>.sock
The socket directory is created at startup and the socket file is deleted on exit.
NDJSON Stream
Events arrive at the socket as Newline Delimited JSON — one JSON object per line. The envelope type for incoming events:
type HookEventEnvelope = {
type: string; // hook event name, e.g. "PreToolUse"
// ... hook-specific fields
}Athena's runtime server reads these envelopes, maps the hook name to a RuntimeEventKind, and constructs a RuntimeEvent with normalized fields (id, timestamp, kind, data, sessionId, context, interaction).
For events that expect a decision (interaction.expectsDecision: true), the server writes a HookResultEnvelope back to the hook-forwarder over the same socket connection before closing.
Session Store (SQLite)
Every event that passes through the pipeline is written to a per-session SQLite database before being dispatched to the UI. Session databases live at:
~/.config/athena/sessions/<session-uuid>/session.db
Each session has its own isolated database file. The schema has five tables:
schema_version
Migration version tracking.
session
One row per Athena session.
id TEXT, project_dir TEXT, created_at INTEGER, updated_at INTEGER,
label TEXT, event_count INTEGER
runtime_events
Raw hook events as received from Claude Code.
id TEXT, seq INTEGER UNIQUE, timestamp INTEGER, hook_name TEXT,
adapter_session_id TEXT, payload JSON
feed_events
Derived display events shown in the UI. One runtime event can produce multiple feed events.
event_id TEXT, runtime_event_id TEXT, seq INTEGER UNIQUE,
kind TEXT, run_id TEXT, actor_id TEXT, timestamp INTEGER, data JSON
adapter_sessions
Claude Code session records with token accounting.
session_id TEXT, started_at INTEGER, ended_at INTEGER, model TEXT, source TEXT,
tokens_input INTEGER, tokens_output INTEGER, tokens_cache_read INTEGER,
tokens_cache_write INTEGER, tokens_context_size INTEGER
SQLite is chosen because it is zero-configuration, embeddable, and handles the append-heavy write pattern of event logging efficiently. Session resume (--continue) works by replaying feed_events from the store back into the timeline.
WAL Mode
The database is opened in WAL (Write-Ahead Logging) mode with an exclusive write lock. This allows Athena's session registry to read sessions from other instances in read-only mode without contention.
Degraded Mode
If the SQLite database cannot be opened (e.g., disk full, permissions issue), Athena continues running in degraded mode — events are processed and displayed in the UI but not persisted. The header indicates degraded state, and session resume will not be available.
Feed Mapper
The feed mapper (core/feed/mapper.ts) transforms RuntimeEvent objects into one or more FeedEvent objects. This is where the data is enriched for display:
RuntimeEventKindvalues liketool.premap toFeedEventKindvalues with display-ready fields- Run tracking groups events into logical runs (
run.start/run.end) - Actor IDs are derived from session context
- Collapsible groups and expandable detail are determined here
Feed events are sequenced by seq number and stored in both SQLite and the in-memory timeline.
Backpressure
If Claude Code fires hooks faster than Athena can process them, the socket buffers events. The hook-forwarder will block on the socket write if the buffer is full, propagating backpressure to Claude Code naturally — the agent pauses until Athena drains the buffer.