Memory docs: facts with two clocks
Memory, a Context Mode Gateway feature, keeps facts from your turns and adds the ones that match to the first request of a conversation, in Claude Code and Codex, on any machine. When a fact changes, the old row is closed, not deleted: in a store eval of 24 facts, 0 of 24 memory blocks held an old value, against 23 of 24 for an append-only store.
Key facts
- What it is
- Bi-temporal memory: each fact has two clocks, when it was true and when the gateway learned it.
- Who it is for
- Developers who want Claude Code and Codex to keep facts across sessions and machines.
- Clients
- Claude Code and Codex on one account share one store. Part of Pro.
- Measured result
- Store eval, 2026-09-30, answers from Claude Haiku 4.5 and Claude Opus 5.5: 0 of 24 memory blocks held an old value, against 23 of 24 for an append-only store, and 38 of 38 "what was true then" questions were answered. Evaluation.
- Limits
- Small evals. Recall in real use and in Codex is not measured. On Claude Haiku 4.5 the memory block added 534 to 548 uncached input tokens to a new session's first request.
Summary
Memory is the part of the Context Mode gateway that remembers across sessions. It takes short facts from your turns and your tools, stores each one with two clocks, and adds the ones that match to the first request of a conversation. When a fact changes, the old row is closed at the moment the new one starts. It stays as history and is never deleted. Claude Code and Codex on one account share one store.
What closing old rows changes, measured on one store of 24 facts that changed 38 times, with the gateway's own code (method):
Store eval, 2026-09-30: the gateway's write, read and block code at commit a12ecce8, on SQLite, with no meaning vector. Answers from Claude Haiku 4.5 and Claude Opus 5.5 through plain Claude Code. A controlled test of the mechanism, not a benchmark of recall in real use. Every row, block, answer and grade is in the evidence file. A live eval in Claude Code through the gateway is further down.
The problem
A coding agent starts each session with what its client loads, and nothing more. Four things go wrong.
- It forgets. What you said in one session is not in the next one, unless someone wrote it into a file the client loads.
- Facts change. You move from one host to another, or a new person goes on call. A store that overwrites loses what was true before. A store that only appends keeps two "current" answers, and the model cannot tell which one is true now.
- Two clients, two memories. Claude Code and Codex keep separate local memories. A fact you give one is unknown to the other.
- Notes look like your words. A note a model wrote about you reads the same as a rule you stated, unless the store records who wrote it.
How it works
Each fact is one row: subject, predicate, object. For example production deployed_to "Render, Oregon region". The subject and predicate together are the fact's slot. A row also records its kind, its trust tier, its writer, where it applies, and two clocks.
Bi-temporal facts: two clocks
Every row carries two time ranges.
| Clock | Columns | Meaning |
|---|---|---|
| Valid time (world) | valid_at, invalid_at | When the fact was true. An empty invalid_at means "true now". A write can name its own valid_at; captured facts use the time of the write. |
| Recorded time (system) | created_at, expired_at | When the gateway learned the row, and when it closed it. |
The two clocks answer different questions. Valid time answers "what was true then". Recorded time answers "what did the store know then". Keeping both is what lets a fact change without losing the old value. The model is not new: Zep's Graphiti uses the same bi-temporal design. What differs is where it runs (see Compared).
What happens when a fact arrives:
- One value or many. Each predicate holds one current value or many. The writer's own declaration decides first, then a fixed registry. Events and symmetric relations hold many. Anything else holds one.
- A new value for a one-value slot. Every current row in the slot gets
invalid_atset to the new row'svalid_at, andexpired_atset to the time of the write. Then the new row is inserted. The two windows meet with no gap and no overlap. - An older value that arrives late. If the incoming
valid_atis older than the current value, the row is stored already closed, as history. It does not replace the newer value. - The same fact in other words. A restatement with close wording (character-pair overlap of 0.7 or more, and no new content) updates the existing row instead of opening a new one. A changed value, such as "3 attempts" to "5 attempts", is not a restatement.
- Many-value slots keep each value, and drop near-copies the same way.
- Nothing is deleted. Closing a row only sets its timestamps. A slot closed by mistake can be reopened.
Worked example: a changed fact
From the eval's run of record. In session 1 the user said: "Our production deploy target is Hetzner Cloud, Falkenstein region. Our on-call engineer is Selin Aydin. Our CI runs on CircleCI." In session 2, 30 seconds later: "We moved production: our deploy target is now Render, Oregon region. Our on-call engineer is now Baran Kaya. Our CI now runs on Buildkite." Session 2 does not name the old values.
The store after session 2:
| Slot | Value | Tier, writer | valid_at | invalid_at |
|---|---|---|---|---|
| production · deployed_to | Hetzner Cloud, Falkenstein region | inferred, turn extraction | 21:14:38.716 | 21:15:09.150 |
| production · deployed_to | Render, Oregon region | asserted, remember tool | 21:15:09.150 | (current) |
| on-call engineer · is | Selin Aydin | inferred, turn extraction | 21:14:38.716 | 21:15:09.150 |
| on-call engineer · is | Baran Kaya | asserted, remember tool | 21:15:09.150 | (current) |
| ci · runs_on | CircleCI | inferred, turn extraction | 21:14:38.716 | 21:15:09.150 |
| ci · runs_on | Buildkite | asserted, remember tool | 21:15:09.150 | (current) |
| production · on_call_engineer | Baran Kaya | inferred, turn extraction | 21:15:14.830 | (current) |
All three old rows were closed and kept. The last row shows slot names drifting in this run too: turn extraction wrote Baran Kaya again under a second slot, production · on_call_engineer, so that value is current in two slots. They agree, so no answer went wrong. The new rows came from the model's own context-mode-remember call in session 2, so they carry the asserted tier. The old rows came from turn extraction, so they carry inferred. Both sessions' own words are also stored as dated event rows, which never replace each other. The export in the evidence file holds fact rows only, not these event rows. Asked "what was our deploy target before we moved it?", the model answered from the session 1 event row: "Before the move, the production deploy target was Hetzner Cloud in the Falkenstein region."
Supersession needs the same slot. In a second run (run B), the extractor wrote session 1 and session 2 under different names, such as ci_provider and then ci_platform. Only 2 of 6 old rows were closed, and 4 old values stayed current. The model still gave no old value as current, because each row it gets carries its valid_at, and the memory block tells it that the newest one wins. The extractor is shown your 40 most used predicates so names stay the same, but that does not always hold. See Limits.
Reading the history
- Timeline.
ctx.timelinelists facts in time order on either clock:clock=worldorders byvalid_at,clock=systembycreated_at. It takessinceanduntil. - Recent changes. A recall lists at most 3 rows closed in the last 48 hours next to the current ones, so the model sees what changed.
- Everything. The Memory screen and the export return every row, closed rows included.
- What the model sees. The memory block the gateway adds drops closed rows, except for session-state predicates such as
decidedorworking_on. Those show as✗ INVALIDlines, marked as history.
There is no single "as of" query yet that returns the store as it stood at a past time. The timeline with until is the nearest tool.
Trust tiers
Every row names who could witness it. The gateway sets the tier from the writer, not from the text.
asserted- Ceilings. Each writer has a highest tier it may claim. When the source and the channel differ, the lower of the two wins.
- Silence is not an assertion. A remember call that declares no tier is stored with none. A row with no tier is never sent to the model.
- Checked against your words. A remember call that claims
asserteddrops toinferredwhen the value is not in your own message. - A hint, not a permission. The memory block tells the model to treat
inferredlines as a guess to verify, never as something you said. A tier grants and withholds nothing.
Scope: where a fact applies
Each fact belongs to one of six classes. The class sets its default place on a ladder of four: everywhere, this repo, this project, this worktree.
| Class | Holds | Default place |
|---|---|---|
| About you | Your name, language, how you like to work | Everywhere, any agent |
| Toolkit | The tools and skills you use | Everywhere, per agent |
| Codebase | Decisions about a project's code | This project |
| Task | What you are doing now | This worktree |
| Session | How your agent app behaves | This worktree, per agent |
| Environment | Machine paths and anything secret | This worktree |
- A fact with no class goes to Task, the narrowest place.
- Eleven categories, such as env, git, cwd, file and sandbox, can never go wider than one project.
- A read returns rows for this workspace and rows with no place.
- A change closes only what it can see. A new value closes old rows in the same workspace or with no place. A fact about you closes the old value everywhere, so you never have two current names.
- Your account is the only wall between tenants. The place decides relevance inside your account.
Capture
Facts get in three ways. You write no notes and no code.
- From your turns. After a turn ends normally, the gateway sends at most 2,500 characters of your text and 2,500 of the answer (4,000 in total) to Llama 3.3 70B on Cloudflare Workers AI. It keeps at most 5 facts of 10 words or less, each labelled fact or event.
- From your tools. Rules with no model read commits, pushes, PRs, edited files, subagent tasks and tool errors.
- On request. The agent calls
context-mode-remember, or a Thinking in Code script callsremember.
Nothing is stored from:
- a turn with no new text from you, a turn cut short, or a refused turn. A remember call made during a turn waits for the turn's end, and is dropped if the model refuses;
- recall and "current work" questions, such as "what is my name";
- a fact whose text matches a credential pattern;
- a dry run, or a request the gateway cannot tie to an account.
With Memory off, turn extraction is queued, not run.
Injection on the first turn
The gateway adds memory in two blocks. Only the model sees them; your chat does not.
[[CM-CORE]] block at the head of the first message: at most 8 current facts, all tier asserted, oldest first. It sits in the cached part of the prompt, so after the first request it is read from cache.[[CM-MEM]] block on your message: at most 5 items. Over 1,200 characters, a reranker keeps the best items and shortens long ones.The [[CM-MEM]] block gives the model these rules:
- Your current message overrides every fact.
- When facts conflict, the newest
valid_atwins. ✗ INVALIDlines are no longer true and are given only as history.- The lines are the whole result of this read. The model must not cite memory for anything not on a line.
Each read also states its result: rows, empty, or unavailable. So "I have no record of that" is a measured answer, not a guess. Later turns of a conversation get no fact block. They keep reading the prompt from cache, and the agent asks for more with a tool.
Recall tools
ctx.recall, ctx.remember, ctx.timeline, ctx.prompts, ctx.predicates, ctx.exportA recall can pass a scope, and scope_strict to read only that workspace.
Try it in 5 minutes
Two moments from a normal week. The next morning, you ask where you left off. Later, you ask Codex about a choice you made in Claude Code. You never repeat yourself.
Setup
npx @context-mode/cli # sign in, connect Claude Code and Codex, then restart them
git clone https://github.com/expressjs/express.git && cd express && npm install
git checkout -b add-nvmrc
claude
The next morning: where was I?
Day 1, in Claude Code:
Add an .nvmrc file that pins Node 20 and commit it.
Our commits start with the Jira key of the ticket I'm on, like `PAY-1432: short summary`. I'm on PAY-1432 this week. Amend that commit.
Day 2. Type /exit, then start a new session with claude (not --resume):
Morning. Where did I leave off yesterday, and which ticket am I on?
What you will see:
⏺ context-mode · memory +1 record about user
You're on PAY-1432 — add `.nvmrc` to pin Node 20. You already made the commit yesterday:
7e2964f7 PAY-1432: add .nvmrc to pin Node 20
The branch is `add-nvmrc` and your git status is clean, so the work is done.
- Where it came from. The ticket and the task come from memory. The commit line also has the key, so to check memory alone, ask from
mainwith the agent's tools off. We did that twice. Both times it named PAY-1432 and the.nvmrcwork. - In the console. The Memory screen lists what it kept, for example "user current_ticket PAY-1432".
Decide in Claude Code, ask in Codex
In Claude Code, in the same folder:
Separate thing, for the router-cache branch: we'll use a plain Map with a size cap, not lru-cache, because we don't want a new dependency in express core. No code yet, just agree.
Later, open Codex in the same folder and ask:
Why did we pick a Map over lru-cache for the router cache?
What you will see:
We chose a plain `Map` with a size cap to avoid adding a new dependency to Express core. That was the agreed plan for the router-cache branch.
⏺ context-mode · memory +1 record about user, router-cache branch
- No tool call. Codex read no file and ran no command. The reason came from memory.
- A new Claude Code session in the same folder gives the same reason, with the line
memory +2 records about user, router-cache branch, express core. - Another worktree. By default a decision stays in the worktree where you made it. In a second worktree of the same repo, the agent said it had no record. To share decisions across a project, set "Decisions about a codebase" to This project on the Memory screen.
Rules you state once
- Commit format. Say once "our commits start with the Jira key of the ticket I'm on, like
PAY-1432: short summary". In the next session the commit isPAY-1432: ignore .env.local, colon included: 3 of 3 runs on Claude Opus 5.5, and on Claude Haiku 4.5. The rule is sent first in each new session under "Rules you set", with your own words as the format. - Answer style. Say once "keep your answers to five lines or less, no headings, no tables". In the next session the answers were 5, 4 and 5 lines, with no heading or table: 3 of 3 runs on Claude Opus 5.5. On Claude Haiku 4.5: 3 lines.
We ran this on 2026-10-04 in real Claude Code sessions (claude -p 2.1.283, Claude Haiku 4.5) and one Codex run (gpt-6-luna) through the gateway, each on a new test account. The morning question was answered from memory in 3 runs of these prompts, and the Codex question in its run. The rules ran again after the "Rules you set" change: 3 times on Claude Opus 5.5 and once on Claude Haiku 4.5.
Why it matters. You pick up where you left off, in either client, without retyping yesterday's context.
Evaluation: what closing old rows changes
The question: does closing old rows change what the model is told, and what it answers? We ran the same store three ways and changed only what happens to an old value.
Method
- Code. The gateway's own write route (
/fact-assert), read route (/fact-query), timeline route and block builder, from commit a12ecce8, run against a real SQLite engine. The read is the one the first turn makes: the turn path's query, a limit of 6 rows, and its own filters. No meaning vector, so only the keyword legs run, as on a turn where the vector misses its deadline. - Store. 24 facts about one team's setup: deploy target, on-call engineer, CI, database engine and 20 more. Each changed 1 to 3 times, about 30 days apart, from 2026-06-01. 62 writes, 38 of them changes. Each write names its own
valid_at, so the two clocks differ on every row. - Arms. Bi-temporal: the shipped code. Append-only: the shipped code with every fact declared many-valued, so nothing closes. Overwrite: the shipped code, then every closed row deleted, so only the current value is kept.
- Questions. 24 "what is it now" questions, one per fact. 6 questions that each ask about 3 facts. 38 "what was true on this date" questions, answered from the timeline by a fixed rule.
- Answers. Each memory block and its question went to Claude Haiku 4.5 and Claude Opus 5.5 through plain Claude Code 2.1.283, with no tools and no gateway. An exact string check graded each fact: current value, old value, or neither.
Results
| Measure | Bi-temporal | Append-only | Overwrite |
|---|---|---|---|
| Rows in the store, open | 62, 24 open | 62, 62 open | 24, 24 open |
| "Now" questions with an old value in the block | 0 of 24 | 23 of 24 | 0 of 24 |
| Three-fact questions: current value in the block | 18 of 18 | 13 of 18 | 18 of 18 |
| Three-fact questions: only an old value in the block | 0 of 18 | 2 of 18 | 0 of 18 |
| Haiku 4.5 answers: current, old, neither | 41, 0, 1 | 36, 2, 4 | same block as bi-temporal |
| Opus 5.5 answers: current, old, neither | 40, 0, 2 | 36, 2, 4 | same block as bi-temporal |
| "What was true on this date" | 38 of 38 | 38 of 38 | 0 of 38 |
42 graded facts per model and arm: 24 single questions and 18 facts from the three-fact questions. Source: evidence file, with every row, block, answer and grade, and both clocks on every row.
What it shows:
- Closing keeps the block clean. With old rows open, 23 of 24 blocks carried an old value next to the new one. The model then has to pick by date.
- Old rows take the places current facts need. The first-turn read keeps 6 rows. In the append-only store, old rows of one fact pushed other facts out. On "who is on call, who owns payments, which package manager", the block held only the oldest on-call name, and both models gave it as current.
- Closing keeps the history an overwrite loses. Asked what was true on a past date, the bi-temporal store answered 38 of 38 from closed rows. The overwrite store had nothing to answer from.
- Append-only keeps history too. With stable slot names, it answered every date question. Its cost is in the block, not the history.
What it does not show: recall in real sessions, recall with the meaning vector on, or Codex. One "now" question, about CI, matched no row in any arm, because the keyword read does not match the short word "CI". One Opus 5.5 answer, "Fly.io in Frankfurt", gave the current value in other words and counts as "neither" under the exact check.
Live eval in Claude Code
An earlier, smaller check ran real claude -p sessions through the deployed gateway. It tests the whole path, and it is too small to show an effect of closing rows.
Method
- Scenario. Session 1 states three facts. Session 2, 30 seconds later, states that all three changed and does not name the old values. Then 8 probes, each a new session: three "now" questions, three "before" questions, "is it still Hetzner?", and "list all three current values".
- Client. Claude Code 2.1.283 (
claude -p), Claude Opus 5.5, effort low, a fresh config folder for every run, so no CLAUDE.md or auto memory carried over. Gateway build 9d047fb4. New, disposable accounts, one per arm. - Arms. Memory on. Memory off. Memory off and not entitled. Claude Code with no gateway, which by design keeps nothing between sessions: it is a no-memory baseline, not a test of Claude Code's own memory. In no arm did the user ask the agent to remember.
- Grading. An exact string check on the new value (now probes) or the old value (before probes). Whether an answer gave an old value as current was read by hand.
Results
| Arm | Correct | Old value given as current | Search calls | Median time | Client-reported cost, 8 probes |
|---|---|---|---|---|---|
| Memory on | 8 of 8 | 0 | 0 | 4.9 s | $0.184 |
| Memory off | 8 of 8 | 0 | 9 | 6.1 s | $0.069 |
| Memory off, not entitled | 8 of 8 | 0 | 11 | 6.3 s | $0.100 |
| Claude Code, no gateway | 0 of 8 | 0 | n/a | n/a | $0.170 |
| Run B, memory on | 8 of 8 | 0 | 0 | 6.0 s | n/a |
| Run B, memory off | 7 of 8 | 0 | 10 | 7.4 s | n/a |
Run C is the run of record; run B repeated the design on new accounts. "Search calls" are context-mode-search calls the model made through the gateway. Time is the wall time of each claude -p run. Source: evidence file.
- Run C closed every old value. Run C closed 3 of 3 old values.
- No arm gave a stale answer. Memory off also gave none, so this run does not show an effect of memory on stale answers.
- Memory on answered faster, and cost more. With memory on, every probe was answered from the memory block with no search call, at a median 4.9 s against 6.1 s. The same 8 probes cost $0.184 against $0.069 with memory off. We have not found the cause of the higher cost.
- The memory switch controls injection, not recall. With memory off, the model still found the answers by searching your stored turns, which is part of Search.
- One clock in the export. In this run every row was written at the moment it became true, so
valid_atequalscreated_at, and the export has noexpired_at. The store eval above has both clocks on every row.
Compared with other approaches
From each vendor's own pages, read on 2026-09-30. "Not stated" means the page we read does not say.
| Approach | When a fact changes | How facts get in | Where, and which clients | What it does not do |
|---|---|---|---|---|
| Context Mode | Bi-temporal. Old rows closed at the new value's time, kept as history. | Picked from turns and tools; the remember tool | The gateway, per account. Claude Code and Codex share one store. | No single "as of" query yet. Needs a stable slot name to close the old value. |
| Claude Code CLAUDE.md and auto memory | You or Claude edit the file. No fact history is stated. | CLAUDE.md you write; notes Claude writes as it works | Local files. Auto memory is per repository, shared across worktrees. Claude Code. | Loads the first 200 lines or 25KB of auto memory. Anthropic calls both "context, not enforced configuration". |
| Codex memories | Not stated | Generated from past chats: summaries, durable entries, recent inputs | Local files under ~/.codex/memories/. Codex. Off by default. | Memories may not update right away when a chat ends. |
| Anthropic memory tool | Your code decides | Claude asks for file operations; your application runs them | Storage you run. Apps on the Claude API. | A building block, not a feature of Claude Code or Codex. |
| Graphiti and Zep | Bi-temporal. Facts have validity windows; old facts are invalidated, not deleted. Queries for what was true at any point in time. | Episodes you send by API | Zep's cloud, or Graphiti on your own Neo4j, FalkorDB or Neptune, with an LLM. Any client you wire up. | Its MCP server lets an agent add episodes; automatic capture of every Claude Code or Codex turn is not built in. |
| mem0 | Memories are added, searched and updated. A validity window is not stated. | Messages you send by API, or its Claude Code plugin | Hosted, or self-hosted. Plugins for Claude Code, Codex, Cursor and others. | The Claude Code plugin needs Python 3.10+ and Git on each machine. |
| supermemory | Facts "update, connect, and forget"; a change should not "leave both preferences equally true". A validity window is not stated. | Its plugin saves conversations when a session ends, and on request | supermemory's service. Claude Code, Codex and OpenCode share one store per repository. Team memory apart from personal. | The hooks need Node.js 18+ on each machine. |
| Letta Code | The agent commits each update to a git-backed memory file system (MemFS) | The agent decides what to keep | Its own agent: CLI, desktop app, cloud agents | A separate agent, not a layer under Claude Code or Codex. |
| claude-mem | Not stated; a timeline tool lists context in time order | Hooks save observations | SQLite with FTS5 and Chroma. Claude Code, with installers for OpenCode and others. | Codex is not listed in its README. |
Where Context Mode differs. The bi-temporal model itself is not ours: Graphiti and Zep use it too. The difference is where it runs. With Graphiti and Zep, you send the data, by API or by an agent calling their MCP server, and you run or rent a graph database and a model pipeline. Context Mode fills a bi-temporal store from traffic Claude Code and Codex already send through the gateway, with trust tiers on every row, and nothing to install on each machine.
Where others are stronger.
- Graphiti can query what was true at any point in time, and it searches by meaning, keyword and graph together.
- CLAUDE.md is free, local, reviewable in git, and needs no network.
- For rules that must always hold, memory is the wrong tool. Anthropic's page calls memory "context, not enforced configuration" and points to a hook for blocking. That holds for our memory too; for enforcement, see Cage.
- mem0 and supermemory cover more clients, such as Cursor and OpenCode.
Safety and privacy
- Where it lives. In your account's own store on the gateway: one Durable Object per account. Your account is the wall between tenants.
- Who reads your turns. Turn extraction sends at most 4,000 characters of each answered turn to Llama 3.3 70B on Cloudflare Workers AI.
- Secrets. A fact whose subject, predicate or value matches a credential pattern (such as
sk-,ghp_,npm_orapi_key=) is refused at the one point every write passes. Model output is redacted before it is stored. - Your message wins. The memory block tells the model that your current message overrides every fact. A model-picked fact never enters the standing rules.
- Refusals. A fact that rides refused turns goes under watch. It is held back only when a model wrote it, refusals in at least 2 separate turns carried it, and no turn that carried it got a normal answer, all in the last 30 days. You can restore it.
- Unknown callers. A request the gateway cannot tie to an account gets no memory, no skills and no tools, and nothing from it is stored.
- Nothing is deleted by an agent. Supersede, retract and hold-back close a row and keep it as history. Physical delete exists only as an operator cleanup; an agent's call is refused.
Your controls
Limits
- Small evals. The store eval tests the mechanism on one scenario of 24 facts, without the meaning vector. The live eval has one scenario of three facts, on Claude Code only. Recall in real use, on a later day and in Codex is not measured.
- Slot names drift. A new value closes the old one only in the same slot. In live run B, 4 of 6 old values stayed current under different names, and in run C one value was written under a second slot. The answers were still right there, but a store with drifted names acts like the append-only arm of the store eval.
- No "as of" query. You can list history by time, but not ask for the whole store as it stood at a past moment.
- History is not in the block. Closed rows reach the model only for session-state predicates, and a recall lists closed rows from the last 48 hours only.
- Cost. In the live eval, the memory-on probes cost more than the memory-off probes, as the client reported: $0.184 against $0.069 for 8 probes. We have not found the cause. In earlier paired runs on Claude Haiku 4.5, memory added 534 to 548 uncached input tokens to a new session's first request.
- No-tool requests get no memory. The gateway treats a Claude Code request that declares no tools as a side call and adds no memory to it. On such requests we also saw the stream stop after the thinking block in some tries. That is not fixed.
- Extraction. The extractor has no category field, so most picked facts land in Task (this worktree) unless they are about who you are.
- Languages. Recall and "current work" questions are matched in English and Turkish only.
- Secrets. A secret with no label or known prefix can pass the credential check.
- Other gaps. Rules that read tool output still write when Memory is off. Place buttons for a single fact do not yet change what a turn reads. A client that replaces its own system prompt sends no folder, so its read covers the whole account. The block shows every time at UTC+3, whatever the account's time zone; a per-account time zone is not built yet.
FAQ for CTOs
Isn't bi-temporal memory already done?
Yes, and we say so: Graphiti and Zep use the same model. What we add is where it runs. The store fills itself from Claude Code and Codex traffic at the gateway, with no pipeline to build and no graph database to run.
Can the model give an old value as the current one?
It can, like any model. The design works against it: closed rows are not in the block, every row carries its valid_at, and the block says the newest wins. In the store eval, 0 of 84 answers gave an old value when old rows were closed, against 4 of 84 when they stayed open.
Can a remembered fact override what I say now?
The block's first rule says your current message overrides every fact. A fact a model picked out of a turn is tier inferred and never enters the standing rules.
If memory off still answered 8 of 8, what does memory add?
In that live run: answers on the first pass, with no search call, at a median 4.9 s against 6.1 s. With memory off, the model searched your stored turns, which Search keeps on. So on a fact this recent, memory saved a round, not an answer.
How do I make it forget?
Retract a fact, or switch Memory off. A retracted fact is closed and kept as history, so you can see what changed. It is no longer sent to the model.
Who sees my data?
Your account's own store on the gateway, and Llama 3.3 70B on Cloudflare Workers AI for at most 4,000 characters of each answered turn. Credential-shaped facts are refused.
Does it work in Codex?
Yes. npx @context-mode/cli ties your Claude and Codex accounts to one Context Mode account, so both read and write one store. This eval used Claude Code only.
Can I check the eval?
Yes. The store eval file has every row with both clocks, every memory block, answer and grade. The live eval file has the scenario, every probe, answer and grade, and the fact rows after session 2 for runs B and C.
Compared with other tools: see the landscape.