Skip to content

Event Reference

Session persistence and subscriptions use a versionless event format. Every new stored event contains engine_version, session_id, required nullable run_id, a positive physical seq, timestamp, a tagged payload, and an optional origin. The engine_version is the writing engine's binary semver; it is diagnostic metadata, not a compatibility gate, and old records may omit it. Pre-origin records omit origin; readers preserve that absence rather than synthesizing a default. Legacy event_schema_version fields are ignored when reading.

origin is either user or <class>:<slug>, where class is engine, plugin, or client and slug matches [a-z0-9][a-z0-9-]{0,63}. It identifies the author of that individual event, not the component described by its payload. For example, a diagnostic about a plugin can have origin engine:plugin-host.

Readers handle each JSONL line independently. Fully readable events load normally. If a known event has an absent or type-mismatched optional field, that field defaults and the event loads with an in-memory degradation diagnostic. Unknown event tags, broken required fields, corrupt JSON, and unknown envelope fields cause that event to be skipped. Sequence numbers must increase strictly, but gaps are retained and reported rather than making the session unreadable. Each contiguous missing sequence range produces one bounded diagnostic. An unreadable initial session_created record is the exception: without creation identity the session cannot open, so the engine reports an unsupported-era or corrupt-log error. The initial session_created record carries an optional short_id, the tree-unique model-facing handle; sessions created before handles existed lack the field and resolve by full UUID only.

Writers remain strict: every appended event passes validation. Event evolution is additive only. Existing variant tags and fields are never removed or renamed, existing required fields remain required, and new fields must be optional. New behavior that cannot fit an optional field uses a new variant tag. CI compares the generated EventPayload schema with the committed additive baseline. Because older binaries do not know the origin envelope key, their tolerant readers skip newly originated events and report an unknown-envelope diagnostic; they do not reinterpret those events without attribution.

Terminal tool-result truncation and tool_output_elided references use the retained-output readback lifecycle.

When tool-emitted additional messages are materialized as persisted turns, their event origin is engine:tool-result. The engine authors the durable turn from a validated tool result; the tool name remains payload context rather than a new origin class. This keeps the origin grammar restricted to user, engine, plugin, and client.

Payloads

Category Event payload types
Session session_created, session_reverted, session_permission_overlay_set, skill_loaded, skill_invocation_noted, session_title_committed, delegated_context_seeded
AGENTS.md context agent_md_loaded
Plugins plugin_event_added, plugin_diagnostic
Goals goal_activated, goal_checklist_revised, goal_lifecycle_changed
Producer messaging producer_message_accepted, producer_message_admitted, producer_messages_claimed, producer_messages_released, producer_message_consumed, producer_message_discarded
User input message_injected, user_input_admitted, user_input_submitted, user_input_transformed, user_input_recalled, user_input_recalled_v2, user_input_applied
Run run_started, run_completed, run_failed, run_cancelled, run_interrupted
Model model_attempt_started, model_request_prepared, text_delta, reasoning_delta, attempt_abandoned, model_replay_evaluated, model_turn_committed, model_usage_recorded, model_fallback
Tools tool_call_started, tool_call_progress, tool_call_terminated, tool_output_elided, tool_stdin_submitted, tool_call_linked, delegate_queued, delegate_finished, delegate_finished_v2, delegate_child_terminated
Delegation durability delegation_reserved, delegation_started, delegation_run_started, delegation_run_attached, delegation_finished
Approvals approval_requested, approval_evaluated, approval_escalated, approval_user_decision_recorded, approval_finalized, approval_cancelled, approval_doom_loop_detected, tree_approval_grant_committed
Internal agents internal_agent_started, internal_agent_usage_recorded, internal_agent_completed, internal_agent_failed, internal_agent_cancelled, internal_agent_interrupted, internal_agent_fallback
Compaction context_checkpoint_committed; legacy read/render only: context_rehydrated

Goal and producer contracts

Goal and producer state is projected independently from model summaries. The engine serializes mutations, message acceptance, and admission through the session actor. Goal activation and producer acceptance persist an otherwise buffered session, even before its first model request.

Event Payload fields Run scope
goal_activated goal_id, objective, revision Runless allowed
goal_checklist_revised goal_id, items, revision Runless allowed
goal_lifecycle_changed goal_id, status, revision Runless allowed
producer_message_accepted message_id, producer_owner, mode, idempotency_key, description, body; optional reminder Runless allowed, including during an active run
producer_message_admitted message_id Requires the destination run
producer_messages_claimed Nonempty message_ids Requires the admission run; envelope seq identifies the claim
producer_messages_released Positive claim_seq Requires the claimed run
producer_message_consumed message_id, run_id Requires matching envelope run_id
producer_message_discarded message_id; optional producer_owner, optional reminder Runless allowed

