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.
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.
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.
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.
- TypeScript
- Python
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.- TypeScript
- Python
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.- TypeScript
- Python
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 andgen_ai.operation.name to the operation, and add the gen_ai.memory.* attributes.
- TypeScript
- Python
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 equalsgen_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_memoryspan and a mutating span. For short-lived scripts, callawait 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.
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