Skip to main content

Overview

This guide shows you how to send your agent’s long-term memory operations to Latitude. Once instrumented, every read and write appears on the Memory page: each store’s current contents, per-record history and diffs, and the tokens read and written per session, with every change linked to the session that caused it.
You keep using your memory store exactly as you do today. You emit one span per memory operation, and Latitude derives the history, diffs, and token counts from those spans. You never compute or send a diff yourself.

Requirements

  • Base tracing already sending traces to Latitude. If you are not tracing yet, start with Start tracing.
  • An app with long-term memory: state your agent persists and reloads across separate interactions, such as a memory/ directory, a database table, a vector store, a key-value store, or a provider like Mem0, Zep, or Supermemory. Within-request conversation history is not memory; base tracing already captures it.
Memory operations ride the same exporter as the rest of your telemetry, so there is no extra account or endpoint to configure.

Instrument with your coding agent

Recommended. The Latitude skill inspects your codebase, finds your memory read and write call sites, and instruments them with the right operations and identifiers.
1

Ask your agent to add memory tracing

Paste this prompt into Claude Code, Cursor, Windsurf, Codex, OpenCode, or another coding agent:
2

Run your agent

Trigger one interaction that recalls memory and one that writes it.
3

Check Latitude

Open your project and go to Memory. The store appears within a few seconds of the trace completing.
Prefer to wire it up by hand? Continue below.

The store and record model

Two identifiers carry the whole model, and getting them right is the entire job, because they are how Latitude groups, versions, and attributes everything. Think of a store as a git repository and a record as a file in it. Store (gen_ai.memory.store.id) is one isolated pool of memory. The rule: two operations share a store if and only if a write by one should be visible to the other.
  • Per-user memory that users cannot see across each other: use the user id, or a user/<id> prefix, as the store id, so each user becomes a separate store.
  • Shared memory that several users read and write: one store id for all of them, and the latest write wins across them.
  • Always set it. A span without a store id lands in the (unattributed) store, which counts as activity but gives you nothing to browse. Keep it stable, or a store’s history fragments into many stores.
Record (gen_ai.memory.record.id) is one addressable unit within a store: what a write creates or updates and what a read returns. Use the store’s natural key, such as a file path relative to the memory root, a key-value key, a row id, or the provider’s own record id. Latitude splits record ids on / to nest them into folders on the Memory page, so a path-like id gives you a browsable tree. The only operations without a record id are the whole-store ones.
Writes carry the whole record, not a delta. Each write span is a full snapshot of the record as it stands after the write. If your app updates only part of a record, read back or reconstruct the full body and send that. Sending only the changed fragment makes Latitude read everything you omitted as a deletion.
Content capture is opt-in. Record bodies and the search query can hold sensitive data, so the SDK does not send them unless you enable captureContent. With it off you still get classification and counts, but no diffs, no token deltas, and nothing to browse. Turn it on unless the memory holds data you cannot send to Latitude; when only some fields are sensitive, keep it on and scrub them with the redact hook.

Instrument manually

1

Install

The memory helper ships in the same telemetry package as base tracing, so there is nothing new to add if you already installed it.
2

Create the memory helper

Pass the latitude instance from your base tracing setup. Set storeId to scope the memory, using a user id for per-user memory, and enable content capture so diffs and token deltas work.
3

Emit memory operations

Call the helper at your store’s read and write boundaries, inside the capture() that already wraps the interaction so the spans join the trace. Each operation has a wrap form, where you pass execute and the helper runs your call inside the span and records its latency, status, and errors, and an emit form, with no execute, for when the read or write already happened elsewhere.

Without the SDK

If your app traces to Latitude without the Latitude SDK, or runs in another language, emit the spans on your existing tracer. Set the span name and gen_ai.operation.name to the operation, and add the gen_ai.memory.* attributes.
Other runtimes such as Go, Java, Ruby, and .NET set the same attributes on whatever span they already export. Only gen_ai.operation.name is required; add the rest to unlock more of the Memory page.

Operations and attributes

Memory operations are the standard OpenTelemetry GenAI memory operation spans, not a Latitude-specific format. The span name equals gen_ai.operation.name, and both are one of: These attributes control what Latitude can show:

Verify

Instrumentation is not finished when the code compiles, only when operations show up correctly in Latitude.
  • Run a real interaction that recalls memory and one that writes it, so you produce both a search_memory span and a mutating span. For short-lived scripts, call await latitude.flush() (Python: latitude.flush()) before exit so spans flush.
  • On the Spans tab, the operations classify and carry the store and record attributes.
  • On the Memory page, the store appears under the expected id, its records show the expected bodies, and a second write to a record produces a new version and a diff.
  • On the trace or session detail, the Memory row shows tokens read, added, and removed.
Common issues: an empty store.id (everything lands in (unattributed)), an unstable or missing record.id (history will not stitch and reads will not attribute), a write sending a partial body instead of the full snapshot, or a span emitted outside capture() so it never joins the trace.

Next steps

  • Memory: Explore stores, record history, diffs, and per-session footprint
  • Start tracing: Base tracing setup, if you are not sending traces yet