GoalId and ProducerMessageId are durable UUID identities. GoalItem contains only description and finished, with no item ID. Writer validation rejects blank objectives/descriptions. Revision is an engine-owned u64, including zero; strictly increasing revisions, overflow rejection, matching goal identity, and lifecycle transitions are enforced by the engine projection. Model updates replace the entire ordered checklist through the session actor: the last accepted update wins for the current/latest session goal at actor acceptance, without a model-supplied goal_id, expected_revision, or lost-update protection. An older run's update can intentionally affect a newly activated active or paused goal; updates reject if the current goal is absent or terminal. The engine records the selected goal's ID and revision in durable events. User lifecycle RPC controls retain SessionGoalLifecycleParams.goal_id and expected_revision, including stale-identity and stale-revision rejection. Empty checklist revisions preserve the active/paused lifecycle. Completion requires nonempty all-finished items, including while paused.

Handling of older stored checklist items containing an id is pending core implementation verification. No specific compatibility decoder or migration is promised here; the versionless best-effort session-history contract still applies.

ProducerOwner is tagged by type: plugin { plugin }, delegation { invocation_id }, goal { goal_id }, goal_control { goal_id }, or agent { session_id }. It identifies a specific stable owner, not a volatile registration or merely an owner kind. Accepted events are the sole authoritative message body/mode storage, including deferred queue sends while a run is active. Registration records are never session events. GoalControl { goal_id } owns engine-authored pause/cancel steering, not goal continuation reminders. These accepted messages survive goal-controller teardown and reminder invalidation. Accepted agent-owned messages may carry optional agent_hop metadata for the messaging loop guard. It is absent on non-agent producers and legacy agent mail, is not rendered in the agent-visible envelope, and does not participate in idempotency comparison.

The optional accepted-event reminder: GoalReminderIdentity { goal_id, revision, kind } identifies a goal reminder; if present, its goal must match the goal owner. kind is started or continuation, defaulting to continuation when absent from legacy events. Its body must contain the full objective and checklist, including finished items and revision. Each continuation attempt has a fresh message ID/idempotency key even at an unchanged revision. A consumed reminder cannot suppress later continuation. New goals use started until a same-goal reminder is covered by committed model input; acceptance, claims, discarded startup messages, and lifecycle-control notifications alone do not switch subsequent reminders to continuation.

Producer descriptions are compact, human-facing text; the full body remains model-facing. Legacy history without a description defaults to empty, allowing clients to fall back to a truncated body.

producer_message_admitted links the accepted body to a run without duplicating user-input storage or changing existing input events. Its physical sequence is covered by model_turn_committed.input_through_seq. Admission is not consumption: append producer_message_consumed only after a committed turn covers the admission in that same run. Recovery repairs missing consumption markers from committed coverage; an uncommitted API attempt remains retryable. Queue acceptance does not create a forbidden runless pending user input during an active run.

producer_messages_claimed durably reserves admitted messages at the actor boundary before request preparation/dispatch. The claim's envelope sequence is its token, not evidence that the provider received input. Projection requires unique accepted, admitted, unconsumed, undiscarded message IDs belonging to the claim's run. Claims cover compaction, hooks, streaming, and model commit. producer_messages_released references the claim sequence in the same run and removes that claim. Failed or cancelled attempts release their reservation; recovery also releases surviving claims. Once all claims are released, an unconsumed, undiscarded message returns to waiting and the queue strip. Consumed messages remain consumed after release. Claim/release is not consumption and does not establish exactly-once model execution.

Discard is separate from consumption and is a generic durable producer-message event, not restricted to goal reminders. discard(session, message_id) checks the caller's ownership against the accepted message, including after its producer unregisters. Owned already-discarded retries are idempotent; cross-owner discard is rejected. Only unclaimed waiting messages qualify, including admitted messages not yet claimed. Consumed or claimed messages reject discard, even before actual provider dispatch; absence of a consumption marker is not proof that a message is retractable. The actor serializes discard against claim acquisition. Durable discard removes the message from pending delivery/readiness and the queue strip, and replay must not restore it as pending. Goal reminder invalidation still checks its identity; it does not authorize discarding another owner's real message.

Projection requires a supplied producer_owner to exactly match the accepted owner; a supplied reminder must also match. Historical reminder-only discards remain readable: when owner is absent, a matching reminder identity is required. An absent owner is not generic discard authority, and a wrong supplied owner cannot fall back to legacy reminder matching. Both forms reject consumed or claimed messages. Claimed messages are hidden from the waiting strip until release makes them eligible again; in-flight input cannot be retracted.

Missing/corrupt optional reminder metadata follows the existing best-effort reader, with degradation diagnostics. Engine projection must quarantine invalid goal-owned reminder records rather than reclassifying them as real sends. Cross-event ownership, at-most-once consumption, and commit coverage are engine invariants, not properties a single-event decoder can verify.

