Privacy and audit
|
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 |
Record an explicit audit event after a sensitive read and mark old conversations
for archival. These are Bolt consolidation operations; the hosted client does
not implement client.consolidation.
Prerequisites
-
A connected Bolt
client, withsettingsconfigured as in Store, retrieve, and summarize messages. -
A test user and seeded preferences for the read example.
-
A retention policy that distinguishes archival markers from deletion and access controls.
Run the fragments in an async function. The encryption section describes a protocol boundary; there is no client setting that installs an encrypter.
1. Record a read audit
For sensitive read paths (preference lookups, exports, cross-tenant queries), record an audit node alongside the actual read:
prefs = await client.long_term.get_preferences_for("[email protected]")
audit_id = await client.consolidation.record_read_audit(
"get_preferences_for [email protected]",
user_identifier="[email protected]",
kind="preference.read",
result_count=len(prefs),
metadata={"endpoint": "/api/preferences", "request_id": "req-abc"},
)
The audit node is (:MemoryReadAudit {id, kind, query, user_identifier,
result_count, metadata_json, recorded_at}) plus a
(:User)-[:PERFORMED_READ]→(:MemoryReadAudit) edge when a user
identifier is supplied.
The library does not transparently intercept every execute_read call
to record audits — that would multiply database traffic by 2x and
record reads the agent doesn’t care about. Auditing is callsite-explicit
on purpose.
2. Preview and apply conversation archival
Configure a default TTL via settings, then run the archival job periodically:
settings.memory.conversation_ttl_days = 90
async with MemoryClient(settings) as client:
report = await client.consolidation.archive_expired_conversations(
ttl_days=settings.memory.conversation_ttl_days,
dry_run=True,
)
print(f"Candidates: {report.candidate_count}")
for candidate in report.candidates:
print(candidate.description)
After reviewing the candidates, repeat with dry_run=False to apply them.
Pass ttl_days explicitly: setting conversation_ttl_days does not schedule a
job or supply the argument automatically. Candidates use created_at, falling
back to updated_at only if creation time is missing.
Archival sets c.archived = true and c.archived_at = datetime() on
each conversation. It does not delete messages or automatically exclude
archived conversations from retrieval. Apply your application retention and
access policy separately; the marker alone is not a deletion mechanism.
3. Verify audit and archival records
rows = await client.query.cypher(
"MATCH (a:MemoryReadAudit {id: $id}) "
"RETURN a.kind AS kind, a.result_count AS result_count",
{"id": audit_id},
)
assert rows == [{"kind": "preference.read", "result_count": len(prefs)}]
After applying archival, inspect report.actions_taken and report.run_id,
then read the selected conversations' archived and archived_at properties.
A dry run should produce no new archive markers.
Optional: Implement content encryption in your application
neo4j_agent_memory.core.encryption.MessageContentEncrypter defines
encrypt(plaintext: str) → str and decrypt(ciphertext: str) → str.
It is a runtime-checkable protocol, not middleware wired into message storage.
NoOpEncrypter returns its input unchanged and provides no encryption.
An application can implement that protocol using its chosen key-management
and encryption library, and call it explicitly at its own write/read boundary.
A runtime isinstance check proves only that methods exist. Verify a round
trip, wrong-key failure, and that stored content differs from plaintext using
the actual implementation before relying on it.
Account for derived data as well: embeddings, extracted entities, metadata,
and logs may expose information even when the stored message body is encrypted.
If you submit ciphertext to add_message, disable extraction and embedding
for that write unless your application deliberately supplies another strategy.
Database encryption at rest is a separate deployment control.
See also
-
Memory consolidation —
archive_expired_conversationsand the underlying audit-run pattern. -
Associate memory with users and select scoped reads — the
:Usernode that audit edges attach to.