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:
or a gap indicating that the subscriber must rebuild its disposable projection:
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.