| type | concept | ||
|---|---|---|---|
| topic | architecture | ||
| audience |
|
Every state change in reyn emits an audit-event. The audit-event log is the runtime's diary: a JSONL stream that records what happened, in order, with enough detail to replay the run.
There is no separate logger, tracer, or telemetry hook. The same channel powers:
- Live debug output. Console reporters subscribe to the audit-event stream and render each audit-event as it arrives.
- Replay.
reyn events <log_file>re-renders a saved log to the console without re-invoking the LLM. - Eval analytics. Eval reports aggregate audit-event data (token usage, validation errors) per case.
- Crash recovery (implemented). Crash recovery reconstructs agent state from the WAL (
.reyn/state/wal.jsonl) plus seq-keyed snapshots as its substrate — not the audit-event log. User-facing rewind/resume (PITR + global rewind) is a separate design; see Time-travel.
If the OS is the only mutator (P3) and every mutation emits an audit-event, the audit-event log is sufficient. There's no "what else happened" to chase down.
A few of the larger buckets:
- LLM and context —
llm_called. - Control IR — one audit-event per op kind, for example:
plus
mcp_called sandboxed_exec_started semantic_search_embed_failed web_search_startedpermission_denied. Afileop like a read instead rides the sharedtool_executedkind, naming the specific operation in itsopfield rather than as a kind of its own. - User interaction —
user_message_received,user_intervention_received,chat_started,chat_stopped,turn_cancelled. - Agent-to-agent messaging —
agent_message_sent,agent_request_received,agent_response_received,agent_message_refused,chain_timeout. Each carrieschain_idso a single user request can be traced across hops.
The buckets above are examples. The type field is a closed vocabulary — every kind reyn emits, and nothing else, is enumerated in the events reference, derived from one source of truth in src/reyn/core/events/event_schema.py and CI-checked against both the emitting code and the page. A consumer outside reyn can therefore enumerate the complete set of types it may receive.
Task↔session binding changes (assignee/requester) were designed (#2187) to be recorded as WAL kinds (task_subscribed, task_rebound — StateLog, .reyn/state/wal.jsonl), not the P6 audit-event log — so if this mechanism were live, you would look for it there, not here. It never got a writer, and #3436 removed the two kinds from the WAL vocabulary entirely (dead declaration, no producer). Task↔session binding changes are not recorded anywhere today. The WAL is generally the crash-recovery and time-travel substrate; the audit-event log is the per-run trace — they are separate logs with different durability contracts (see Time-travel — WAL vs audit-event separation).
Every audit-event has a stable envelope:
type — event type (see reference)
timestamp — ISO-8601 timestamp
data — flat dict of payload fields specific to the type
Key fields present in most audit-events (in data):
run_id — uuid for the run (present on most run-scoped audit-events)
Note: run_id is present on most run-scoped audit-events (llm_called,
permission_denied) but absent from some audit-events emitted outside a run
context (e.g. chat_started). Which kinds carry run_id is a payload question,
not a vocabulary one — the closed set above says which kinds exist, and does not
partition them by field. There is no enumeration of the run_id-carrying subset;
read the per-kind payloads in the reference.
A growing set of audit-event kinds are required to carry specific
audit fields in their data dict (the required fields vary per kind — e.g.
llm_called requires model; permission_granted/permission_denied
require run_id, actor, phase). The authoritative, current registry
lives in src/reyn/core/events/event_schema.py
(EVENT_AUDIT_REQUIREMENTS) — this list has grown over time and will keep
growing, so it is not duplicated here. Dedicated per-feature invariant tests
(e.g. tests/test_session_lifecycle_events_1800.py,
tests/test_mcp_search_tool_invariants.py,
tests/test_chat_turn_completed_inline.py) each assert that their event
kinds are declared here with the correct required fields, on every CI run.
Enforcement is test-time only (not at emit() runtime) to keep
production overhead zero.
Stable shape makes the log machine-readable without a custom parser per consumer.
- Not application logs. A workflow author shouldn't emit free-form audit-events. The set is OS-defined.
- Not memory. Audit-events are the runtime's per-run record; memory is across-run knowledge. See ../data-retrieval/memory.md.
- Not the source of truth for artifacts. Artifacts pass through the workspace channel; audit-events record that they passed.
When something looks wrong:
- Find the run id from the run output's last line (
events saved → ...). reyn events .reyn/events/<run_id>.jsonl --conversationto see what each LLM call looked like and what it returned.- Or
--filter permission_deniedto jump straight to where the OS refused an op.
You don't need a debugger; the log already has the information.
- Reference: events — the full audit-event taxonomy
Audit-events are the per-run trace, not the crash-recovery or time-travel substrate — those are WAL-backed (see Time-travel). For payload-level trace inspection, see reference/dogfood-tracing.md.