Events¶
An event is an immutable record of something that happened in a
run. Events are append-only — once an event lands in the store,
nothing modifies it. The graph state is a projection of the event
log (see graph); behaviors fire by subscribing to
events and producing more events.
The event log is the source of truth. Everything else — the graph, the trace, the audit history — is derived from it.
The structure¶
Every event has:
id— framework-generated, monotonic per run, unique per run.type— a string discriminator. Framework events use a dotted namespace (object.created,behavior.completed,runtime.idle); user code emits custom types viagraph.emit(any string is valid, but the dot-namespaced convention is recommended).payload— a dict of JSON-encodable values. The framework enforces JSON encodability at emit time; seenon-serializable-event-error.actor— who or what produced the event."user"for goals pushed in from outside,"runtime"for framework-emitted events, a behavior name for behavior-emitted events.caused_by— the id of the event that triggered the behavior that produced this one. The causal chain is reconstructable by walkingcaused_byback to a root event (goal.created, typically).timestamp— ISO 8601, set at emit time. Used for the trace display; behavior bodies must not depend on it for determinism (seebehaviors— the determinism contract).
The framework event types¶
Events emitted by the runtime itself fall into a small set of families:
- Lifecycle:
goal.created,runtime.idle,runtime.budget_exhausted— boundary events around a run. - Object mutations:
object.created,object.patched,object.removed— every graph mutation lands as one of these. - Relation mutations:
relation.created,relation.removed. - Behavior dispatch:
behavior.started,behavior.completed,behavior.failed,behavior.scheduled— what the runtime did while running behaviors. - Pattern matching:
pattern.matched— emitted beforebehavior.startedwhen the behavior used a pattern subscription; carries the match count. - LLM / tool:
llm.requested,llm.responded,tool.requested,tool.responded— every LLM call and every tool call appears as a request/response pair. - Patches:
patch.proposed,patch.applied,patch.rejected— the patch lifecycle. - Approvals:
approval.proposed,approval.granted— the policy-gated approval lifecycle. - Pack lifecycle:
pack.loaded— emitted once perruntime.load_packcall, carries the pack name, version, and prompt content hashes.
Custom event types from user code live alongside these and follow
the same shape. Behaviors subscribe to either set with the same
on= argument.
Append-only and what that means¶
Once an event is in the store, it doesn't change. No edit, no
delete, no truncate (except via the explicit truncate_after
primitive, which is operator-side, not behavior-side). This is
the property that makes replay work:
Runtime.load reads the
event log and produces the same graph state every time.
Three consequences:
- There's no "current value" of an object outside its event
history. An object's data is the result of applying every
object.createdandobject.patchedevent for that object id, in order. The in-memoryObject.datadict is a cache of that computation, not an authoritative store. - Operations that look like mutations are emissions.
add_objectemitsobject.created;patch_objectemitsobject.patched;remove_objectemitsobject.removed. The graph in memory updates as a side effect of the emit. - The audit trail is automatic. Anything that happened in a run is in the event log. Nothing else is needed for audit — there's no separate audit-log subsystem because the event log is the audit log.
Events vs exceptions¶
The framework distinguishes two failure modes: exceptions for caller-actionable problems the caller can catch at the call site, events for non-fatal stops the audit trail should record and the runtime should continue past. Behavior failures, tool failures, budget exhaustion, and approval denials are events. Construction errors, lookup misses, replay divergence, and pattern syntax errors are exceptions.
See failure-model for the full principle and
why the framework treats them differently. The principle was
load-bearing across the v1.0 audit and is referenced from most
other concept pages — failure-model.md is the canonical
statement.
Reading the event log¶
The event log is available three ways:
# In-memory, current run:
for event in graph.events:
...
# From the store, by run id:
from activegraph.store import open_store
store = open_store(url, run_id)
for event in store.iter_events():
...
# CLI, operator-side:
# activegraph inspect <url> --run-id <run> --tail 50
# activegraph inspect <url> --event <event_id>
The trace printer (Runtime.print_trace()) is the human-readable
projection of the event log — same data, formatted with tags and
short summaries for visual scanning. The trace is informational;
the events are the data.
What's related¶
graph— the projection of the event log. Owns the "graph as projection" principle.behaviors— the reactive code that subscribes to events.failure-model— the events-vs-exceptions distinction.replay— the operation that uses the append-only property to reconstruct state.