Intent
Make one backend operation responsible for the invariants of durable memory, regardless of whether a write originates from chat, an agent tool, an API, a review screen, or background extraction.
The problem
Memory systems often grow several write paths. One creates evidence, another checks duplicates, a third allows an assistant to overwrite an active fact, and a background worker bypasses all three. Policy then depends on which interface happened to receive the write.
The pattern
Expose narrow adapters but converge on one governed command:
Diagram source
%% caption: every writer — command, tool, review UI, background extractor — reaches the store through one gateway that applies actor, scope and trust policy before an atomic write
flowchart TD
A["User command"] --> G["Governed write gateway"]
B["Agent tool"] --> G
C["Review UI"] --> G
D["Background<br/>extractor"] --> G
G --> P["Actor + scope +<br/>trust policy"]
P --> K["Dedupe, conflict,<br/>tombstone checks"]
K --> T["Atomic claim, evidence,<br/>relation, audit write"]The gateway accepts an explicit actor, scope, candidate value, evidence, source, and intent. Inside one transaction it:
- Normalizes identity and value.
- Checks authorization and sensitivity.
- Searches same-key and near-duplicate memory.
- Checks rejected-value tombstones.
- Chooses create, corroborate, conflict, supersede, or refuse.
- Assigns trust state according to actor and evidence.
- Writes provenance and audit events.
Correction should use the same invariants and atomically supersede the old claim while creating or activating the replacement.
Why it works
One gateway makes policy auditable and testable. New integrations inherit existing safeguards instead of reimplementing them. Atomicity prevents half-corrections such as superseding an old claim without successfully creating its replacement.
Tradeoffs
The gateway can become a monolith. Keep storage, normalization, and policy components separate behind the command boundary. High-contention keys may need locks or serializable transactions. Not every note deserves belief governance; distinguish low-risk archival capture from claims that influence behavior.
Cost to adopt
Build: one transactional path all mutations pass through, plus enforcement that they cannot bypass it — a private store, a lint rule, or a type that only the gateway can construct.
Forces elsewhere: the gateway becomes a bottleneck for feature work, and every new write path is a negotiation with it. Its failure mode is a design decision, not an accident: fail-closed loses writes during an outage, fail-open admits ungoverned ones.
Ongoing: policy lives here and needs review as policy, not as code.
Skip it if there is exactly one writer. A gateway in front of a single caller is ceremony.
Seen in the atlas
RainBox is the reference:
record_belief is the single path, taken under a Postgres
advisory lock, running dedupe, tombstone checks, lattice-aware conflict
detection, and actor-based trust in one transaction, with
correct_belief as its atomic correction counterpart.
Several systems reach the same shape with a narrower policy inside the gate.
Aura's
ConcreteMemoryWriteGateway fails closed on both branches
that usually differ: with no governance authority wired it denies the
write with a warning naming the family, and a governance call that
raises is denied too, after recording a degradation. Its
receipt is a precondition by another route — the write lands, and if the
receipt cannot be emitted the gateway restores the previous payload and
raises. What it does not have is exclusivity. The legacy
MemoryFacade.add_memory is a second write path whose
constitutional gate can degrade open, for five named service not
wired reasons and only on non-user-facing writes, so one codebase
carries the stricter and the softer policy for the same operation. That
is the problem this page opens with, in a codebase that has the gateway
to solve it.
Osiris states exclusivity as the
contract of its actions layer — "the only mutation path into the
ontology", with the domain write, its audit_log row
and any outbox rows committed in one transaction, and "No
bypassing." Its own daily housekeeping deletes audit rows and
redundant assertion rows through raw SQL outside that layer, and a
source's own assertion writes no audit row, so the stated contract holds
for the writes agents make and not for the maintenance the system runs
on itself. runar-forge routes
MCP saves, prompt capture, extraction and its crawler through one
propose, which strips private blocks and redacts secret
patterns before it bounds content, then stamps the author and dedupes on
a topic key. Neuron refuses a
missing required field or an invalid enum value at one
enforceFieldSchema before the row lands. kannaka-memory sends every
wire-to-store absorb through absorb_gate, which forces the
hallucination flag to the local default because a remote peer must not
be able to set or clear its own verdict.
OpenSRE is a small complete
instance, and what it puts in the gate matters more than the gate
itself. Two writers — an agent tool and an automatic post-turn extractor
— reach one save_memory, which takes a directory lock,
preserves created_at, writes through a temp file and an
atomic replace. The policy in front of it is what distinguishes it: a
closed type vocabulary, a model-free grounding check
requiring an extracted infrastructure or incident claim to share
distinctive tokens with text the user typed, a refusal of anything
extracted from a transcript containing the product's own demo scenarios,
and a secret filter whose rejection names the rule rather than echoing
the value. Only the proposal step is a language model; every gate after
it is deterministic and testable, which is what makes the committed
negative cases possible.
PLUR1BUS shows what a gate can
demand of a correction, and which caller should be allowed past it.
lib/safe-update.js refuses a content change missing either
updateSource or updateEvidence, refuses new
text with no new embedding, deduplicates on a hash of the change rather
than of the row, and appends the outcome to a reconsolidation event log
— five checks in one function, each of which a caller cannot skip. The
sixth is a semantic drift gate: the correction is
rejected outright if the new embedding sits more than 0.45 cosine from
the old, on the reasoning that a replacement meaning something else is
corruption rather than correction. The two callers in the tree take
opposite sides of it. The automated conflict apply, behind a
confirm === true check, calls safeUpdate with
the gate on and turns a drift refusal into review_only
instead of a write. The user's /correct command passes
skipDriftGate: true, with the reasoning at the call site: a
nonce-confirmed user correction is exactly the case where large drift is
intended, the confirmation dialog shows the old and new text in full,
and the gate throws rather than warns. A skipped gate does not skip the
measurement — the drift is recorded on the audit event. That split is
the part worth carrying: a gate can meaningfully refuse a correction on
semantic grounds, and the caller that should pass it is the one a person
has confirmed, not the one a resolver chose.
Memory Palace shows the
write-side version with a degradation rule most gateways lack.
write_guard runs a semantic and a lexical search before a
write and returns ADD, UPDATE or
NOOP — and when the embedding provider has degraded to a
hash fallback and fail-open is not explicitly enabled, it returns
NOOP with the reason instead of a decision. A duplicate
gate that falls through to "not a duplicate" whenever its evidence is
bad is the failure this avoids.
ClawMem gates the
destructive branch on the quality of the classifier that would
select it: resolveEffectiveContradictionPolicy downgrades a
configured supersede policy to a non-deactivating
link whenever no audited judge is configured, warns once
per process, and emits a merge_supersede_blocked audit
event per occurrence. The comment gives the reason — "an unaudited
heuristic whose number-mismatch score sits exactly at the default action
threshold must never select deactivation".
Hermes Agent routes memory mutations through a staged write-approval gate, per subsystem and off unless configured, that can allow, block, or hold a write for human approval — but the gate fails open if its module cannot be imported, which is documented in the code and worth noticing: a gateway's failure mode is part of its design.
Hestia declines the gateway for
one adapter on purpose. The diagram above routes background extraction
into the same governed command as every other caller; Hestia routes it
somewhere else. brain/note_taker.py extracts durable facts
from conversation and they "land in a review inbox
(memory/inbox/*.md), NOT straight into the live memory
store" — the queue is deduplicated against both live memory and its
own pending entries, and a person promotes or drops each one with
review_notes.py or a tap on an e-ink board. The module
states that "deployment environment variables cannot bypass this
gate", and a test sets the legacy
HESTIA_NOTETAKER_AUTOWRITE=1 and asserts the fact reaches
the inbox, not the store. The gate rests on recall not reading the inbox
directory, so HESTIA_INBOX_DIR pointed at the memory
directory undoes it. The direct write path still goes through one
governed function with a type whitelist that raises rather
than coercing, and returns the error text to the model so it can retry.
So this is not a system without a gateway; it is one that decided the
least trustworthy adapter should not have a machine-decided
verdict at all. Worth weighing against the cost: the queue defers the
judgement rather than deciding it, and nothing downstream can mark a
promoted fact wrong.
Lobu separates the gate from the writer completely. Its entity-mutation surfaces are described in their own comments as policy-blind: each builds one mutation request, calls the gate, and acts on the decision without importing any policy module. The one shipped interceptor consults a write-policy table whose action and effect rows resolve to auto, approval, deny or disabled, and an approval turns the write into a deferred mutation whose queue closure the core runs strictly post-commit — the repository's stated invariant that approvals never ride the caller's transaction. Two details make the gate hold rather than merely exist: the approval context check rejects any context carrying an agent, client or MCP session id and requires a user id, so an automation cannot approve the run it queued, and the queued proposal carries the snapshot it was built on, so a field a human edited in the meantime is skipped as stale instead of being clobbered by the approval.
yantrikdb-mcp is a
counterexample: a gate that is thorough and not exclusive.
skill(action="define") runs a write gate off by default and
frozen at import, schema validation, content scanners, a namespace
allowlist, attribution, an audit line and a stamped hash, while the
generic remember tool writes into the same
skill_substrate namespace with any metadata. The catalog
reader filters on namespace and a metadata tag and accepts a missing
hash as a legacy row, so a skill written past the gate is served. The
server's own cluster-mode warning states the fix — "the MCP server's
gate alone is not sufficient" — and it applies in embedded mode too.
MEX builds the gateway and then
routes the ordinary loop around it. mex wiki apply is the
only module that writes Wiki Markdown, re-plans against the current
tree, blocks on the subject's content hash, and journals an intent and a
completion line to a committed, body-free ledger. The GROW checklist its
setup installs tells the agent to edit context/ files
directly after every task, and the ledger's own header says manual edits
leave no entry. The gateway's guarantees therefore cover the rare path,
and the repository's own ledger holds two agent operations beside 130
migration lines.
MIND-Mem makes exclusivity a
property of the store rather than of the callers. Every
BlockStore.write_block implementation calls
require_admission and raises when no receipt from the
governance gate is open, the receipt names an ingest tier whose row in
INITIAL_STATUS fixes the status the write may carry, and
tests/test_governed_write_paths.py parses the source tree
and fails on any new raw caller, with a negative control on its own
matcher. What the gateway cannot see is the store itself: the corpus is
Markdown, and a block written by an editor enters recall with no receipt
at all.
Cortex (Obelyth) runs two
write paths with opposite governance into one store. A guest's
brain_propose lands in a Redis queue that only the passcode
console or a trusted MCP door can drain, and acceptance commits the note
and a receipt keyed on the proposal id in one Git operation, so a
retried accept resolves instead of applying twice. The trusted door's
brain_write reaches the same writeNote with
none of that: no review, no receipt, and a replace retry
that overwrites a concurrent write. The gateway exists, and only the
untrusted caller is routed through it.
Flowly has the governance and
puts it on the wrong side of the write. Its GovernanceStore
enforces a transition table and audits every move, but the live hook
runs after memory_append has appended to
MEMORY.md and after knowledge_graph add has
written its triple — the two stores the prompt injects whole. A
needs_review automation write is therefore in context on
the next turn, and reject changes the wrapper row while the
line and the open triple stay. The gate is real for the dreamer, the
importers and Obsidian ingest, whose text exists only inside the
governance store.
Global Agent Memory
converges every adapter — MCP tool, CLI, dashboard — on one
MemoryService over one VaultRepository, and
that gateway enforces idempotency by request id, compare-and-set on
updated_at, a pure lifecycle transition table and an
append-only audit line. It enforces no actor. The same
approve is reached from the agent's tool list and the
owner's dashboard, and supersede activates a candidate
replacement without passing the transition table. Its invariants are all
about consistency and none about authority.
Gates on something other than a write
These are analogies rather than instances. Each puts one gate, or one stated rule, in front of a decision that is not a durable memory write, and each is worth borrowing for the shape.
MetaClaw governs the policy rather than the claim. A candidate retrieval policy is replayed against real past turns and promoted only if it does not regress on eight measured deltas over at least ten samples, with an explicit cap on additional zero-retrieval cases. It is the same shape — one gate, several checks, an auditable decision — applied one level up.
Atomic Agent governs by stated invariant: its schema comments cite numbered cross-phase rules back into a design document (never auto-execute a procedure; at most one LLM call per consolidator cluster; keep the vote prompt off the main KV cache). Rules that are cited can be reviewed as rules; rules that are merely implemented are indistinguishable from accidents.
MateClaw gets a chokepoint from
framework conventions rather than a lock: turn lifecycle events flow
through a MemoryLifecycleMediator to every registered
provider, so the write path is observable in one place. Its decorators
(MetricsMemoryProvider,
RetryableMemoryProvider) add resilience and instrumentation
to every backend without per-plugin code.
Magic Context enforces the negative form — fail-closed: if persistent storage is unavailable, the plugin refuses to register rather than running without it.
Memora applies the idea to a bulk
mutation rather than a single write: its supersession sweep takes
dry_run: bool = True, so the pass that would hide
superseded memories reports its proposals by default and mutates only
when explicitly asked. The flag is a parameter of an MCP tool the model
calls, so the default invites a preview without requiring a person. A
gateway concentrates writes so they can be governed; a dry-run default
lets them be reviewed first, which is the one control this
pattern otherwise lacks for operations whose blast radius is unknowable
in advance.
Midas applies the same idea one
step later — a gateway on use rather than on write.
decide_memory_use crosses a four-value provenance
vocabulary with a four-value intended-use vocabulary: planning may rest
on anything, an answer may not rest on an internal plan, and an external
or destructive action requires user_confirmation and
nothing else. Two refinements are worth lifting whole. A
forbidden_action rule is stamped user-confirmed and would
therefore be valid authorizing evidence, so it is explicitly excluded
from the supporting set — "a prohibition is a gate, not an
authorization" — and a live prohibition in the same evidence set
vetoes any confirmation beside it, closing the approve-around
attack. Currency is re-checked at the gate rather than trusted from
recall, because, in the code's words, the guard "verifies currency
itself rather than trusting recall to have filtered the stale
record".
Verel gates promotion rather than writing; engram surfaces conflict candidates for judgment.
The gateway that is not yours
Every gateway above is inside the memory system it governs. AEGIS, from the token-cost corpus, puts one outside, in front of the agent, and ruflo below holds the read-side half of the same threat. AEGIS is worth naming although it is not a memory system and gets no report here.
AEGIS, MIT,
examined on 2026-08-09 at 82b7501c…,
is an MCP gateway with a pre-execution detector chain, and one of its
built-ins is memory-poison-detector.ts. Its threat model is
stated exactly as this atlas would state it: "an adversary tricks
the agent into persisting attacker- controlled instructions into
long-term storage that subsequent sessions retrieve and treat as
authoritative."
The interesting part is how it finds the write path it is supposed to
gate. Sitting outside every memory system, it cannot know which tool is
a memory write, so it pattern-matches the tool name —
^(write|save|store|put|append|persist|remember|memorize)_?(memory|state|context|note|fact|scratchpad),
^(upsert|insert)_?(vector|embedding|document|memory|fact|chunk),
^memory_(set|store|write|put|append|add), and two more —
then inspects the payload for imperatives, role overrides and known
jailbreak phrasing. It calls itself "heuristic, not perfect — a
determined adversary can paraphrase" and pairs itself with a
request-side injection classifier and cross-agent detectors.
That naming convention is the whole trick and the whole weakness. A gateway outside the system can only recognise a write it can name, so a memory system whose write tool is called something else is ungoverned by construction — and several in this atlas are. The lesson runs the other way for a builder: if you want an external policy layer to be able to protect your memory, name your write tool the boring, guessable thing.
ruflo is the read-side half of the
same idea and the one that does have a report:
agentdb-retrieval-guard.ts, off unless an environment
variable turns it on, screens chunks coming out of the store
before they are assembled into context. Write-side screening catches the
payload as it lands; read-side screening also catches whatever was
already in the store when you turned the guard on. They are not
substitutes, and a system with a governed write gateway of its own has
the better version of the first half — because it knows which writes are
writes.
MAMA has the gate and shows where
its policy stops. Every decisions mutation outside
migrations runs inside appendJudgmentOnAdapter, which
checks scopes and amendment targets against the caller's admitted set,
strips caller provenance in favour of host-attested session facts, and
writes the row, scope bindings, a memory_events row and a
payload-hashed receipt in one transaction; the dispatcher refuses
secret-shaped input on every action declared a recallable write. What
the gate does not check is the turn. Timezone and report-phrase settings
refuse outside an owner message turn, and memory.save,
which writes corrections the prompt ranks above the runtime's own rules,
does not.
Pluribus puts its strongest
gate on one of four write paths. The deterministic harmful-advice screen
that stores safety-negating advice as quarantined has one
caller, the inline formation behind record_experience.
memory_create, memory_promote and
curation_materialize reach
memory.Service.Create without it, and under the shipped
hive defaults a governing constraint written through
memory_create lands active at authority 4, above the
enforcement binding floor of 3. A screen placed inside the shared
Create would have covered every path at the same cost.
Browsentic implements the
gateway as a location rather than a status. The producing agent's only
write verb stages into .staging/<uuid>/; the loader
never reads dot-prefixed entries, so an unreviewed map is not merely
unused but never opened; and the commit is a rename that only a frame
from the paired extension's panel can trigger
(src/daemon/daemon.ts:491-499). The agent side is closed
structurally rather than by prompt: the mapping run's tool gate admits
seventeen read actions and no page code
(src/daemon/agent/mapping.ts:21-60), each agent CLI but
Antigravity is spawned with its own file and shell tools denied, and
page navigation refuses non-http(s) URLs, so the panel is out of reach.
The limit is that the gate is per path: recordings reach the prompt
through another door, with no review.
Tests to require
- Exercise every adapter against the same invariant suite.
- Race two conflicting writes and verify one coherent outcome.
- Prove model-originated writes cannot acquire human authority.
- Roll back the entire correction if replacement creation fails.
- Verify tombstone and scope checks cannot be bypassed.
- Confirm audit events and evidence commit atomically with the claim.