| type | reference | ||
|---|---|---|---|
| topic | runtime | ||
| audience |
|
reyn emits a structured event for every state change. The full event log is JSONL, written to .reyn/events/<run_id>.jsonl and replayable with reyn events <log_file>.
The type field is drawn from a closed set. Every kind reyn emits is listed
below, and reyn emits nothing else — so a subscriber can enumerate the complete
set of types it may receive, and a handler written for a kind not on this list
will never fire.
The source of truth is AUDIT_EVENT_KINDS in
src/reyn/core/events/event_schema.py; this list is checked against it in CI,
and that declaration is in turn checked against the emitting code — in both
directions, so neither a kind without a declaration nor a declaration without a
producer can land. Adding a kind is a public-interface change, and it shows up
as a diff on this page.
Two things this list is deliberately not:
- Not the
opvalues.read_file,write_file,grepand their siblings are values of theopfield inside the sharedtool_executedkind, not kinds of their own. They are a different axis and are not part of this vocabulary. - Not a field contract. Which payload fields a given kind must carry is a
separate registry (
EVENT_AUDIT_REQUIREMENTS, same module) covering a subset of these kinds; the per-kind sections below document the payloads in prose.
agent_delta
agent_message_refused
agent_message_sent
agent_request_received
agent_response_received
asyncio_unhandled_exception
body_summary_hard_truncated
budget_reset
bus_subscriber_dropped
canonical_degraded
canonical_fallback_used
chain_peer_discarded
chain_timeout
chain_timeout_extended
chat_started
chat_stopped
chat_turn_completed_inline
client_attached
client_detached
client_seized
compact_op_completed
compact_op_failed
compact_op_requested
compact_op_unavailable
compaction_check
compaction_completed
compaction_failed
compaction_started
composer_dropped
composer_fired
config_reload_rejected
config_reloaded
control_ir_failed
control_ir_skipped
direct_alias_call_salvaged
elide_evaluated
embed_attempts
embed_cancelled
embed_secret_redacted
embedding_index_build_complete
embedding_index_build_error
embedding_index_build_progress
embedding_index_build_started
exec_threat_blocked
exec_threat_match
file_read_media_denied
force_close_triggered
hook_event_emitted
hook_push_fired
hook_shell_executed
hot_list_updated
inbox_cancel
index_dropped
index_update_cost_warning
index_updated
intervention_answer_submitted
intervention_denied
intervention_routed
limit_denied
llm_call_retry
llm_call_retry_exhausted
llm_called
llm_request
llm_request_error
llm_response_received
mcp_called
mcp_cancelled
mcp_completed
mcp_elicitation_answered
mcp_elicitation_auto_declined
mcp_elicitation_requested
mcp_elicitation_timed_out
mcp_failed
mcp_initialized
mcp_install_cancelled
mcp_install_probe_failed
mcp_install_threat_blocked
mcp_install_threat_match
mcp_progress
mcp_prompt_get
mcp_prompt_get_cancelled
mcp_prompt_get_completed
mcp_prompt_get_failed
mcp_prompt_list_changed
mcp_prompts_listed
mcp_resource_read
mcp_resource_read_cancelled
mcp_resource_read_completed
mcp_resource_read_failed
mcp_resource_subscribe
mcp_resource_subscribe_cancelled
mcp_resource_subscribe_failed
mcp_resource_subscribed
mcp_resource_templates_listed
mcp_resource_unsubscribe
mcp_resource_unsubscribe_cancelled
mcp_resource_unsubscribe_failed
mcp_resource_unsubscribed
mcp_resource_updated
mcp_resources_listed
mcp_server_installed
mcp_server_removed
mcp_tool_list_changed
mcp_tool_probe_degraded
mcp_tools_listed
memory_deleted
memory_saved
model_budget_fallback
model_cost_block
model_cost_warn
network_ssl_verify_disabled
new_msg_exceeds_budget
oauth_login_completed
oauth_login_started
peer_reply_failed_surfaced
pending_intervention_claimed
pending_intervention_discarded
permission_denied
permission_granted
pipeline_install_threat_blocked
pipeline_install_threat_match
pipeline_installed
pipeline_load_failed
pipeline_run_attached
pipeline_step_completed
pipeline_step_started
plan_step_llm_memoized
plugin_install_completed
plugin_install_copied
plugin_install_reconciled
plugin_install_registered
plugin_install_started
plugin_uninstall_completed
plugin_uninstall_registry_dropped
plugin_uninstall_started
presentation_install_blocked
presentation_installed
presentation_load_failed
presented
router_context_overflow_detected
router_context_overflow_unrecovered
router_empty_response_detected
router_empty_response_retry_injected
router_force_close_handoff
router_loop_terminated_by_exception
router_represent_round
router_retry_exhausted
routing_decided
safety_limit_checkpoint
sandbox_policy_narrowed
sandbox_policy_not_applied
sandboxed_exec_cancelled
sandboxed_exec_completed
sandboxed_exec_started
secret_cleared
secret_rotated
secret_set
semantic_search_complete
semantic_search_embed_failed
semantic_search_started
session_completed
session_halted
session_restored
session_started
skill_body_loaded
skill_install_threat_blocked
skill_install_threat_match
skill_installed
skill_invoke_body_loaded
skill_invoke_collision
state_change_notified
summary_resummarize_failed
summary_resummarized
threat_block
threat_scan_match
token_refresh_failed
token_refreshed
tool_call_cap_exceeded
tool_call_deduped
tool_called
tool_cycle_kept_whole_over_budget
tool_executed
tool_failed
tool_result_offloaded
tool_returned
turn_cancelled
turn_completed
turn_settled
turn_started
turn_too_large_truncated
untrusted_narrowing_engaged
user_answered_intervention
user_intervention_received
user_intervention_requested
user_message_received
user_submitted
web_fetch_completed
web_fetch_failed
web_fetch_media_denied
web_fetch_ssrf_blocked
web_fetch_started
web_fetch_too_large
web_fetch_too_many_redirects
web_search_completed
web_search_failed
web_search_started
workspace_updated
Every event has:
{
"type": "<event_kind>",
"timestamp": "2026-04-30T10:00:00.123456+00:00",
"data": {
... // kind-specific payload; may include agent_id / run_id when the
// emitting EventLog was configured with them (see below)
}
}Every event emitted from a session whose agent.id is configured (in reyn.yaml) automatically carries an agent_id field in its payload. The default value is reyn/<hostname>. This enables RBAC and multi-agent audit trails per SOC2 / ISO 27001 / METI v1.1 requirements.
See Concepts: multi-agent — "Agent ID propagation" for details.
| Kind | Key payload |
|---|---|
llm_called |
model (+ chain_id when the call belongs to a delegation chain) |
llm_response_received |
prompt_tokens, completion_tokens, cached_tokens, cache_creation_tokens, cost_usd, usage_source (+ chain_id) |
usage_source says where the token counts came from: provider (the provider
reported them) or estimated (the provider's stream carried no usage, so
LiteLLM filled the counts locally with its own tokenizer). unknown means the
origin was not stated. An estimated figure is recorded and enforced exactly like
a reported one — the field exists so a cost audit can tell them apart, and so
the turns billed on an estimate can be found afterwards:
reyn events .reyn/events --filter llm_response_received # then grep/jq for "estimated"See reference/config/budget.md — "Token-count provenance".
Each Control IR op kind emits its own event:
| Kind | When |
|---|---|
read_file, write_file, edit_file, delete_file, glob_files, grep, regenerate_index |
file op variants — all via tool_executed with op=<sub_op> |
sandboxed_exec_started, sandboxed_exec_completed |
sandboxed_exec op — started: argv, argv0_resolved, backend; completed: argv, argv0_resolved, backend, returncode, denial_class. argv0_resolved (#2820) is the absolute path actually executed: a version-manager shim (~/.pyenv/shims/python3) is resolved to its real binary by reading the manager's on-disk layout (part A — filesystem-only, no subprocess) so the sandbox runs the real binary directly instead of the shim, whose launch-fork() would die under (deny process-fork); equals the plain PATH resolution for a non-shim command, or the unchanged argv[0] when resolution is unavailable (fail-open). denial_class is "fork_denied" when the sandbox blocked fork() at a PATH launcher/shim (pyenv/asdf/mise/npx/uvx) under (deny process-fork), else null — an environment/config condition, not a tool failure |
mcp_called, mcp_completed, mcp_failed, mcp_cancelled |
MCP tool ops — mcp_cancelled (#2813) fires instead of mcp_completed/mcp_failed when a Ctrl-C cancel_event interrupts an in-flight call before it completes |
mcp_server_installed |
mcp_install op — name, key names only (no values) |
mcp_install_cancelled, mcp_prompt_get_cancelled, mcp_resource_read_cancelled, mcp_resource_subscribe_cancelled, mcp_resource_unsubscribe_cancelled |
#2813 — a Ctrl-C cancel_event interrupted the corresponding op (install probe / get-prompt / read-resource / subscribe / unsubscribe) before it completed; the op returns status:"cancelled" and nothing is committed |
web_search_started, web_search_completed, web_search_failed |
web_search ops — started: query, backend; completed: adds result_count; failed: adds error |
web_fetch_started, web_fetch_completed, web_fetch_failed |
web_fetch ops — started: url; completed: url, status_code, content_length, extractor; failed: url, status ("timeout" or "error"), error |
semantic_search_embed_failed |
semantic_search op (FP-0057 Phase 2a; renamed from recall_embed_failed) — emitted when a model group's embed call fails; query, model, error |
index_dropped |
index_drop op — source, chunks_dropped: int |
index_update_cost_warning |
index_update op (FP-0057 Phase 2a) — the to-embed batch exceeds embedding.cost_warn_threshold; source, chunk_count, estimated_tokens, threshold |
index_updated |
index_update op — source, added: int, updated: int, removed: int, skipped: int |
control_ir_skipped, control_ir_failed |
dispatch failures (control_ir_skipped reasons include handler_not_implemented) |
permission_denied |
When an op is denied by the resolver |
Unlike the Control IR mcp_* events above (tied to a tool-call op), these
fire asynchronously from the MCP connection/receive-loop, independent of any
op dispatch:
| Kind | Trigger | Key payload |
|---|---|---|
mcp_initialized |
Emitted on every (re)connect, once the server's initialize handshake completes. |
server, negotiated_version, capabilities |
mcp_resource_updated |
A subscribed resource's server-pushed resources/updated notification, or a synthetic resync fired per re-subscribed URI after a transport-death reconnect. Also wired into the hook dispatcher as an external-event hook-point — see Concepts: hooks. |
server, uri, resync (true for a reconnect resync, false for a real push) |
mcp_elicitation_requested |
A server issues an elicitation/create structured-input request. |
server, field_keys (the requested schema's property names only — never values) |
mcp_elicitation_answered |
The request resolves to accept or decline (human choice, or a decline from auto_decline config). |
server, field_keys, action ("accept" | "decline") |
mcp_elicitation_timed_out |
No answer arrived before elicitation_timeout_seconds. |
server, field_keys |
mcp_elicitation_auto_declined |
Declined without prompting — reason distinguishes a server configured elicitation: auto_decline from a headless context (no live intervention listener). |
server, field_keys, reason ("server_configured" | "headless") |
None of these events include the human's typed answer or any field value — only the requested schema's property names, matching the sensitive-field handling described in Concepts: MCP § Elicitation.
| Kind | Trigger | Key payload |
|---|---|---|
token_refreshed |
Emitted by reyn.secrets.get_valid_token(key) after a successful OAuth refresh against the provider's token endpoint (RFC 6749 §6). |
key: str — OAuth token key (same as the ~/.reyn/oauth_tokens.json entry); expires_at: str — ISO-8601 timestamp of the new access token's expiry. |
token_refresh_failed |
Emitted by get_valid_token when the token endpoint returns a non-2xx response or the response payload is malformed. Raises OAuthRefreshError. |
key: str; error: str — short error description (HTTP status + provider error code if available). |
Notes:
token_refresh_failedpairs withtoken_refreshed— exactly one is emitted perget_valid_tokencall that performs a network refresh.
See also: Concepts: secret handling — OAuth lifecycle and credential scoping; Concepts: permission model — per-skill credential scoping.
| Kind | Trigger | Key payload |
|---|---|---|
routing_decided |
Emitted at the router's single dispatch chokepoint (RouterLoop._dispatch_resolved) whenever a catalog action is dispatched — via the invoke_action wrapper, a bare hot-list-alias tool call, an ARS-salvaged direct call, or (#3455) the flat bare-name dispatch path used when action_retrieval.universal_wrappers_enabled: false is set in reyn.yaml. |
action_name: str; source: str — "invoke_action" | "hot_list_alias" | "ars_direct"; outcome: str — "success" | "error"; chain_id: str — request chain identifier for cross-call correlation. |
Notes: enables auditing catalog-action routing regardless of which entry surface the model used (#3455: previously gated on the invoke_action wrapper surface, so the universal_wrappers_enabled: false opt-out configuration never emitted this event at all). Cross-correlate with chain_id across the action's downstream events.
| Kind | When |
|---|---|
user_message_received |
A new user turn enters the runtime. Carries chain_id (the uuid minted by submit_user_text and propagated through any agent-to-agent messages this turn produces) |
user_intervention_received |
An ask_user op got its answer |
chat_started, chat_stopped |
Chat session lifecycle |
turn_cancelled |
A user turn was cancelled mid-router-loop (e.g. /cancel or a new submission supersedes the running turn). Payload: chain_id. |
The boundary events every trigger passes through, whatever surface submitted it.
Required fields are declared in src/reyn/core/events/event_schema.py.
| Kind | When | Key payload |
|---|---|---|
session_started, session_completed |
The session's resource scope opens / closes (alongside chat_started / chat_stopped). |
agent_name |
turn_started |
A trigger has been consumed from the inbox and is about to be dispatched. | kind — the inbox message kind that triggered this turn ("user", "agent_response", "task_ready", "hook", "agent_step", "external_message", "cron", …), so a subscriber can tell human triggers from automated ones without parsing the payload. #3595 made that reading TRUE rather than approximate: a pipeline agent step's prompt (step 1), and a webhook / MCP / A2A / cron push (step 1b), used to arrive claiming "user"; each now carries its own member, so kind == "user" means an operator typed the line at a first-party client and nothing else does. chain_id; seq |
turn_completed |
The router loop reached a terminal condition — router path only. | chain_id |
turn_settled |
The turn is done, for EVERY turn kind including slash / intervention short-circuits that return before the router. The reliable clear signal for a working indicator started on turn_started. |
kind; chain_id (may be absent for non-user triggers) |
Emitted by dispatch_tool (src/reyn/core/dispatch/dispatcher.py) around every
router-dispatched tool call. tool_returned and tool_failed are mutually
exclusive per call.
| Kind | When | Key payload |
|---|---|---|
tool_called |
Before invocation, after argument validation. | caller_kind, caller_id, tool, chain_id, args, args_hash |
tool_returned |
The invocation returned a value that does NOT declare an error (see tool_failed). |
caller_kind, caller_id, tool, chain_id, args_hash, result |
tool_failed |
The invocation was refused, raised, or returned normally with a self-declared error (#3450 — a handler's own {"error": ...} / {"error_message": ...} / {"error_kind": ...} return, plain or one level under its own {"status": "error", "data": {...}} self-envelope, promoted to this event instead of silently wrapped as a success). |
same, plus error_kind (permission_denied | exception | a validation reason | a handler-supplied kind | handler_error) and message |
args_hash is a stable SHA-256 prefix over the canonical-JSON arguments — the
correlation id that pairs a tool_called with its outcome across the log.
Remote fan-out. turn_started / llm_called / tool_returned / tool_failed
are the kinds the A2A and MCP progress surfaces forward to a remote peer
(src/reyn/core/events/progress_lifecycle.py); see
Concepts: A2A § Progress payloads
and Concepts: MCP § Progress notifications.
Three of the four (turn_started, tool_returned, tool_failed) are also what
the AG-UI transport maps to RUN_STARTED / TOOL_CALL_END — see
agui-transport.md § "Working-indicator path". Cost events
(llm_called) ride the progress fan-out but not the AG-UI working-indicator set.
| Kind | When | Key payload |
|---|---|---|
agent_message_sent |
_send_to_agent or _send_agent_response delivered a payload |
kind=agent_request|agent_response, from_agent, to_agent, depth, chain_id |
agent_request_received |
Receiving agent pulled an agent_request from its inbox |
from_agent, depth, chain_id |
agent_response_received |
Originating agent pulled an agent_response from its inbox |
from_agent, depth, chain_id |
agent_message_refused |
A send was refused (e.g. exceeded safety.loop.max_agent_hops) |
reason, to_agent, depth, chain_id |
chain_timeout |
A pending chain exceeded safety.timeout.chain_seconds and was force-resolved with a synthetic error response upstream |
chain_id, waiting_on (sorted list of agents that hadn't replied), timeout_seconds, origin_agent |
chain_id is uuid4 hex; one per top-level user submission, propagated unchanged across every hop. Cross-agent reconstruction is grep <chain_id> over each agent's events.jsonl plus history.jsonl.
| Kind | When |
|---|---|
workspace_updated |
Any artifact is written |
tool_executed |
Generic tool dispatch |
Every tool/op result is normalized to one canonical shape before it reaches the LLM. A producer with a real mapper is shaped cleanly; one that has no mapper (declared debt, or a genuinely unregistered source) takes a lossless whole-dict fallback instead — and that fallback is made visible here rather than silent, so unmapped-producer debt is one grep away.
| Kind | When | Key payload |
|---|---|---|
canonical_fallback_used |
A tool/op result took the whole-dict canonical fallback: a producer with declared-but-unwritten mapper debt, a genuinely unregistered / unknown source, or a passthrough producer whose whole-dict blob exceeded the structured offload gate. | source — the invoked producer id (op kind / tool name; null for a genuine unknown); reason — unregistered | canonical_todo | passthrough_oversized. Carries the source identity only — never the result body. |
| Kind | When | Key payload |
|---|---|---|
memory_saved |
The memory tool persisted a memory file to a layer |
layer, slug, path |
memory_deleted |
The memory tool deleted a memory file |
layer, slug, path |
These fire on chat turns as the context-budget advisor and compaction controller evaluate whether history needs summarising. Most carry a "checked but did not compact" outcome — they are high-frequency and mostly informational.
| Kind | When | Key payload |
|---|---|---|
compaction_check |
The compaction gate ran for a turn. outcome records the decision — e.g. too_few_turns, below_min_batch, pre_frame_overflow, already_running, forced_sync, forced_sync_no_turns. Some outcomes also carry turns, head, tail. |
outcome, plus outcome-specific fields |
compaction_failed |
A compaction attempt raised. | error |
compact_op_unavailable |
The compact Control IR op was dispatched in a context where no compaction engine is wired. |
run_id, phase |
summary_resummarize_failed |
Re-summarising an existing summary (nested compaction) raised. | error |
budget_reset |
The chat budget gateway reset its per-window accounting. | before (prior accumulated value) |
See Concepts: safety framework for the intervention flow and force-close wrap-up.
| Kind | When | Key payload |
|---|---|---|
limit_denied |
A safety limit was denied (no extension granted) and the OS is about to attempt the force-close wrap-up. | kind (max_iterations | router_cap), chain_id, plus limit (router iterations) or count/cap (router cap) |
reyn events .reyn/events/<run_id>.jsonlReplays the log to the console with the same formatting as a live run. The LLM is not re-invoked — replay is purely for inspection.
Two consequences fall out of "every state change emits":
- Replayability. A saved log is a complete record of execution. Future checkpoint/resume designs (see roadmap) build on this.
- Observability with no bolt-on. No separate logger, tracer, or telemetry hook — the same channel powers debug output, replay, and (eventually) eval analytics.
- control-ir.md — Control IR ops
- Concepts: events