Memory consolidation
|
Available on NAMS: No. The procedure on this page needs the Bolt backend. With the hosted NAMS backend its operations are unavailable: depending on the call, the client raises |
How to keep your memory graph clean over time. The library ships four dry-runnable consolidation primitives — entity dedupe, long-trace flagging, preference supersede detection, and conversation TTL archival.
All primitives default to dry_run=True: they identify candidates
without mutating the graph. Pass dry_run=False to apply changes,
which writes a :ConsolidationRun audit node when candidates are applied so you can track when
each job last ran.
Prerequisites
-
A connected Bolt client and configured
MemorySettings; run the snippets inside an async function. -
A representative test graph with embeddings and the relevant vector indexes.
-
A scheduler owned by your application; the library does not start periodic jobs.
In 0.7.0, do not apply detect_superseded_preferences to a database that holds
more than one user’s preferences. Even with user_identifier, its
candidate query scopes the old preference but does not enforce the same owner
on the proposed replacement. Review candidates and use explicit, owner-checked
superseding in Store and revise a user preference instead.
Goal
Preview each hygiene job before scheduling mutations:
from neo4j_agent_memory import MemoryClient
async with MemoryClient(settings) as client:
# 1. Find near-duplicate entities above 0.95 similarity.
report = await client.consolidation.dedupe_entities(
similarity_threshold=0.95, dry_run=True
)
print("Candidate pairs:", report.candidate_count)
# 2. Flag long traces for out-of-band summarization.
await client.consolidation.summarize_long_traces(
min_steps=20, dry_run=True
)
# 3. Inspect preference candidates on a single-user test graph only.
await client.consolidation.detect_superseded_preferences(
dry_run=True
)
# 4. Archive conversations older than 90 days.
await client.consolidation.archive_expired_conversations(
ttl_days=90, dry_run=True
)
Steps
1. Always dry-run first
Every primitive returns a ConsolidationReport with a
candidates list. Inspect it before applying.
report = await client.consolidation.dedupe_entities(dry_run=True)
for c in report.candidates[:10]:
print(c.description)
print(f"... {report.candidate_count} total candidates.")
2. Apply with dry_run=False
report = await client.consolidation.dedupe_entities(
similarity_threshold=0.95, dry_run=False
)
print(f"Run id: {report.run_id}")
print(f"Applied: {report.actions_taken}")
When mutations occur, the library writes a
(:ConsolidationRun {id, kind, started_at, completed_at, stats_json})
node. Inspect the most recent run for a given kind. This is an audit record, not a
transactional checkpoint or an automatic scheduler:
MATCH (cr:ConsolidationRun {kind: 'dedupe_entities'})
RETURN cr.id, cr.completed_at, cr.stats_json
ORDER BY cr.completed_at DESC LIMIT 1
3. Idempotent re-runs
Each primitive skips work it has already done — for example,
summarize_long_traces only flags traces where
summarization_pending is not already true. Re-running on a clean
graph is a no-op.
4. Verify the applied state
Compare report.actions_taken with the candidates you reviewed. When changes
were applied, query report.run_id in ConsolidationRun and inspect the
resulting relationship or marker. A run with no candidates has no run ID.
Repeat the dry run to confirm those candidates no longer appear. Each operation
can make multiple writes; a mid-run failure is not an atomic rollback.
Primitives reference
| Primitive | What it does |
|---|---|
|
Find entity pairs with embedding similarity above the threshold (defaulting to 0.95) that aren’t already linked via |
|
Find |
|
Find pairs of preferences in the same category with high embedding similarity but different text — likely supersedes. Mutation writes |
|
Mark |