CortexDB · docs

CortexDB 0.10.5 — fast erasure on long-lived stores, no spurious wait=indexed timeouts, cross-customer answers that mean the same in every language, and a Settings page that keeps your keys

0.10.5 is a fix release on top of 0.10.4, the last published release. Everything in RELEASE_NOTES_0.10.4.md still applies; this page lists only what changed since. It adds no column family, directory, WAL row kind, index field, key prefix or on-disk format, and nothing is migrated: a 0.10.4 server opens and serves a data dir 0.10.5 has used (see "Rollback").

Summary

Upgrade from 0.10.4

Before you upgrade: what clients and operators will notice

Only deployments with the experimental cohort lanes on (CORTEX_COHORT=on, CORTEX_SUBTREE_RECALL=request) see the first four items:

Nothing else changes for a client: no new route, no new required field, no changed default response.

Upgrade steps

Nothing is migrated and there is nothing to run afterwards. Replace the binary or image and restart as for any release, giving the server 120 s to stop. Then check:

  1. GET /v1/admin/version: crate_version 0.10.5 and the git_sha you deployed. The capability list is the same 11 names as 0.10.4.
  2. GET /v1/admin/ready: ready: true.
  3. Search the boot log for SETTINGS_OVERRIDE_CLEARS_SECRET (see above).
  4. Before a later release enforces the label limits, check your integrations' labels (see "Label limits (announced)"); CORTEX_LABEL_LIMITS=on on a staging server shows what would be refused.

Upgrading from 0.10.3 or earlier

0.10.5 contains everything in 0.10.4. Read RELEASE_NOTES_0.10.4.md, "Upgrade from 0.10.3" (and from there the earlier notes) first; this page applies on top.

Fixes

Erasure and its proof on long-lived stores

An erasure and its proof find the idempotency (retry) records of every event they touch through the idempotency store's event-to-record reverse rows. Those reads were prefix scans without an upper bound. When a store's old retry records expire, the boot clean-up deletes them, leaving one deleted row per expired record, and a lookup whose prefix held no live row walked every one of them before it stopped. On a store with about 130,000 expired records every erase and every proof took 25-60 s. The reads now stop at the end of their prefix, so the same steps take well under a second; a test checks that the bounded reads return exactly what a full scan returns for every prefix shape.

One customer's erasure with proof, same store copy0.10.40.10.5
POST /v1/erasures (execute)24.6 s0.26 s
GET /v1/erasures/{id}/verify24.8 s0.02 s
Proofclean, 13 of 13 storesclean, 13 of 13 stores

?wait=indexed and the lost stage wake-up

The stage wait read the event's stage first and subscribed to stage notifications only then, and its 500 ms poll re-checked only for failures. When the stages finished between that read and the subscription (on an idle server they finish within microseconds of the write), the notification was missed and the request waited out the whole budget, answering 408 WAIT_TIMEOUT with waiting_on: "stage" and waited_ms near 30,000 for an event that was already indexed. The wait now subscribes first, then reads, and reads again on every wake-up and every poll. A failed stage still fails at once, a real timeout still answers 408 at the deadline, and a 408 with waiting_on: "indexer" (a real indexer backlog) is unchanged. Two deterministic tests reproduce the race: both time out on 0.10.4's code and pass on 0.10.5's. The race is rare under load: about 51,600 wait=indexed writes against the release candidate (sequential and concurrent writers, and a write, seed and forget load) gave no 408, and the same loops gave none on 0.10.4 either, so the tests are the proof, not the load runs. The race matches an intermittent 408 in this project's own CI, which has not recurred in the release runs.

Cross-customer questions in Hindi or Hinglish: time windows

With the model planner on (CORTEX_COHORT_PLANNER=auto or llm), a question such as "kin customers ne kabhi bhi ..." could get a plan whose window.natural was a phrase the executor cannot resolve, and the answer was 422 INVALID_BODY naming plan.window.natural. Now, on a model-written plan only:

A plan you send yourself in cohort.plan is checked exactly as before.

Read-all answers: the same in every language, with the question's meaning kept

Asked "which customers discussed X" or "what do customers raise about X" in English and then in Hinglish, the read-all lane could list different customers, and two runs of the same question could too. 0.10.5 changes what the readers (the map, theme and check calls) are given:

Measured on a copy of a 300-customer store whose cohort of phone calls holds 574 conversations (read-all on, map model claude-haiku-4-5, answer model claude-opus-4-8). On 0.10.4 the question "Which customers discussed a loan or EMI on a phone call?" listed 115 to 128 customers from one run to the next, and its Hinglish wording 132, because the model planner left out "phone call" and the chats were read too. On 0.10.5:

