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 NotSupportedError, AttributeError or TypeError, or ignores a Bolt-only setting or argument (NAMS manages embedding and extraction server-side). See the backend capabilities reference for what NAMS provides instead.

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, with settings configured 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