Core concepts
Memory indexes
A memory index is a key like any other. One key holds one index, one index holds many records, and the mode fixed at creation decides which retrieval structures exist inside it.
A key like any other
Memory is a native type, not a service bolted on the side. That means the generic keyspace commands work on it the day you create one: TYPE answers memory, and DEL, EXPIRE, TTL, RENAME, COPY, KEYS, SCAN, and DBSIZE all behave exactly as they do for a hash.
The namespace you would otherwise configure is just the key. user:123, session:abc, and agent:support:tickets are three indexes, isolated from one another, discoverable with SCAN.
Modes
One implementation exposes three logical structures. The mode is set at MEM.CREATE and cannot change afterwards, because it decides what gets built as records arrive.
| Mode | Text indexed | Vector stored | MEM.SEARCH | MEM.VSEARCH | MEM.QUERY |
|---|---|---|---|---|---|
| SEARCH | Yes | No | Yes | Error | Keyword only |
| VECTOR | Stored, not indexed | Yes | Error | Yes | Vector only |
| HYBRID | Yes | Yes | Yes | Yes | Fused |
HYBRID is the default and the one to reach for. Pick SEARCH when you have no embedding pipeline, and VECTOR when the text is not worth an inverted index, for instance when records are already summarised into vectors elsewhere.
A VECTOR index rejects MEM.SEARCH instead of falling back to something weaker. If a mode cannot serve a query, you hear about it at the call site rather than in your evaluation numbers a week later.
Records
Every record carries the same fields:
| Field | Type | Notes |
|---|---|---|
| id | bytes | Supplied with ID, or assigned by the server as a counter |
| text | bytes | Binary safe: spaces, newlines, and NUL bytes are all fine |
| vector | float32[dim] | Optional; normalised at insert when the metric is cosine |
| meta | flat string pairs | Ordered, small, scanned linearly; not JSON |
| importance | float 0.0–1.0 | Defaults to 0.5 |
| created_at / updated_at | timestamp | Set by the server, readable in filters |
| expire_at | timestamp | Optional per-record TTL, independent of the key's |
Ids
Pass ID to control the identity of a record, which makes writes idempotent and lets you update in place. Omit it and Klyro assigns a monotonically increasing id and returns it.
Metadata
Metadata is flat string pairs, deliberately not JSON. Filtering wants comparable scalars, and values that parse as a number compare numerically while everything else compares as bytes. Set and remove fields after the fact with MEM.SETMETA and MEM.DELMETA.
Importance
Importance is a number between 0 and 1 that you assign when writing. It is the lever for the difference between “the user stated a preference” and “the user said hello”. It contributes to the fused score with its own weight, 0.05 by default.
Per-record TTL
Records expire on their own clock, separate from the key that holds them, so a session memory can lapse without the index going with it.
Inspecting an index
MEM.SCAN pages through ids with a resumable cursor and accepts the same filters as a query, which makes it the right tool for audits, exports, and bulk deletes.
