AgentOps Memory Observability
See every memory operation in your AgentOps traces. The first SDK-level tracing instrumentor shipped by any memory vendor: one line after agentops.init() and every add, recall, query, and forget appears as a first-class span — with memory content excluded by default.
How it works
- Install
opentelemetry-instrumentation-memorysyncnext toagentops. agentops.init()registers AgentOps as the global OpenTelemetry tracer provider.instrument_memorysync()wraps the MemorySync SDK once — every client in the process now emits standard OTel spans through whatever provider is active.- Your memory operations land in AgentOps traces next to your LLM calls. No adapter, no plugin, no AgentOps-specific code.
Before you start
- An AgentOps account and API key, plus a MemorySync API key.
- Python 3.9+ with the
memorysyncSDK 1.8+ in your app.
Install
pip install opentelemetry-instrumentation-memorysync agentops
Set up
Step 1 — two lines at startup
Order doesn’t matter — the instrumentor binds the active provider late, so it works before or after agentops.init():
import agentopsfrom opentelemetry.instrumentation.memorysync import instrument_memorysyncagentops.init() # registers AgentOps as the global OTel providerinstrument_memorysync() # before or after init() — both work
Step 2 — use the SDK as normal
Nothing else changes. Every operation now produces a span:
from memorysync import MemorySyncClientclient = MemorySyncClient(api_key="ms_...", base_url="https://api.memorysync.io")client.add("Prefers window seats", source="chat") # span: memorysync.addclient.query("seating preferences", k=5) # span: memorysync.query
Zero-code alternative
The package registers the standard OTel entry point, so the CLI wrapper also works with no source changes:
opentelemetry-instrument python app.py
What flows into your traces
| Operation | Span | Key attributes |
|---|---|---|
add / bulk_add / summarize | memorysync.add … | memory_id, status (stored/skipped), skip reason |
query / retrieve / recall / search_routed | memorysync.query … | k, results_count, score min/max/avg, context_chars, server latency |
add_turn | memorysync.add_turn | tenant/user/session ids, role, processing_status, request_id (the extraction job), already_exists idempotency signal |
forget | memorysync.forget | selector (ids/filters), ids_count, deleted_count, dry_run |
get / update / history / feedback / list_memories | memorysync.get … | memory_id, updated field NAMES (never values), result counts |
append_history / list_history / delete_history | memorysync.append_history … | session_id, turns_count, extract, created_count, deleted_count, deleted_facts_count |
put_state / get_state / list_state / search_state / delete_state / list_state_namespaces | memorysync.put_state … | namespace, key, created, request_id, result counts, score min/max/avg, deleted_facts_count |
add_turn sends one turn to fact extraction and does not store the turn itself, so its span reports the extraction receipt (processing_status: distilling, skipped_non_user_turn, skipped_low_value, skipped_replay, or skipped over the plan limit) rather than a memory id. The conversation-history and framework-state methods exist in the memorysync SDK 1.10.0 and later; with an older SDK installed, the memory operations are traced as usual.
A memorysync.client.operation.duration histogram is recorded per call for latency dashboards.
Privacy: content is never recorded by default
Memory content is customer data. By default the spans carry counts, identifiers, scores, and latencies — never memory text, query text, or recalled context. Opt in explicitly with instrument_memorysync(capture_content=True) or MEMORYSYNC_OTEL_CAPTURE_CONTENT=true; even then input is truncated to 500 characters and at most 5 result texts of 200 characters each are recorded.
Engineering guarantees
- Instrumentation can never break the app. Attribute extraction is fully guarded; results and exceptions pass through untouched; spans always end.
- Full sync/async parity — both
MemorySyncClientandAsyncMemorySyncClient, all twenty-three operations (the incumbent wrapper skipshistoryon cloud clients). - Errors are first-class: failed calls produce ERROR spans with
error.typeand the HTTP status, and the original exception is re-raised unchanged. - Idempotent lifecycle: double-instrument is a no-op;
uninstrument_memorysync()restores the original methods.
Troubleshooting
- No memory spans in AgentOps — confirm
agentops.init()actually ran in the same process, and thatinstrument_memorysync()was called before the operations you expected to see. - Memory text missing from spans — that’s the privacy default. Opt in with
capture_content=Trueif you truly need it. - Double-instrumented by accident — harmless; the second call is a no-op.
- Need to detach —
uninstrument_memorysync()restores the original client methods.
Supported versions
| Surface | Requires | Verified on |
|---|---|---|
opentelemetry-instrumentation-memorysync 1.1.0 | Python 3.9+; opentelemetry-api 1.20+; memorysync 1.8+ | 74 CI checks: the REAL memorysync SDK over production response shapes with the OTel in-memory exporter — sync/async parity, add_turn extraction receipts, the history and state spans, privacy default, byte-identical pass-through, error spans, lifecycle, the late-binding provider seam — plus agentops installed at latest with seam drift alarms pinning the global-tracer-provider contract |