> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.thatdevguy.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent Tracing: Capture and Inspect Every Run in Anvil JS

> Record every model call, tool execution, and retrieval step as a structured span tree. Query traces in the dashboard or export them to any OTel backend.

Anvil's tracing system captures every agent run as a tree of spans — one span per model call, tool call, retrieval, cache hit, or judge evaluation — and rolls up token usage and cost into the parent trace. Pass a `Tracer` instance to `defineAgent` and every request handled by that route is automatically instrumented, with no changes required inside individual handlers or tools.

## Stores: where traces live

Anvil ships two `TraceStore` implementations. Choose one based on your environment.

<CardGroup cols={2}>
  <Card title="MemoryTraceStore" icon="flask">
    Zero-dependency in-memory store. Traces survive only for the lifetime of the process — ideal for unit tests and local experimentation where you don't need a database.
  </Card>

  <Card title="SqliteTraceStore" icon="database">
    SQLite-backed store powered by `better-sqlite3` (optional peer dependency). Persists traces across restarts. The recommended default for production. Default path: `.anvil/traces.db`.
  </Card>
</CardGroup>

### MemoryTraceStore

```ts theme={null}
import { MemoryTraceStore } from 'anvil-sdk/trace';

const store = new MemoryTraceStore();
```

Call `store.clear()` between test cases to reset state.

### SqliteTraceStore

```ts theme={null}
import { SqliteTraceStore } from 'anvil-sdk/trace';

const store = await SqliteTraceStore.open('.anvil/traces.db');
```

`SqliteTraceStore.open` creates the file and schema on first call. Pass `':memory:'` for a process-scoped SQLite database that behaves like `MemoryTraceStore` but uses the same SQL interface.

<Note>
  `better-sqlite3` is an optional peer dependency. Install it before using `SqliteTraceStore`:

  ```bash theme={null}
  npm install better-sqlite3
  ```
</Note>

## Creating a Tracer

`Tracer` wraps a `TraceStore` and optionally accepts an `onExport` hook that fires when each trace ends — this is the seam used by the [OpenTelemetry exporter](/observability/otel).

```ts theme={null}
import { Tracer, SqliteTraceStore } from 'anvil-sdk/trace';

const store = await SqliteTraceStore.open('.anvil/traces.db');

const tracer = new Tracer(store, {
  // Optional: called with the finished Trace object after each run ends.
  onExport: (trace) => console.log('trace finished', trace.id),
});
```

`TracerOptions` accepts one field:

<ParamField body="onExport" type="(trace: Trace) => void" optional>
  Callback invoked with the completed `Trace` after `traceHandle.end()` is called. Use this to pipe traces to an external system without coupling your store to the export logic.
</ParamField>

## Wiring the Tracer into defineAgent

Pass the `tracer` as an option to `defineAgent`. Anvil opens one `TraceHandle` per incoming request and closes it when the response stream ends.

```ts theme={null}
import { defineAgent } from 'anvil-sdk/agent';
import { Tracer, SqliteTraceStore } from 'anvil-sdk/trace';
import { openai } from 'anvil-sdk/llm';

const store = await SqliteTraceStore.open('.anvil/traces.db');
const tracer = new Tracer(store);

export const post = defineAgent({
  client: openai({ model: 'gpt-4o' }),
  tracer,
  tools: [/* ... */],
});
```

### Naming traces

By default, Anvil names each trace `agent <route-path>`. Override this with `traceName`:

<Tabs>
  <Tab title="Static string">
    ```ts theme={null}
    defineAgent({
      tracer,
      traceName: 'customer-support-agent',
      // ...
    });
    ```
  </Tab>

  <Tab title="Dynamic from context">
    ```ts theme={null}
    defineAgent({
      tracer,
      traceName: (ctx) => `support:${ctx.query.userId ?? 'anonymous'}`,
      // ...
    });
    ```

    The function receives the Anvil `Context` object for the current request, so you can embed user IDs, session tokens, or any other request-scoped data in the trace name.
  </Tab>
</Tabs>

## The Trace and Span types

Every finished trace is a `Trace` object containing a flat list of `Span` records that form a tree via `parentId` references.

### Trace

```ts theme={null}
interface Trace {
  id: string;
  name: string;
  startedAt: number;      // Unix ms
  endedAt?: number;       // Unix ms; set when the run ends
  status: SpanStatus;     // 'running' | 'ok' | 'error' | 'aborted'
  spans: Span[];          // full span tree
  totalCostUsd: number;   // rolled-up cost across all model spans
  totalInputTokens: number;
  totalOutputTokens: number;
  attributes: Record<string, unknown>;
}
```

### Span

```ts theme={null}
interface Span {
  id: string;
  traceId: string;
  parentId?: string;      // links into the tree; absent on root spans
  name: string;
  kind: SpanKind;         // 'agent' | 'model' | 'tool' | 'retrieval' | 'cache' | 'judge' | 'http'
  startedAt: number;
  endedAt?: number;
  status: SpanStatus;
  attributes: Record<string, unknown>; // model, provider, usage, cost, tool args/result, etc.
  error?: string;
}
```

`SpanKind` identifies what produced the span:

| Kind        | Description                                          |
| ----------- | ---------------------------------------------------- |
| `agent`     | The top-level agent loop span                        |
| `model`     | A single LLM generate call, including usage and cost |
| `tool`      | One tool execution                                   |
| `retrieval` | A `ctx.retrieve()` call                              |
| `cache`     | A semantic cache hit or miss                         |
| `judge`     | An LLM-as-judge evaluation                           |
| `http`      | An outbound HTTP call                                |

<Note>
  Retrieval spans from `ctx.retrieve()` are recorded in the **same** trace tree as the agent run. They appear as `kind: 'retrieval'` children of the enclosing `agent` span, so you can see the full RAG context alongside model calls in one view.
</Note>

## TraceStore interface

If you need a custom backend (Redis, Postgres, etc.) implement `TraceStore` directly:

```ts theme={null}
interface TraceStore {
  saveTrace(trace: Trace): void | Promise<void>;
  saveSpan(span: Span): void | Promise<void>;
  getTrace(id: string): (Trace | undefined) | Promise<Trace | undefined>;
  listTraces(opts?: ListTracesOptions): Trace[] | Promise<Trace[]>;
}
```

`ListTracesOptions` accepts `limit` and `offset` for pagination.

## Full example

```ts theme={null}
import { defineAgent } from 'anvil-sdk/agent';
import { Tracer, SqliteTraceStore } from 'anvil-sdk/trace';
import { anthropic } from 'anvil-sdk/llm';

const store = await SqliteTraceStore.open('.anvil/traces.db');
const tracer = new Tracer(store);

export const post = defineAgent({
  client: anthropic({ model: 'claude-3-5-sonnet-latest' }),
  tracer,
  traceName: (ctx) => `billing-agent:${ctx.query.customerId}`,
  tools: [lookupOrder, issueRefund],
  guardrails: [/* ... */],
});
```

Every request to this route produces a trace persisted to SQLite. Open `/_anvil` in the browser to inspect the span tree, token usage, and cost in real time.
