Durable Memory
Give an agent a notebook that survives between runs — remember a fact once, recall it by meaning later, and forget it when you want it gone.
Durable memory is opt-in: it needs KX_SERVE_MEMORY=1, a served model (to turn text into the vectors that make recall work), and a build that includes the hnsw index. Without all three, the memory commands answer honestly — the gateway reports that memory is not wired, rather than pretending to remember.
An assistant that forgets everything between sessions never gets better at your work. Memory gives it a notebook: you can write in it, read it back, and tear a page out.
Turn it on
Build with the index. Two build features are involved: hnsw is what makes "find me the closest thing by meaning" possible, and the inference build is what gives the gateway an embedder to turn your text into a vector. See Install for build options and Local inference for the model side.
Serve with the flag set.
KX_SERVE_MEMORY=1 kx serve --dev-allow-local--dev-allow-local is loopback-only development access. Starting the gateway with no auth posture at all is refused by design — it will not silently run open. For a token-authenticated serve, use --auth-token <token>=<party> or --auth-token-file <path>; see Serving.
Check the gate. If memory is off, or the build has no index, or no model is being served, every kx memory command tells you so instead of failing quietly.
Whose memory is it
Every memory is scoped to the calling principal, derived by the server from who you authenticated as. A client cannot ask for another principal's memories, and it cannot claim to be someone else — the caller proposes, the runtime decides.
Write, read, forget
Four commands cover the whole notebook.
kx memory add "the project deadline is March 3rd"
kx memory add "the client prefers email over calls" --kind episodic
kx memory recall --text "when is my deadline?"
kx memory list
kx memory forget <memory_id>addstores one fact.--kind semantic(the default) is a standing truth;--kind episodicis something that happened. Storing the same text twice is a no-op — the secondaddreports that the fact was already remembered — so a retry never duplicates a memory.recall --textreturns the closest memories by meaning, not by keyword — "when is my deadline?" finds the March 3rd fact without sharing a single word with it. Use--k Nto ask for more or fewer (the default is 5; the server applies its own ceiling).listis the log, newest first.--limit Npages it;--instance <hex>narrows it to one run, taking that run's id as 32 hex characters.forget <memory_id>erases one entry by its content id. This is the tear-a-page-out command.
Get the full id from --json
The human-readable output of add, list and recall shortens each memory id to its first 16 characters so the lines stay readable. forget and restore require the whole 64-character id and refuse anything shorter. Add --json to any of those commands to get the full memory_id, then pass that.
kx memory add "the project deadline is March 3rd" --json
kx memory list --limit 20 --jsonkx memory add "the project deadline is March 3rd"
kx memory recall --text "when is my deadline?" --k 5
kx memory list --limit 20from kortecx import KxClient
kx = KxClient("http://127.0.0.1:50151")
result = kx.memory.store("the project deadline is March 3rd")
print(result.memory_id) # the full hex id, for forget/restore
for hit in kx.memory.recall("when is my deadline?", k=5):
print(hit.text)
for m in kx.memory.list(limit=20):
print(m.kind, m.text)import { KxClient } from "@kortecx/sdk/node";
const kx = new KxClient("http://127.0.0.1:50151");
const stored = await kx.memory.store("the project deadline is March 3rd");
console.log(stored.memoryId); // the full hex id, for forget/restore
const hits = await kx.memory.recall("when is my deadline?", { k: 5 });
console.log(hits[0].text);
const log = await kx.memory.list({ limit: 20 });Ageing memories out
A notebook nobody prunes becomes noise. Decay removes entries that are both old and rarely recalled — a fact you keep coming back to is protected by how often you use it, however old it is.
Decay previews by default. It shows you the candidates and evicts nothing until you say so.
kx memory decay --dry-run
kx memory decay --apply --ttl-days 30 --min-access 2A memory is a candidate when its age is past --ttl-days (default 90) and it has been recalled fewer than --min-access times (default 1). So the default sweep only touches facts older than 90 days that were never recalled once.
Eviction is reversible. The row is never deleted, only marked, so you can look at what went and bring it back.
kx memory stats
kx memory list --include-tombstoned
kx memory restore <memory_id>--apply is the one that changes things
decay and consolidate both default to a preview. Nothing is evicted or written until you pass --apply. Run the dry run first and read it.
Distilling what it learned
Over a long stretch of work an agent accumulates many small episodic notes. Consolidation bundles the recent ones so a model can boil them down into one durable standing fact, which it then writes back with a normal remember.
kx memory consolidate --dry-run
kx memory consolidate --apply --query "Q3 launch"--dry-run(the default) is model-free: it lists the episodic memories that would be bundled, so you can inspect the input before spending a model turn on it.--applyruns the chain — bundle, distill, remember — and so needs a served model.--queryfocuses the bundle on a topic,--k Nsets how many entries to bundle (default 16; the client caps it at 64),--window-hours Hlimits it to recent hours, and--timeout-secs Nbounds the wait (default 120).
preview = kx.memory.consolidate(dry_run=True)
result = kx.memory.consolidate(query="Q3 launch", dry_run=False)const preview = await kx.memory.consolidate({ dryRun: true });
const result = await kx.memory.consolidate({ query: "Q3 launch", dryRun: false });How agents use it
Inside an agent run, memory shows up as tools the agent can call in its loop: one to remember, one to recall, one to consolidate. They reach the store in-process — no network egress and no filesystem access — and recall is read-only, so it does not need a human to approve it.
They also fail soft. If no model is being served or the store is empty, the agent gets an honest empty answer it can reason about, rather than an error that ends the run.
Recall is recorded. The runtime commits the exact ordered references of the memories that were read, so you can go back and see precisely what the agent had in front of it at each step. Similarity scores are shown for your benefit only — they are dropped before anything is committed and never decide anything downstream.
Memory or a dataset?
Both find things by meaning, and both need an hnsw build.
- Memory is the agent's own notebook: small facts it writes for itself, scoped to you, ageing out over time.
- A dataset is a body of documents you ingest for the agent to read from — a manual, a policy archive, a folder of notes.
Use memory for what the agent learns. Use a dataset for what you already have.
Next
Context bundles
Keep the brief, the spec and the brand guide in a named folder the runtime can read, and hand it to a run by name instead of pasting it every time.
Agentic RAG
Give an agent a library card instead of a photocopied packet — it decides when to search your dataset, searches again if the first answer was thin, and every search it ran is recorded and replayable.