kortecxdocs
Context

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>
  • add stores one fact. --kind semantic (the default) is a standing truth; --kind episodic is something that happened. Storing the same text twice is a no-op — the second add reports that the fact was already remembered — so a retry never duplicates a memory.
  • recall --text returns 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 N to ask for more or fewer (the default is 5; the server applies its own ceiling).
  • list is the log, newest first. --limit N pages 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 --json
kx memory add "the project deadline is March 3rd"
kx memory recall --text "when is my deadline?" --k 5
kx memory list --limit 20
from 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 2

A 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.
  • --apply runs the chain — bundle, distill, remember — and so needs a served model. --query focuses the bundle on a topic, --k N sets how many entries to bundle (default 16; the client caps it at 64), --window-hours H limits it to recent hours, and --timeout-secs N bounds 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