Events, journals, and execution evidence
Separate model stream events from semantic Run events, persist audit evidence, and reconstruct causal execution trees.
Two event streams
Runifold deliberately separates model protocol events from Run events.
ModelStreamEvent reconstructs a provider response: content blocks, reasoning,
tool calls, usage, warnings, provider extensions, and terminal completion.
RunEventKind records what the execution system did: lifecycle changes,
budget updates, callable boundaries, effects, children, cancellation, failure,
and completion.
Model events can become prompt-visible content. Run events are control and audit evidence; they must not be injected into the model conversation by accident.
The Run event model
Every semantic event carries stable identity and causal location. A root Run may create child Runs for model calls, tools, delegated Agents, or workflow steps. Shared accounting and cancellation do not erase those individual identities.
Use domain events for facts operators or recovery code must understand. Use provider events for lossless wire information that has no normalized meaning yet. Do not encode critical state transitions only in log strings.
The journal contract
Journal is the append boundary for semantic evidence. InMemoryJournal is
useful in tests; durable stores preserve evidence across process loss.
RunRecorder connects structured events to the active RunContext.
A journal write failure is not “just a logging error” when downstream audit or recovery depends on it. Decide at the application boundary whether work may continue, fail closed, or degrade with an explicit signal.
Reconstruct a Run
A useful reconstruction answers:
- which root request started the work;
- which children were created and in what causal order;
- what authority and budget were consumed;
- which external effects were requested and completed;
- where cancellation or failure originated;
- whether a terminal outcome was committed.
OpenTelemetry answers operational questions across services. The Journal answers semantic questions about one execution. Correlate them by Run and invocation identity rather than treating either as a replacement for the other.
Capture policy
Record identifiers, normalized status, model identity, usage, timing, error kind, and safe extension metadata. Keep prompts, outputs, tool arguments, credentials, raw documents, and provider bodies redacted by default.
Metric labels must remain low-cardinality. Put Run IDs and request-specific facts in traces or journal records, never Prometheus labels. Review capture policy as a security and data-retention decision, not a debugging preference.
Record and inspect semantic events
use std::sync::Arc;
let journal = Arc::new(InMemoryJournal::new());
let run = RunContext::root(
BudgetTracker::new(Budget::default()),
CapabilitySet::new(),
)
.with_journal(journal.clone());
run.record(RunEventKind::Domain(DomainEvent {
namespace: "acme.orders".into(),
name: "order.validated".into(),
payload: json!({ "order_id": "ord_42" }),
}), None)?;
for event in journal.events() {
println!("{} {:?}", event.meta.sequence, event.kind);
}Use stable low-cardinality event names and safe payloads. If payload contains customer text, credentials, raw Tool arguments, or provider bodies, store a redacted reference instead. Durable stores should read events in sequence order; consumers must be idempotent because delivery or projection may repeat.