Memory Runtime Integration Guide
Status
Canonical implementation guide. Supersedes: v0.1. Companion to the LangGraph Runtime Implementation Guide (Layer 4) — this guide covers the Layer 4 ↔ Layer 7 binding. Related specs: Memory Fabric Spec, ADR-OS-002, Synedrix OS - Agent Spec, Synedrix OS - Agent Runtime Policy Spec, Synedrix OS Event Taxonomy Standard.
Purpose
This guide bridges the Memory Fabric Spec to concrete LangGraph and NAMS constructs. It is the memory counterpart of the LangGraph Runtime Implementation Guide: that guide maps agents onto graphs, this one maps memory onto stores.
A memory binding is non-compliant unless it preserves the control plane: scope resolution before retrieval,
recall as an authorized tool call, commit authority restricted to os-agt-010, lineage on every record,
tombstone-preserving erasure, and Langfuse tracing of every recall. A binding that lets an agent read or write
memory outside these paths is a shadow memory fabric.
1. OS concept → runtime construct
| OS concept | Runtime construct |
|---|---|
| Memory tier | A namespace segment in SynedrixMemoryStore, plus a Qdrant collection and/or a Postgres partition |
| Memory record | A row in memory_records (write-authoritative), projected to a Qdrant point and/or Neo4j node |
memory_profile.recall_scope | The scope intersection applied by os-agt-012 before the store query is built |
tool:memory-recall | A LangChain tool bound only if present in the resolved active_tool_set |
tool:memory-propose | A LangChain tool that emits a proposal; it never writes |
| Working tier | LangGraph graph state + a Postgres checkpointer |
| Formation / consolidation / reflection | Nodes in the background consolidation graph, not in the agent graph |
| Promotion | A tool call to os-agt-010, gated by os-agt-012 |
| Graph tier | NAMS MemoryClient in bolt mode against the OS Neo4j |
| Recall span | A Langfuse observation nested under the agent’s trace |
memory.* events | Emitted by os-agt-010 / os-agt-012, carrying the inherited correlation_id |
2. SynedrixMemoryStore — the compliance seam
LangGraph’s BaseStore is the only memory surface the Layer 4 runtime natively understands, so everything the OS
builds must present through it. Stock implementations (InMemoryStore, PostgresStore) are not canonical:
used directly they become a second source of truth with no authorization, no lineage and no retention.
The OS implements BaseStore itself. That implementation is where the control plane is enforced.
2.1 Namespace convention
(tenant_id, tier, scope_id)tenant_id is always first. This is fail-safe ordering, not style: a namespace assembled in the wrong order
under-matches and returns nothing, rather than over-matching and returning another tenant’s memory. scope_id is
the subject, agent or session identifier that visibility implies.
("acme", "semantic", "user-8842") # subject-scoped semantic memory
("acme", "procedural", "com-agt-003") # an agent's distilled skills
("acme", "episodic", "sess-1f9c") # session-scoped episodesPrefix search — asearch(("acme", "semantic")) — returns everything below that prefix, which is why a tenant can
never be reached by a partially-specified namespace.
2.2 The five methods, and what each must enforce
| Method | Enforcement |
|---|---|
aget(namespace, key) | Scope check via os-agt-012; superseded and tombstoned records are not returned. |
aput(namespace, key, value, index=None) | Rejects direct writes. Routes to tool:memory-propose; only os-agt-010 may commit. Raises rather than silently downgrading to a proposal. |
adelete(namespace, key) | Rejects. Deletion is erasure (§8.4 of the spec) and requires approval; it is never a store operation. |
asearch(namespace_prefix, *, query, filter, limit, offset) | Scope intersection and filters pushed into the Qdrant/Postgres query, never applied after retrieval. Returns records with provenance and validity_interval attached. |
alist_namespaces(...) | Returns only namespaces within the caller’s resolved tenant. |
The two rejecting methods are the load-bearing ones. aput and adelete are how a well-meaning agent would
otherwise write durable memory directly, bypassing commit authority and erasure approval — so the store refuses
them by design rather than by convention.
2.3 Backing stores
| Concern | Store |
|---|---|
| Record of truth | Supabase PG17 — memory_records and the lineage tables |
| Semantic index | Qdrant — one collection per fabric tier (below); KB consumer collections are a separate class |
| Entity / relationship / reasoning graph | Neo4j via NAMS |
| Working tier | Redis + the LangGraph checkpointer |
Wiping the Qdrant mem_* collections or the Neo4j fabric graph and rebuilding from Supabase must produce identical behaviour. A domain pack’s operational stores are rebuilt from the sources the pack declares (Memory Fabric Spec §3.6), and a full-store drill must run both. That is the operational
test for “projection, not source of truth”, and it should be run.
Qdrant collection contract (normative)
Fabric tier collections (Memory Fabric indexed tiers — one collection per tier; scope lives in the payload, not in the collection name):
| Collection | Tier |
|---|---|
mem_episodic | episodic |
mem_semantic | semantic |
mem_procedural | procedural |
mem_profile | profile |
KB / consumer collections are a separate class (example: synedrix_os_kb). Each consumer owns a dedicated collection and MUST NOT share a fabric tier collection. The same payload rules apply where the point represents a memory record; document ingest may carry additional source-path fields.
Operational collections are a third class: the domain-owned operational stores of Memory Fabric Spec §3.6. They are outside the tiers and the mem_* and KB rules above do not apply to them; these do:
- Name:
ops_<domain-pack-id>_<store>(for exampleops_trd_regime_fingerprints). Nevermem_*, and never a fabric tier or KB collection. Each belongs to one domain pack. - Point identity: a deterministic UUIDv5 (
uuid5(OPS_NS, name), withOPS_NS = uuid5(NAMESPACE_URL, "https://synedrix.io/ns/operational-point-id/v1")) wherenameis the UTF-8 stringtenant_id, thenbusiness_instance_id(the empty string for a tenant-level record, per Tenancy Spec rule 4), then each domain-key component, joined by the ASCII unit separatorU+001F. Components are Unicode NFC strings, integers are decimal with no leading zeros, and no component may containU+001F. The composite key is(tenant_id, business_instance_id, <domain key>), where the pack declares the domain key per collection (for exampleregime_version). It is never amemory_id, andtenant_idis part of the ID so two tenants, or two business instances of one tenant, with the same domain key cannot address each other’s point. Writes are idempotent upserts on that ID, and the scope payload filter below is still mandatory. - Tenant isolation:
tenant_idis a required indexed payload field and a mandatory filter on every query and write. Where the source data is business-instance-owned,business_instance_idis also a required indexed payload field and a mandatory filter, because a tenant can own several business instances with their own configuration and history. A request without it fails closed (invariant 12). Where the tenant has its own Qdrant (data-plane per tenant), the filter is still required. - Neo4j operational graphs: Neo4j Community has no database RBAC, so credentials cannot scope a graph. Agents reach an operational graph only through an allowlisted, parameterised query wrapper that injects the authenticated
tenant_id(andbusiness_instance_idfor business-instance-owned graphs) and the graph’s label predicates. The generic read-only Cypher tool is not granted for them. - Authorization (Qdrant): a collection-scoped credential (JWT
r/rwper collection, see Qdrant) is issued to the declared writer workflow, and a read-only credential to readers. No agent holds a credential that spans an operational and amem_*collection. - Rebuild: from the source the domain pack declares for that collection, never from
memory_records. The embedding model, model version and dimension are part of the collection contract, as above. The pack declares them per collection (or names the business-instance setting that pins them) alongside the source. - Readiness flags: where a pack gates reads on a readiness flag (for example Trading’s
scenario_vectors_ready:{version}), a rebuild clears the affected flags before wiping the collection and restores each only after all of that version’s points are committed. - Not recalled as memory: read by a bound domain tool, never through the recall contract.
Point identity: for fabric projections, the Qdrant point id MUST be the memory_id UUID string from memory_records.
Required payload fields (indexed for filterable search):
memory_id(string UUID; equals point id for fabric points)tenant_id(string)tier(string:episodic|semantic|procedural|profile)visibility(string: fabric visibility / ACL class from the memory record)scope_type(string:tenant|subject|agent|session|platform— matches Memory Fabric scope)scope_id(string; the resolved subject_id, agent_id, session_id, or tenant_id for that scope_type; required whenever scope_type is not purely tenant-wide)subject_id(string or null; indexed when present — subject-scoped profile/episodic)agent_id(string or null; indexed when present — agent-scoped procedural/episodic)session_id(string or null; indexed when present — session-scoped episodic)content_hash(string)confidence(number)valid_from,valid_until(timestamps or null-equivalent)consolidation_state(string)retention_class(string)compliance_labels(string array)version(string SemVer)
Recall queries MUST push tenant_id and the applicable scope filter (scope_type+scope_id, or the concrete subject_id/agent_id/session_id fields) into Qdrant. Post-filtering a full tenant result set for scope is forbidden.
Postgres remains write-authoritative. The embedding model and vector dimension are part of each collection’s contract: changing either requires a new collection and cutover, not in-place mutation. See also Qdrant.
3. Checkpointer, store, and memory record are three different things
The existing rule stands and is extended here:
memory_profile.working_memorymaps to graph state plus a checkpointer; canonical memory access is a tool node performing a Supabase read. The two must not be collapsed.
| Scope | Lifetime | Source of truth? | |
|---|---|---|---|
| Graph state | One super-step | In-process | No |
| Checkpointer | One thread / run | Until retention closes | No — run state only |
SynedrixMemoryStore | Cross-thread, tenant-scoped | Per retention class | No — a governed view of records |
memory_records (Supabase) | Tenant | Per retention class | Yes |
Two operational consequences:
- Checkpoint retention must exceed the longest approval timeout. A graph that interrupts for approval and loses its checkpoint before the human answers leaves an approved action with no run to resume.
- Do not share checkpointer state across unrelated agents. Each Layer 4 service gets its own checkpointer namespace, and checkpoint stores must be covered by the stack’s backup procedure.
4. Hot path versus background
| Stage | Where it runs | Why |
|---|---|---|
Recall (tool:memory-recall) | Hot path — a tool node in the agent graph | The agent needs the answer now. |
Formation proposal (tool:memory-propose) | Hot path — fire-and-forget, returns a proposal id | The agent must not wait for extraction. |
| Formation commit, consolidation, reflection, decay, demotion | Background — WF-OS-MEMORY-CONSOLIDATE | LLM-backed, latency-tolerant, must not block reasoning. |
| Erasure | Out of band — approval-gated compliance workflow | Has its own SLA and approver. |
4.1 The background workflow
WF-OS-MEMORY-CONSOLIDATE is a scheduled n8n workflow — Layer 3 owns the trigger and the sequence, as always:
flowchart TD S([schedule trigger]):::term --> F[fetch uncommitted proposals + consolidation candidates Supabase]:::agent F --> I[invoke os-agt-011 LangGraph, execution_pattern: reflect-and-revise]:::agent I --> PR[proposes: merges, promotions, distilled skills, conflict resolutions]:::agent PR --> V[os-agt-012: scope validation per proposal]:::check V --> PG[os-agt-002: policy gate for promotions requiring approval §7.3 of the spec]:::check PG --> C[invoke os-agt-010: commit]:::agent C --> EM[emit memory.record.consolidated / .promoted / reflection.completed / skill.distilled]:::agent EM --> O([persist Outcome Envelope]):::term
os-agt-011 runs as a LangGraph agent because consolidation is genuinely reflect-and-revise: a proposal is
generated, critiqued against the existing record set, and revised. That is the pattern the runtime split exists
to serve.
In Mode 3, this workflow competes with agent inference for local GPUs. Degrade by deferring the schedule,
never by skipping os-agt-012 or os-agt-002.
5. Binding NAMS for the graph tier
NAMS is adopted as a library over the existing self-hosted
Neo4j, not as a platform. The hosted memory.neo4jlabs.com service is not permitted in any deployment mode.
5.1 Configuration
# Self-hosted bolt mode ONLY. Never set MEMORY_API_KEY — its presence
# switches the client to the hosted service.
settings = MemorySettings(
uri="bolt://neo4j:7687",
database="neo4j", # Community Edition has exactly one
# standard database; a dedicated name
# is Enterprise-only. See §5.4.
llm="openai/local-model", # LiteLLM → vLLM on DGX
embedding="openai/local-embed",
)| Setting | Rule |
|---|---|
| Transport | Bolt against the OS Neo4j. MEMORY_API_KEY must be unset in every mode. |
MEMORY_LLM / MEMORY_EMBEDDING | Via LiteLLM. In Mode 3 these must resolve to local URIs (Ollama / vLLM). |
| Multi-tenancy | user_identifier carries tenant_id. Never omit it. |
| Concurrency | The client is async-only; all calls are coroutines. This fits LangGraph nodes directly. |
| Version | Pin explicitly. NAMS is an experimental Neo4j Labs project with no SLA. |
5.2 Scope of use — what NAMS owns and does not
| NAMS capability | Position |
|---|---|
| Long-term knowledge graph (POLE+O entities and relationships) | Adopted — this is the graph tier’s ontology |
Reasoning memory (AgentStep, ToolCall, :TOUCHED edges) | Adopted — supplies reasoning-to-entity lineage, the gap Academy 02.8 identifies |
| Entity deduplication / consolidation primitives | Adopted — invoked from os-agt-011, committed by os-agt-010 |
| Its own vector index | Scoped to entity resolution only. Qdrant remains the single semantic substrate; NAMS’s index is not a general retrieval path. |
| Short-term memory (conversations) | Not used. The working tier is Redis plus the checkpointer. |
| MCP server | Governed by mcp_tool_policy. The read-only Cypher tool is not exposed by default; the core profile is preferred over the extended one. |
| Hosted NAMS service | Rejected — proprietary SaaS, breaks Principle 11 and Mode 3. |
5.3 Writes still go through the control plane
NAMS’s client can write to Neo4j directly. It must not be used that way from an agent graph. Every NAMS write is
issued by os-agt-010 after os-agt-012 has validated scope, and carries the record’s correlation_id. A NAMS
call made from an arbitrary agent node is a governance bypass even though the data lands in the right store.
5.4 Running against Neo4j Community Edition
Vector indexes are available in Community Edition, so NAMS’s 5.20+ requirement is satisfied and the graph
tier is deployable on Community in all three modes. Verified against the Operations Manual’s per-edition feature
table and the Neo4j 5 changelog; see the Neo4j platform page.
Three Community limits shape the binding, and the control plane already absorbs all three:
| Limit | Consequence | How it is handled |
|---|---|---|
| Exactly one standard database | database= must be neo4j; no per-tenant database isolation | Tenant separation is enforced above the database, by the tenant-first scope tuple and os-agt-012. user_identifier still carries tenant_id on every call. |
| Only uniqueness constraints (existence, type and key constraints are Enterprise) | The POLE+O schema cannot enforce required properties or types at the database | Envelope validation happens in os-agt-010 before commit, which is where it belongs anyway — the database was never the only check. |
| No online backup | The graph cannot be hot-backed-up | Supabase is write-authoritative, so the Neo4j projection is rebuilt, not restored. This is the same property §2.3 already requires as a correctness test. |
Open item. NAMS is an experimental Neo4j Labs project with community support and no SLA. Version-pin the dependency and keep an exit path. The vector-index question is closed.
6. Governance bridge (mandatory)
Each item maps to an invariant in Memory Fabric Spec §10.
| Concern | Implementation |
|---|---|
| Scope | os-agt-012 intersects the request scope with memory_profile.recall_scope and pushes the result into the store query. Retrieving broadly and filtering afterwards is non-compliant. |
| Tool authorization | tool:memory-recall and tool:memory-propose are bound only if present in the resolved active_tool_set; anything else emits agent.tool.unauthorized. |
| Commit authority | SynedrixMemoryStore.aput rejects direct writes. Only os-agt-010 commits durable memory. |
| Lineage | Every record carries provenance.source_event_id, correlation_id, causation_id, inherited from the invocation and never regenerated. |
| Promotion | Every tier transition emits memory.record.promoted; promotions listed in spec §7.3 additionally pass os-agt-002. |
| Erasure | adelete rejects. Erasure runs its own approval-gated workflow and leaves a tombstone; graph lineage survives. |
| Observability | Every recall opens a Langfuse span nested under the agent trace, recording tiers requested, tiers denied, record count and scope applied. |
| Audit read-only | Never bind os-agt-008 as a memory tool. It queries the fabric; it is not invoked by it. |
7. Observability
Recall is measured with the metric families that already exist — no new vocabulary is introduced where the Observability Metrics Spec already has one.
| Signal | Source |
|---|---|
| Retrieval quality, groundedness | Existing metric families, applied to tool:memory-recall results |
| Recall latency, records returned, tiers denied | Langfuse span attributes on the recall observation |
| Consolidation lag (capture → committed) | memory.record.formed → memory.record.consolidated timestamps |
| Memory growth rate, staleness | Aggregated in QuestDB from memory.* events |
| Promotion rejection rate | os-agt-012 denials over promotion attempts |
A recall that returns records without their provenance and validity_interval cannot be scored for
groundedness. That is why §6.4 of the spec makes attaching them mandatory rather than optional.
8. Compliance checklist
Before declaring a memory binding compliant:
-
SynedrixMemoryStoreimplements all fiveBaseStoremethods;aputandadeletereject. - Namespaces are
(tenant_id, tier, scope_id)withtenant_idfirst. - Scope is resolved by
os-agt-012and pushed into the store query before retrieval. -
tool:memory-recall/tool:memory-proposeare bound only from the resolvedactive_tool_set. - No agent other than
os-agt-010commits toepisodic,semantic,proceduralorprofile. - Every committed record has
provenance.source_event_id,derived_fromandvalidity_interval. - Every tier transition emits
memory.record.promotedwith the inheritedcorrelation_id. - Erasure leaves a tombstone; graph lineage survives;
content_hashis retained. - Recall results carry
provenanceandvalidity_interval. - Recall spans export to Langfuse; no LangSmith dependency.
- Checkpointer retention exceeds the longest approval timeout, and checkpoint stores are backed up.
- Qdrant
mem_*collections and the Neo4j fabric graph can be wiped and rebuilt from Supabase without behaviour change, and each operational store (ops_*collections and domain graphs) can be rebuilt from its declared source in the same drill. - NAMS runs in bolt mode;
MEMORY_API_KEYis unset; in Mode 3MEMORY_LLM/MEMORY_EMBEDDINGare local URIs. - The NAMS MCP server is governed by
mcp_tool_policy; the read-only Cypher tool is not exposed by default. -
os-agt-008is not bound as a memory tool.
Versioning
- Version: 0.3
- Supersedes: synedrix-os-memory-runtime-integration-guide-v0.2.md
- Related: Synedrix OS Memory Fabric Spec, Synedrix OS ADR-OS-002
Related Documents
Core specs (what this guide implements)
- Synedrix OS Memory Fabric Spec — Tier taxonomy, record envelope, lifecycle, recall and promotion contracts, retention and erasure.
- Synedrix OS ADR-OS-002 — Memory Fabric Control Plane — The decision this guide implements; the NAMS and EverOS positions.
- Synedrix OS - Agent Spec —
memory_profile, the per-agent security boundary. - Synedrix OS - Agent Runtime Policy Spec — The three-layer tool authorization model recall passes through.
Orchestration and runtime
- Synedrix OS LangGraph Runtime Implementation Guide — The Layer 4 counterpart; graph state, checkpointers, the invocation contract.
- Synedrix OS n8n Implementation Guide — Layer 3 owns the
WF-OS-MEMORY-CONSOLIDATEtrigger and sequence. - Synedrix OS Interoperability Protocol Strategy —
mcp_tool_policygovernance for the NAMS MCP server.
Platform reference
- LangGraph — Checkpointers, stores, durability modes.
- Neo4j — The graph store NAMS binds to; Community Edition limits.
- Qdrant — Collections, payload filters, filtered search.
- Supabase PG17 — Write-authoritative store; RLS as a governance primitive.
Teaching material
- Synedrix OS Academy — Chapter 04.4 covers short-term state versus the canonical store and the tenant-first namespace rule; chapter 02.8 covers context engineering and the memory taxonomy. Teaching material, not normative: where a chapter disagrees with this guide, this guide wins.
Synedrix OS · August 2026