Goal state and unconsumed messages must survive compaction. Fork copies durable branch data, never registrations; revert reconciles only the visible branch. Failed/disabled recovery notices use the additive PluginDiagnosticKind values recovery_failed and recovery_disabled, not persisted readiness/registration records.

Existing event behavior

agent_md_loaded is a run-scoped root-session event stamped engine:agent-md. It contains one or two discovered AGENTS.md entries, each with a bounded display source, retained content, truncation flag, and original byte size. Missing or disabled context produces no event. History uses only the latest run's event and replays its entries as one provenance-delimited user turn. Compaction pins that turn alongside loaded skill bodies. See System Prompt Composition.

Legacy plugin_event_added is a runless plugin publication containing plugin, name, and arbitrary JSON payload. Plugin-originated payloads are capped at 256 KiB, names at 128 characters, and the complete serialized event at 272 KiB; per-plugin/session rate and byte quotas apply. The event is normal branch content: it survives reopen and fork, is visible to model history and compaction, and is tombstoned by revert. plugin_diagnostic records operational notices such as interception timeouts, hook blocks, invalid modifications, oversized publications, and dropped stream counts. Current model-bound plugin sends must use an explicitly registered producer; plugin/emit cannot append model-visible session publications. Bus traffic is unchanged.

Delegation persistence includes delegate_queued and delegate_finished. The completion event is written to the parent run and carries { session_id, status, preview, short_id, total_lines }; short_id is the optional child handle so replay can render the same teaser the runtime pushed, and preview is limited to the first 20 lines and 2 KiB. It becomes model-visible at the next turn boundary, while full output remains available through get_subagent_result.

Later delegation records add delegated_context_seeded and delegate_finished_v2. delegated_context_seeded is a runless creation event containing the bounded, text-only user/assistant turns copied by inherit_context; it must precede the child's first run and deterministically rebuilds the same initial model history after restart. delegate_finished_v2 adds the delegation invocation_id to the same completion payload so repeated resumes of one child each receive exactly one teaser. Current background completions first durably accept a steer producer message, then emit V2 for transcript consumers. Model history suppresses V2 when the invocation has a producer acceptance, so the result is materialized only once. Foreground delegation retains its tool result as its only result channel.

The delegation_* lifecycle records are durable control events on the parent session. delegation_reserved contains the complete current delegate request, child agent snapshot, selected model suffix, runtime revisions, request fingerprint, and optional typed staged-skill provenance. delegation_started records publication of the child session; delegation_run_started and delegation_run_attached distinguish a new child run from attachment to an already-running resumed child. delegation_finished records the terminal status, optional recovery reason, and child session/run references. The existing delegate_queued and delegate_finished_v2 events remain the model-visible queue and result-summary records.

Engine startup projects delegation state while opening session logs. A reserved or started delegation without a terminal event resumes or rolls back according to its child and parent state. Recovery recomputes the reservation fingerprint; a mismatch rejects the reservation. If best-effort reading skips a corrupt delegation event, that delegation is not recovered, the parent session records the diagnostic, and unrelated delegations remain recoverable. Legacy delegations.jsonl files are ignored, so pre-release in-flight journal entries do not cross this upgrade.

The runless session_permission_overlay_set event's overlay contains the complete, validated set of unique (action, resource) rules for the session. The latest visible event wins. Because it is an ordinary branch event, restart replay, revert, and fork recover the same overlay without a sidecar file.

model_usage_recorded follows a committed provider turn and contains the positive model_turn_seq, agent ID, resolved model identity, and Oven's normalized usage fields: inclusive input/output totals plus optional uncached input, cache-read input, cache-write input, text output, and reasoning output counts. The optional estimated_cost_pico_usd stamps the engine-selected price for that request: a number is priced, present null is unpriced, and an absent field identifies an older event. Usage rollups preserve stamped prices exactly and apply the current pricing configuration or catalog only to legacy records where the field is absent. A present null stamp is durably unpriced and is never recomputed. Event-log validation requires its run, agent, model, and turn to match and rejects duplicate records for one turn. Session, agent, and global usage projections rebuild from these events after restart, revert, and fork.

model_request_prepared follows request assembly and all accepted model/provider request hooks. Its prompt_fingerprint hashes the authoritative normalized request sent to the provider, while model_attempt_started remains the earlier cancellation and lifecycle boundary.

tool_call_started records the single/named output declaration. Raw output subscriptions enumerate these channels, including arbitrary named streams. tool_call_progress contains a control-free status message and optional UI-only display. Display deltas append; terminal result.display replaces them. The reader accepts historical output_chunk fields, but current writers use display. Each live message/display field is bounded at 1 KiB, their cumulative per-call budget is 64 KiB, and final display has an independent 64 KiB bound. TUI state is bounded and never reconstructs display by merging authoritative streams.

