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 conceptRuntime construct
Memory tierA namespace segment in SynedrixMemoryStore, plus a Qdrant collection and/or a Postgres partition
Memory recordA row in memory_records (write-authoritative), projected to a Qdrant point and/or Neo4j node
memory_profile.recall_scopeThe scope intersection applied by os-agt-012 before the store query is built
tool:memory-recallA LangChain tool bound only if present in the resolved active_tool_set
tool:memory-proposeA LangChain tool that emits a proposal; it never writes
Working tierLangGraph graph state + a Postgres checkpointer
Formation / consolidation / reflectionNodes in the background consolidation graph, not in the agent graph
PromotionA tool call to os-agt-010, gated by os-agt-012
Graph tierNAMS MemoryClient in bolt mode against the OS Neo4j
Recall spanA Langfuse observation nested under the agent’s trace
memory.* eventsEmitted 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)
Illustrative example, not a production implementation. Review and test before use.

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 episodes
Illustrative example, not a production implementation. Review and test before use.

Prefix 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

MethodEnforcement
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

ConcernStore
Record of truthSupabase PG17 — memory_records and the lineage tables
Semantic indexQdrant — one collection per fabric tier (below); KB consumer collections are a separate class
Entity / relationship / reasoning graphNeo4j via NAMS
Working tierRedis + 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):

CollectionTier
mem_episodicepisodic
mem_semanticsemantic
mem_proceduralprocedural
mem_profileprofile

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 example ops_trd_regime_fingerprints). Never mem_*, and never a fabric tier or KB collection. Each belongs to one domain pack.
  • Point identity: a deterministic UUIDv5 (uuid5(OPS_NS, name), with OPS_NS = uuid5(NAMESPACE_URL, "https://synedrix.io/ns/operational-point-id/v1")) where name is the UTF-8 string tenant_id, then business_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 separator U+001F. Components are Unicode NFC strings, integers are decimal with no leading zeros, and no component may contain U+001F. The composite key is (tenant_id, business_instance_id, <domain key>), where the pack declares the domain key per collection (for example regime_version). It is never a memory_id, and tenant_id is 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_id is a required indexed payload field and a mandatory filter on every query and write. Where the source data is business-instance-owned, business_instance_id is 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 (and business_instance_id for 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/rw per 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 a mem_* 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_memory maps to graph state plus a checkpointer; canonical memory access is a tool node performing a Supabase read. The two must not be collapsed.

ScopeLifetimeSource of truth?
Graph stateOne super-stepIn-processNo
CheckpointerOne thread / runUntil retention closesNo — run state only
SynedrixMemoryStoreCross-thread, tenant-scopedPer retention classNo — a governed view of records
memory_records (Supabase)TenantPer retention classYes

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

StageWhere it runsWhy
Recall (tool:memory-recall)Hot path — a tool node in the agent graphThe agent needs the answer now.
Formation proposal (tool:memory-propose)Hot path — fire-and-forget, returns a proposal idThe agent must not wait for extraction.
Formation commit, consolidation, reflection, decay, demotionBackground — WF-OS-MEMORY-CONSOLIDATELLM-backed, latency-tolerant, must not block reasoning.
ErasureOut of band — approval-gated compliance workflowHas 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",
)
Illustrative example, not a production implementation. Review and test before use.
SettingRule
TransportBolt against the OS Neo4j. MEMORY_API_KEY must be unset in every mode.
MEMORY_LLM / MEMORY_EMBEDDINGVia LiteLLM. In Mode 3 these must resolve to local URIs (Ollama / vLLM).
Multi-tenancyuser_identifier carries tenant_id. Never omit it.
ConcurrencyThe client is async-only; all calls are coroutines. This fits LangGraph nodes directly.
VersionPin explicitly. NAMS is an experimental Neo4j Labs project with no SLA.

5.2 Scope of use — what NAMS owns and does not

NAMS capabilityPosition
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 primitivesAdopted — invoked from os-agt-011, committed by os-agt-010
Its own vector indexScoped 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 serverGoverned 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 serviceRejected — 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:

LimitConsequenceHow it is handled
Exactly one standard databasedatabase= must be neo4j; no per-tenant database isolationTenant 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 databaseEnvelope validation happens in os-agt-010 before commit, which is where it belongs anyway — the database was never the only check.
No online backupThe graph cannot be hot-backed-upSupabase 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.

ConcernImplementation
Scopeos-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 authorizationtool:memory-recall and tool:memory-propose are bound only if present in the resolved active_tool_set; anything else emits agent.tool.unauthorized.
Commit authoritySynedrixMemoryStore.aput rejects direct writes. Only os-agt-010 commits durable memory.
LineageEvery record carries provenance.source_event_id, correlation_id, causation_id, inherited from the invocation and never regenerated.
PromotionEvery tier transition emits memory.record.promoted; promotions listed in spec §7.3 additionally pass os-agt-002.
Erasureadelete rejects. Erasure runs its own approval-gated workflow and leaves a tombstone; graph lineage survives.
ObservabilityEvery recall opens a Langfuse span nested under the agent trace, recording tiers requested, tiers denied, record count and scope applied.
Audit read-onlyNever 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.

SignalSource
Retrieval quality, groundednessExisting metric families, applied to tool:memory-recall results
Recall latency, records returned, tiers deniedLangfuse span attributes on the recall observation
Consolidation lag (capture → committed)memory.record.formed → memory.record.consolidated timestamps
Memory growth rate, stalenessAggregated in QuestDB from memory.* events
Promotion rejection rateos-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:

  • SynedrixMemoryStore implements all five BaseStore methods; aput and adelete reject.
  • Namespaces are (tenant_id, tier, scope_id) with tenant_id first.
  • Scope is resolved by os-agt-012 and pushed into the store query before retrieval.
  • tool:memory-recall / tool:memory-propose are bound only from the resolved active_tool_set.
  • No agent other than os-agt-010 commits to episodic, semantic, procedural or profile.
  • Every committed record has provenance.source_event_id, derived_from and validity_interval.
  • Every tier transition emits memory.record.promoted with the inherited correlation_id.
  • Erasure leaves a tombstone; graph lineage survives; content_hash is retained.
  • Recall results carry provenance and validity_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_KEY is unset; in Mode 3 MEMORY_LLM / MEMORY_EMBEDDING are local URIs.
  • The NAMS MCP server is governed by mcp_tool_policy; the read-only Cypher tool is not exposed by default.
  • os-agt-008 is not bound as a memory tool.

Versioning


Core specs (what this guide implements)

Orchestration and runtime

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