Why the v0.2 operational primitives are opt-in
Buffered writes, consolidation, multi-tenancy scoping, privacy/audit, and the evaluation harness (the v0.2 surface) all share a design stance: each one is off by default, each one defaults to the least destructive behavior it can, and each one leaves an explicit trail instead of acting silently. This page explains that stance and what each primitive trades away for it. For the procedures themselves, see Buffered writes, Memory consolidation, Associate memory with users and select scoped reads, and Evaluate memory quality.
Opt-in by default
None of these primitives change behavior until an application asks for them:
-
Buffered writes require
MemorySettings.memory.write_mode="buffered"; the default is synchronous, where every write awaits its Neo4j round-trip. -
User scoping is per call: passing
user_identifier=to a supported write method associates the conversation, preference, or trace with a:Userwhether or not any setting is changed. Calls without it behave as before.MemorySettings.memory.multi_tenant=Truedoes not turn scoping on; it is a guard that makesadd_message,add_messages_batch,add_preference, andstart_traceraiseValueErrorwhenuser_identifier=is omitted, so an accidental unscoped write fails instead of landing unowned. Turning it on therefore breaks existing unscoped writes until they pass a user. -
Consolidation, evaluation, and audit recording are methods on
client.consolidationandclient.evalthat an application calls explicitly — nothing runs on a timer inside the library.
The library ships no built-in scheduler for any of this. An application
that wants periodic dedupe, nightly archival, or a CI evaluation gate has to
wire that itself; see the runnable examples under
examples/
for the shape (eval-harness/ci_gate.py for a CI gate,
buffered-writes/ for a timed sync-vs-buffered comparison).
Keeping these opt-in means existing v0.1 code keeps working unchanged after upgrading, and it means adopting one primitive never silently pulls in the behavior of another.
Dry-run-first where the operation mutates the graph
Consolidation is the primitive that actually rewrites the graph, and every
one of its four jobs — entity dedupe, long-trace summarization, preference
supersede detection, conversation archival — defaults to dry_run=True. A
dry run returns the same candidate report a live run would produce, without
touching a node or relationship. An application reviews (or scripts a
threshold check against) that report, then re-runs the same call with
dry_run=False to apply it.
This shifts the risk of a bad merge or a wrongly archived conversation from "discovered after the fact" to "visible before it happens." It also means consolidation can run against production data during development without a separate throwaway database — the dry run is read-only by construction, not by convention.
The one job carved out of that pattern is detect_superseded_preferences:
even in dry-run form, its candidate query does not enforce that the proposed
replacement belongs to the same user as the old preference in a
multi-tenant graph. That is a real gap, not a tradeoff — review candidates
by hand or use the owner-checked path in
Store and revise a user preference instead of
applying its output directly.
Audit-logged where a read or a write needs a trail
Two different audit mechanisms cover two different needs:
-
A
dry_run=Falseconsolidation run that applies changes writes a(:ConsolidationRun)node recording when that job last ran and what it touched — a trail for graph mutations. -
client.consolidation.record_read_audit(…)writes a(:MemoryReadAudit)node, optionally linked to a(:User)through:PERFORMED_READ, for a sensitive read (a preference lookup, an export, a cross-tenant query).
Neither is automatic. The library does not intercept every
execute_read call and log it, because that would double the database
traffic for reads an application does not consider sensitive. Auditing is
callsite-explicit: you decide which reads matter enough to record, and you
call record_read_audit at that callsite.
What each primitive trades away
| Primitive | Cost of the opt-in, dry-run-first design |
|---|---|
Buffered writes |
A queued-but-undrained write is lost if the process is killed before
|
Consolidation |
Dry-run-first means consolidation never prevents a duplicate or a
stale preference from existing — it only helps you find and clear ones
that already exist, on a schedule you own. |
Multi-tenancy scoping |
|
Privacy and audit |
Because auditing is callsite-explicit, an audit trail only exists for
the reads and writes a developer chose to instrument. A sensitive read
added later without a |
Evaluation harness |
The harness is a scaffold for regression detection against your own
labeled cases, not a benchmark or a guarantee of retrieval quality. It
reports whether this run’s scores moved against your labels; it
does not replace the |