Authoritative output chunks go to runtime capture before completion, not into unbounded event records. Terminal retained_output contains full artifact or generic manifest references, ordered per-stream byte/line counts, truncation offsets, and an incomplete flag. Failed/cancelled calls retain accepted output with their terminal status and error. Self-paginating tools omit output retention and fail instead when a requested page exceeds the 2 MiB result bound. Historical truncation and tool_output_elided records remain readable.

During cancellation cleanup, a progress record is considered retained once its append command enters the session actor mailbox; actor FIFO then orders it before the later terminal command even if persistence has not completed yet. Cleanup deadline diagnostics count only display progress records that never entered that mailbox; authoritative chunks already accepted into capture remain retained.

For a batch of tool calls from one model turn, tool_call_started events are written in model content order before execution begins. Calls may then execute in parallel, so progress and tool_call_terminated events from different calls may interleave, and terminations commit in completion order. The ordering guarantee is per call: its start precedes all of its progress and stdin events, which precede its single termination. Consumers must correlate calls by tool_call_id rather than cross-call event position.

internal_agent_usage_recorded attributes each completed internal model request to its internal run, internal agent ID, kind, and resolved model. It carries the same optional estimated_cost_pico_usd stamp. The same session, agent, and global projections include these records.

skill_loaded and skill_invocation_noted persist skill use. skill_loaded stores the rendered body, skill name, source path, arguments, base directory, and at most ten supporting-file paths. It is pinned across context checkpoints. An identical repeat uses the shorter skill_invocation_noted payload.

user_input_admitted normally belongs to an active run. A steer accepted for a queued delegated child is runless and requires that the session have no active run. It is persisted immediately and enters the next run's pending-input FIFO, including when a terminal child is queued for resume. A queued cancellation appends matching runless user_input_recalled events so those inputs cannot leak into a later resume. Promotion appends the ordinary run-owned user_input_submitted event, so transcript rendering and model history use the same path as interactive run steering.

message_injected is a run-owned role and text message accepted from agent_before_start. It is written during run setup and projected into model history, so reopen, fork, revert, and compaction use the injected message without consulting the plugin again. user_input_transformed is the run-owned audit record written immediately before user_input_submitted when user_before_input changes direct input; it contains both original_input and the non-empty committed input.

Running-resume rollback appends user_input_recalled_v2 with the exact user_input_seq of its admission. Replay removes that specific pending input and preserves direct steers admitted after it. Legacy user_input_recalled continues to recall the newest pending input.

Compaction checkpoints

context_checkpoint_committed carries a commit containing the text summary or opaque native checkpoint, boundaries, and budgets. Internal summaries cover the entire active pre-compaction history, including recent messages retained unchanged after the summary. Active history is the currently assembled context, not a reload of events replaced by earlier checkpoints. The original saved log is preserved; retained recent messages are replayed from it. System and tool context comes first, followed by pinned AGENTS.md, loaded skills, the summary, and the recent messages. See Compaction for selection and fit rules.

The additive retention fields are optional in saved events:

Field within commit Type Legacy absent value Meaning
boundaries.recent_from_seq optional u64 None Inclusive starting sequence of the retained original-message suffix. No value means no retained suffix.
budgets.keep_recent_tokens u64 0 Effective recent-history token budget for this checkpoint, not the raw configured request or a count of retained tokens.

The effective budget is at most min(configured keep_recent_tokens, context_limit / 4) and is further limited by actual post-checkpoint available space. Complete tool-call/result groups can leave the retained suffix smaller than that budget; an oversized newest indivisible group yields no tail rather than exceeding the budget. An absent legacy retention boundary does not cause the reader to infer a suffix from current configuration.

Native compacted windows retain their existing replay behavior, with no independently appended recent-message suffix. Neither checkpoint path triggers new file rereads or emits context_rehydrated. That legacy payload (ContextRehydrated in Rust) remains decodable and renderable for saved logs; its persisted contents do not require a new filesystem read.

Subscriptions

events.subscribe returns all retained events after the optional cursor and starts events.subscription notifications. A notification is tagged as either:

{ "type": "event", "event": { "engine_version": "0.1.0", "seq": 43 } }

or a gap indicating that the subscriber must rebuild its disposable projection:

{
  "type": "gap",
  "session_id": "...",
  "last_delivered_seq": 42
}

Revert markers are delivered like every other physical event. Clients should rebuild branch-derived state when one arrives; they must not assume sequence numbers were truncated or reused.

Tool stdout and stderr use separate snapshot, delta, and gap notifications. Offsets are byte offsets, and clients use a snapshot after a gap before applying later deltas. The ordinary event subscription also includes durable tool_call_progress output chunks and terminal events without a separate filter.