Requirements: observability and logging of the-loop CLI's actions
Phase 1 of 3 (requirements → design → tasks). Ticket: issue #50. This phase should be reviewed and approved before moving to design.
Introduction
the-loop's CLI processes — the webhook receiver (gh-webhook, issue-15), the poller (poll, issue-34) and the sessions CLI — decide autonomously which GitHub events to act on, which sessions to spawn/resume, and what to drop. Today those decisions surface only as free-text logging lines on the process's stderr: there is no durable, structured, queryable record. A user cannot answer "which events triggered this session?", "what was rejected, and why?", or "what failed, and was it retried?" without scrolling terminal output; an agent (Claude/Cursor) has nothing machine-readable to dig through; a dashboard has nothing to ingest.
This work item adds an end-to-end structured event log: every accept / reject / dispatch / spawn / retry / close decision is appended as one JSON object per line to a well-documented JSONL file, plus a the-loop events query command over it. JSONL (not SQLite) is the storage decision — see decision-025.
Requirements
Requirement 1 — every triggering decision is durably recorded
User story: As an operator running the-loop's automation, I want a durable record of which events triggered which sessions, so that I can audit and debug what the system did on my behalf.
Acceptance criteria (EARS)
- WHEN the receiver accepts a webhook POST THEN the system SHALL record a
webhook.receivedevent carrying the GitHub event name, delivery id and whether the HMAC signature was verified. - WHEN a verified event is mapped to work item(s) THEN the system SHALL record a
routing.routedevent naming the work items, the actor and the delivery id. - WHEN a session is spawned or resumed for an event THEN the system SHALL record
session.spawned/dispatch.succeededevents naming the work item, harness, harness session id and the triggering event — so "what triggered this session?" is answerable from the log alone. - WHEN a session is registered, closed or auto-closed (PR merge/close) THEN the system SHALL record the corresponding
session.*lifecycle event.
Requirement 2 — rejections are recorded with machine-readable reasons
User story: As an operator, I want to see which events were rejected and why, so that I can tell a mis-configured filter from a prompt-injection attempt from a duplicate.
Acceptance criteria (EARS)
- WHEN an inbound POST fails signature verification or payload parsing THEN the system SHALL record a
webhook.rejectedevent with areasonofinvalid-signature/invalid-payload. - WHEN a verified event is not routed THEN the system SHALL record a
routing.droppedevent whosereasondistinguishesdisabled-event,duplicate-delivery,no-work-itemandunauthorized-actor(naming the actor). - WHEN a routed event is discarded at dispatch THEN the system SHALL record a
dispatch.droppedevent whosereasondistinguishesduplicate-delivery,already-processed,spawn-policy,session-vanishedandno-adapter. - WHEN the poller ignores an item or comment from an unauthorized author THEN the system SHALL record a
poll.unauthorizedevent naming the author.
Requirement 3 — failures and retries are observable
User story: As an operator, I want failure and retry scenarios logged, so that I know whether a missed action will heal itself (redelivery / next poll cycle) or needs me.
Acceptance criteria (EARS)
- WHEN dispatching an event to a harness fails THEN the system SHALL record a
dispatch.failed(ordispatch.errorfor an unexpected crash) event at levelerror, carrying the error text and awill_retryflag reflecting whether the delivery id was released for redelivery. - WHEN spawning a session fails THEN the system SHALL record a
session.spawn_failedevent with the error and its retry semantics. - WHEN a poll provider or item errors THEN the system SHALL record
poll.provider_error/poll.item_errorevents withwill_retry: true(the next cycle retries). - WHEN writing to the event log itself fails THEN the system SHALL NOT fail ingress — o11y must never break the pipeline it observes.
Requirement 4 — the log is queryable by humans and agents
User story: As a developer (or a coding agent asked to "dig through the events"), I want to query the trail with filters, so that I can answer questions without writing a parser.
Acceptance criteria (EARS)
- WHEN
the-loop eventsruns THEN the system SHALL print matching events filtered by--type(fnmatch patterns),--work-item,--delivery-id,--source,--level(minimum) and--since(ISO or relative like2h), intable,jsonorjsonlformat, with--followtailing live. - WHEN
the-loop events --typesruns THEN the system SHALL list every documented event type with its description (the catalog agents discover the schema from). - WHEN the log file is missing or contains corrupt/partial lines THEN the reader SHALL tolerate them (skip, not crash).
Requirement 5 — one well-understood, documented format
User story: As a user pointing an agent or a dashboard at the log, I want one documented, stable event schema, so that anything can be built on top of it.
Acceptance criteria (EARS)
- The system SHALL write JSON Lines with a common envelope (
ts,source,event,level,pid) plus documented per-type fields, one event per line, append-only (multi-process safe), toobservability.eventLog.path(default.the-loop/logs/events.jsonl, git-ignored). - The system SHALL keep the event-type catalog (
EVENT_TYPES) and the emitted types from drifting apart (enforced by a test) and SHALL document the schema in the observability reference shipped with the plugin. - WHEN
observability.eventLog.enabledisfalseTHEN the system SHALL emit nothing. - WHERE the CLI is used as a library or under unit test WITHOUT an entry point configuring the log, emission SHALL be a silent no-op (zero I/O).
Out of scope
- A SQLite store or query engine — JSONL is the source of truth; anything (including a SQLite import or dashboard) can be layered on it later (decision-025).
- Log rotation/retention — the file is append-only runtime state; operators may rotate or truncate it externally (the reader and
--followtolerate truncation). - Observability of the harness sessions' own work (that is issue-32's tmux attach/web terminal); this log covers the-loop's own ingress/dispatch decisions.