Event Store
Persist agent stream events for replay and debugging.
An event store records runtime events from PromptRequest.stream(). Use it when you need to replay or inspect what happened during a run after the live stream has ended.
Most applications start by rendering stream events directly to a UI:
for await (const event of agent.prompt(prompt).stream()) {
render(event);
}That is enough for live output, but the event data is gone once the stream is consumed. An event store gives you a durable runtime log for debugging, local inspection, Studio-like replay, audits, tests, or post-run analytics.
Why Event Store Exists
Memory and event storage solve different problems.
| API | Stores | Used as future model context |
|---|---|---|
memory(...) | Conversation messages: user prompts, assistant messages, final tool results | Yes |
eventStore(...) | Runtime events: text deltas, tool progress, nested child-agent events | No |
Memory is deliberately transcript-shaped because it is loaded back into future prompts. If Anvia stored every streamed text delta, tool progress event, nested child-agent turn, or UI-only runtime marker in memory, future model calls would receive noisy partial state instead of a clean conversation.
The event store exists so you can keep that runtime detail without polluting future model context.
Use memory when the model should remember something. Use event store when your application should remember how a run executed.
When To Use It
Use an event store when you need any of these:
- replay a run after the live stream is finished
- inspect nested
asTool({ stream: true })child-agent progress - debug which tool or child agent produced a bad result
- build a run timeline or Studio-style viewer
- calculate latency, tool usage, or agent activity after the run
- keep an audit log of runtime execution separate from conversation memory
Skip it when you only need the final response, or when your UI consumes live stream events and does not need replay later.
Design Boundary
Anvia treats model transcript, runtime events, and observability as separate surfaces:
| Surface | Main question | Typical consumer |
|---|---|---|
| Memory | What should the model know next time? | Future prompt runs |
| Event Store | What happened during this run? | Product UI, replay, debugging |
| Observers | What telemetry should be exported? | Tracing and monitoring systems |
This separation keeps each storage layer small and predictable. Your application can store events in the same database as memory if you want, but the APIs stay separate so you can apply different retention, indexing, privacy, and replay policies.
Configure an Event Store
const agent = new AgentBuilder("support", model)
.eventStore(eventStore, { include: "all" })
.build();Anvia calls your store during streaming runs:
interface AgentEventStore {
append(input: AgentEventAppendInput): Promise<void>;
load(runId: string): Promise<AgentEventRecord[]>;
clear?(runId: string): Promise<void>;
}
type AgentEventStoreOptions = {
include?: "all" | "agent_tool_events";
};include: "all" stores every parent stream event and every nested child-agent event. Choose this when you want a full run replay.
include: "agent_tool_events" stores only nested child-agent events emitted by asTool({ stream: true }). Choose this when the parent stream is already easy to reconstruct from memory, but child-agent progress would otherwise be lost.
Event storage is tied to streaming runs. A normal .send() call stays opaque and does not emit or persist runtime stream events.
Store Shape
type AgentEventAppendInput = {
runId: string;
agentId: string;
agentName?: string;
turn?: number;
toolName?: string;
toolCallId?: string;
internalCallId?: string;
event: unknown;
};The event field is unknown because an event store is a durable log boundary. Store it as JSON or another format your application controls. Use runId to group one run, and use toolName, internalCallId, and agentId to group nested child-agent progress.
The terminal final stream event also includes runId, so an application can load the saved runtime log after the live stream ends:
let runId: string | undefined;
for await (const event of agent.prompt(prompt).stream()) {
render(event);
if (event.type === "final") {
runId = event.runId;
}
}
const savedEvents = runId === undefined ? [] : await eventStore.load(runId);Streaming Agent Tools
Event stores are especially useful with streaming multi-agent tools:
const coordinator = new AgentBuilder("coordinator", model)
.tools([
supportAgent.asTool({ name: "ask_support_agent", stream: true }),
engineeringAgent.asTool({ name: "ask_engineering_agent", stream: true }),
])
.eventStore(eventStore, { include: "all" })
.build();During .stream(), callers receive live agent_tool_event values and the event store receives the same runtime history for replay. The parent agent and child agent models must both support streaming for nested progress to appear; otherwise the agent-tool still returns its final result normally.
for await (const event of coordinator.prompt(prompt).stream()) {
if (event.type === "agent_tool_event") {
console.log(event.agentId, event.event.type);
}
}The parent model still receives only the final child-agent output as the normal tool_result. Partial child deltas are for UI, debugging, replay, and inspection.
Production Notes
append(...) runs in the streaming path before each event is yielded. Keep it fast: write to a local database, enqueue work, or batch outside the stream if your storage backend can add noticeable latency.
Apply retention and redaction policies to event storage separately from memory. Event logs may contain partial deltas, tool arguments, intermediate tool results, and nested child-agent output that you might not want to keep as long as conversation memory.
Minimal In-Memory Store
class InMemoryAgentEventStore implements AgentEventStore {
readonly records: AgentEventRecord[] = [];
async append(input: AgentEventAppendInput): Promise<void> {
this.records.push({ ...input, createdAt: new Date() });
}
async load(runId: string): Promise<AgentEventRecord[]> {
return this.records.filter((record) => record.runId === runId);
}
async clear(runId: string): Promise<void> {
const remaining = this.records.filter((record) => record.runId !== runId);
this.records.length = 0;
this.records.push(...remaining);
}
}For a runnable example, see examples/cookbook/07_multi_agent/04-agent-event-store.ts.