QuestionWhat the readers were givenResult
"Which customers discussed a loan or EMI on a phone call?""Which customers discussed loan or EMI?"574 of 574 calls read, 113 customers, 13.5 s, $1.25
The same, asked again(reused)the same 113, no map call, 6.4 s, $0.04
"Kin customers ne phone call par loan ya EMI ki baat ki?"(reused: the same plan)the same 113, no map call, 9.6 s, $0.07
The Hinglish question first, on another boot of the same copy"Which customers discussed loan or EMI?"574 of 574 calls read, 112 customers; the English question then reused this read: the same 112
"Which customers did not discuss a loan or EMI on a phone call?"the question as askedits own read of the 574 calls: 114 customers, 111 of them also in the first list (see "Known issues")
"Kin customers ne phone call par loan ya EMI ki baat nahi ki?""Which customers did not talk about loan or EMI on a phone call?" (the planner's translation)its own read of the same 574 calls: 111 customers
"Which customers discussed both a loan and insurance on a phone call?"the question as asked88 customers, 86 of them in the first list (the earlier candidate's canonical "loan or insurance" would have widened it)
The first question again, after one listed customer was erased"Which customers discussed loan or EMI?"the cache was cleared by the erasure: 571 of 571 calls read again, 114 customers; the erased customer, listed by the first read, is not among them

A fresh read of the 574 calls took 12-17 s and cost $1.24-1.28 in model calls; a reused one 6-10 s and $0.04-0.07 (the planner and the narration). The two fresh reads of the plain question (113 and 112 customers) show the run-to-run spread that reuse removes for repeated questions.

Reuse needs the same data revision, so on a scope that receives captures all the time a result is rarely reused; the canonical question and the glossary binding are what make two such reads agree. docs/ERASURE_COVERAGE.md lists the result cache as a store (no conversation text; covered by the clears and the fence re-check).

Settings page

Label limits (announced)

Before 0.10.5 nothing checked a label. 0.10.5 adds these limits on context.labels, not enforced by default: a cap on what clients send is announced before it applies. A later release will enforce them by default; an operator can enforce them now with CORTEX_LABEL_LIMITS=on.

LimitDefaultSettingError while enforced (422, not retriable)
Labels per memory4,096CORTEX_LABELS_MAX_COUNTLABELS_TOO_MANY
UTF-8 bytes per label262,144 (256 KiB)CORTEX_LABEL_MAX_BYTESLABEL_TOO_LONG
Not empty or only whitespacealwaysnoneLABEL_EMPTY
No ASCII control character except tab, line feed and carriage returnalwaysnoneLABEL_INVALID_CHARACTER

API reference §3.5.3, "Label limits", has the full rules.

Docs

Measurements

Measured on two release candidates of this code: 1c5acd42 (every check below) and 8fa30280, which only adds the planner instruction about denials (described under "Read-all answers"; its image was smoked, and the Hinglish questions were measured again on it). Only these notes changed after 8fa30280.

Environment variables

VariableDefaultWhat it does
CORTEX_COHORT_READ_CACHEonRead-all result reuse (see "Read-all answers"); 0 turns it off.
CORTEX_LABEL_LIMITSoffon enforces the label limits on new writes.
CORTEX_LABELS_MAX_COUNT4096Labels per memory while the limits are enforced.
CORTEX_LABEL_MAX_BYTES262144UTF-8 bytes per label while the limits are enforced.

docs/generated/env-vars.md has the details.

Error codes

HTTPCodeWhereRetriable
422LABELS_TOO_MANYA write with more than CORTEX_LABELS_MAX_COUNT labels, only while CORTEX_LABEL_LIMITS=onno
422LABEL_TOO_LONGA label longer than CORTEX_LABEL_MAX_BYTES UTF-8 bytes, only while enforcedno
422LABEL_EMPTYAn empty or whitespace-only label, only while enforcedno
422LABEL_INVALID_CHARACTERA label with an ASCII control character other than tab, line feed and carriage return, only while enforcedno

On /v1/experience/bulk the code is on the failing item inside a 422 INVALID_ENVELOPE.

Startup warnings

Rollback

A 0.10.4 image opens a data dir 0.10.5 has used: no column family, directory, WAL row kind, index field or key prefix is added, and the read-all result cache is in memory only. A Settings save that cleared a key writes the same file entry 0.10.4 writes for a removal. To roll back, stop 0.10.5 with the usual 120 s grace and start 0.10.4 on the same data dir. After a rollback, a Settings save with an empty key field from a client that sends the field removes the key again (the 0.10.4 behaviour), the label limits do not exist, and erasures on a store with expired retry records are slow again.

Checked on one data dir with the published v0.10.4 image and each release candidate, the cohort flags on, a graceful stop between each step (0.10.4 writes, 0.10.5 reads and writes, 0.10.4 again, 0.10.5 again): no column family, directory or on-disk format was added (the only new top-level entry, cohort_alias.key, comes from 0.10.4's cohort lanes), no error line and no panic, and every data check passed (84 of 85 checks; the remaining one expected the 404 a 0.10.3 rollback answers on /v1/cohort/query, where 0.10.4 answers 409 COHORT_DISABLED with its flags off, as it should).

Known issues

Clients

No client release. Every client published with 0.10.4 (Python SDK cortexdbai 0.12.0, TypeScript SDK cortexdbai 0.10.1, CLI cortexdb-cli 0.6.0, MCP cortexdb-mcp 0.8.0, connectors 0.2.22) works against 0.10.5 unchanged. In source, the Python SDK's experience(), the TypeScript ExperienceOptions.labels comment and the MCP server's memory_store tags parameter describe the announced label limits (docstrings only; no version change).

Notes

Single-node remains the only supported topology.