Docs

Examples

Runtime State and Persistence

What to persist around agent runs, tool calls, events, traces, jobs, and audit records.

Anvia runs create several kinds of state. Do not collapse them into one table or one transcript. Messages, runtime events, product records, audit logs, traces, and job status each answer different operational questions.

Scenario

A support answer is wrong. The team needs to find the conversation, replay the agent events, inspect retrieved sources, see which tools ran, connect the external trace, and verify whether any side effects were written.

Persistence Map

State Store it in Used for
messages memory store or conversation table future prompts and user-visible chat history
stream events event store replay, debugging, and audit of runtime behavior
trace ids product record or trace backend cross-system observability
tool side effects product tables source of truth for business state
audit records audit log who requested or performed sensitive actions
retrieved evidence evidence log or trace metadata citation debugging and eval review
job status job table or queue backend UI status, retries, and worker recovery

Event Store

import type { AgentEventStore } from "@anvia/core/agent";

export const eventStore: AgentEventStore = {
  async append(input) {
    await db.agentEvents.insert({
      runId: input.runId,
      agentId: input.agentId,
      agentName: input.agentName,
      turn: input.turn,
      toolName: input.toolName,
      toolCallId: input.toolCallId,
      internalCallId: input.internalCallId,
      event: input.event,
    });
  },
  async load(runId) {
    return db.agentEvents.findMany({ where: { runId } });
  },
  async clear(runId) {
    await db.agentEvents.deleteMany({ where: { runId } });
  },
};

Agent Factory

import { AgentBuilder } from "@anvia/core";

const PERSISTENT_SUPPORT_INSTRUCTIONS = "Answer support questions using tools and retrieved policy.";

export function createPersistentAgent(scope: PersistentAgentScope) {
  return new AgentBuilder("support", scope.model)
    .instructions(PERSISTENT_SUPPORT_INSTRUCTIONS)
    .tools(createSupportTools(scope))
    .memory(scope.memoryStore, { savePolicy: "turn" })
    .eventStore(eventStore, { include: "all" })
    .observe(scope.observer)
    .build();
}

Runner

export async function runSupportTurn(input: SupportTurnInput) {
  const user = await input.auth.requireUser();
  const agent = createPersistentAgent({ ...input, user });
  const session = agent.session(input.conversationId, {
    userId: user.id,
    metadata: { tenantId: user.tenantId },
  });

  let final;
  for await (const event of session
    .prompt(input.message)
    .withTrace({
      name: "support-chat",
      userId: user.id,
      metadata: { tenantId: user.tenantId },
    })
    .stream()) {
    if (event.type === "final") {
      final = event;
    }
  }

  if (final === undefined) {
    throw new Error("Agent stream ended without a final event.");
  }

  await input.runs.record({
    runId: final.runId,
    traceId: final.trace?.traceId,
    conversationId: input.conversationId,
    userId: user.id,
    tenantId: user.tenantId,
    output: final.output,
  });

  return { output: final.output, runId: final.runId };
}

Event store writes are operational data. Product side effects still belong in product tables, and audit records should be written by the tools that perform sensitive actions.

What Not To Mix

Do not use As
memory authorization, audit, or current product state
event logs user-facing source of truth
traces durable business records
prompt text permission enforcement
model output unvalidated product writes

Production Checks

  • Every user-visible answer can be linked to a run id and trace id.
  • Side-effect tools write product records and audit records atomically where possible.
  • Runtime events have retention and redaction policies.
  • Job status is separate from detailed debug events.
  • Evidence logs or trace metadata identify which retrieved chunks influenced the answer.

Next Patterns