CortexDB · docs

CortexDB 0.10.3 — order=recency honoured on leaf-scope answers, event-kind coverage, an answer cache, one-click updates, and a JSON error for every rejected request

0.10.3 is a fix release on top of 0.10.2, the last published release. Everything in RELEASE_NOTES_0.10.2.md still applies; this page lists only what changed since, including the work merged after 0.10.2 was cut (the answer cache, one-click updates, the directives.extract fix and the best-practices guide). It adds no column family, directory or on-disk format, and nothing is migrated: a 0.10.2 server opens and serves a data dir 0.10.3 has used (see "Rollback").

Summary

Upgrade from 0.10.2

Before you upgrade: what clients and operators will notice

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.3 and the git_sha you deployed. The capability list is the same 11 names as 0.10.2.
  2. GET /v1/admin/ready: ready: true.
  3. With enrichment off and the background scheduler on, the compaction log line reports llm_stages="enrichment_unset"; set CORTEX_CONSOLIDATION_ENABLED=1 if you relied on consolidation.
  4. If your client calls /v1/answer without view on event-stream scopes and wants ranked answers, send order: "relevance".

Upgrading from 0.10.1 or v0.9.13

0.10.3 contains everything in 0.10.2. Read RELEASE_NOTES_0.10.2.md, "Upgrade from 0.10.1" (and from there RELEASE_NOTES_0.10.1.md for v0.9.13) first; this page applies on top.

What changed

Recall and answer: order: "recency" in every view that reads the scope's own events

order: "recency" replaces ranked retrieval for the events layer with the scope's newest eligible events by observed_at, newest first, with the same eligibility as ranked mode (scope, tenant, filters, time window, forgets, cancels). Since v0.9.0 it ran only in holistic, and /v1/answer recalls an exact scope with no child scopes granular, so both an explicit and an inferred order were dropped there without a trace. 0.10.3:

Recall viewExplicit order: "recency"Order inferred by /v1/answer
holistictail (as before)tail (as before)
granular (the leaf-scope answer view), lineagetail (was ranked)tail on a typed event stream, else ranked (as before)
raw (/v1/recall only)tail in layers.events, no context block (was ranked)n/a
descend, structuredrankedranked

A typed event stream is a scope where more than half of the tail's events carry a non-empty context.intent. The inferred tail is limited to them because a cue word says nothing about where a conversation's evidence is: in a chat history, "which bike am I currently riding?" is answered by a turn from weeks ago, not by the newest 100 turns (measured on LongMemEval below). A tail the entity grounding gate would clear (no event names the question's capitalised names) falls back to ranked retrieval instead of an empty context, in every view.

Answer: the over-budget cut keeps the newest events

The answer context is capped at budgets.max_tokens (default 4,000, at most 8,000). A timeline tail longer than that now keeps its newest-first order and loses its oldest events. Before, the cap re-ranked the tail by evidence type, could drop its newest event, and still told the model the context was newest-first.

Answer: event-kind coverage (CORTEX_ANSWER_KIND_COVERAGE, default on)

On a scope whose events carry context.intent, /v1/answer reads the scope's kind census: the newest eligible event of each distinct intent (at most 32 kinds; a scope with more gets none), under the recall's scope, view, filters and time window. The census is one bounded read of the scope's own events (at most 8 pages of 1,024; a scope whose events do not fit gets none). It is skipped for use_pack_id, for an answer that descended into child scopes and on native reads.

Events without an intent have no kind, so a scope without intents is unchanged.

Answer: premise guard

When an answer asserts something but also says the memories do not mention what the question presupposes, /v1/answer replaces it with "The information provided is not enough." Changes:

Answer cache (opt-in)

With CORTEX_ANSWER_CACHE=1, a repeated /v1/answer from the same caller and token, in the same scope and view, with the same answer-shaping fields (and the same UTC day when the request has no question_date), returns the stored response without recall or a model call, while it is provably current:

One-click updates (opt-in)

docker-compose.updater.yml adds an updater sidecar, the only container with the Docker socket, with no published port. With CORTEX_UPDATER_URL and CORTEX_UPDATER_TOKEN set, the admin console's Updates page offers Update now: POST /internal/ui/update asks the sidecar to check the data is on a volume, pull the release while serving, take a cold backup of the data, stop the server gracefully, recreate it on the new image with the same settings, wait for health, and roll back on failure. It refuses an older version (422 DOWNGRADE_REFUSED) and answers 503 UPDATER_NOT_CONFIGURED without a sidecar. docs/PRODUCTION_DEPLOYMENT.md has the setup, and TELEMETRY.md describes the one new call (to the sidecar on the compose network; CortexDB makes no new outbound call).

directives.extract on single captures

A POST /v1/experience under the unified WAL lost directives.extract when the indexer rebuilt the request from the stored event, so a "search only" capture ran every enrichment stage. The directive is now stored on the event (extract, omitted when absent), honoured by the indexer, returned on events, recall items and /v1/export, and restored by /v1/import/jsonl. Events written before 0.10.3 carry none and are not repaired. Triples are always extract-none.

Recall: scope_partition_map degraded source

After a restart over a large store the server builds its scope-partition map in the background. Until it lands, descendant listings and cold-tier vector scans come back empty without an error. Recall now names a scope_partition_map: ... entry in diagnostics.degraded_sources (for a caller that may read diagnostics), /v1/answer reports the scope_partition_map code to every caller, and the answer cache never stores such an answer.

Errors: the envelope for every rejected request

A request whose body, query string or path parameter the route's extractor could not read was answered with a plain-text body. It now gets the error envelope (error_code, message, request_id, retriable: false, details.part, details.reason; /v1/answer and /v1/recall/stream add details.field_path), with the status unchanged (table below). The envelope's request_id and the X-Request-Id header are the request's own id: the client's when it sent one, else the one the server minted for the request, which is also the id of the request log line and of the authentication layer's audit row. The 413 of an oversized body does the same.

Operations

Docs

docs/BEST_PRACTICES.md (ingestion, embeddings, what runs on-prem, answer reuse, staying current, launch checklist), the embedding migration runbook and the production deployment guide are in the admin console's docs. The API reference documents all of the above, and docs/schemas/openapi.json adds order to /v1/recall, the answer cache headers and metrics, and the stored extract.

Fixes

Measurements

Measured against the published 0.10.2 image, one host per test. Unless a paragraph says otherwise, the figures come from the 0.10.3 release candidate before its review fixes; the last section checks the final build against it.

A customer-journey regression set

115 questions over typed event streams (page views, transactions, cancellations and chats per end user), graded by a model judge, sent through the API exactly as the customer's integration sends them (no view):

Questions0.10.20.10.3
Recommendations: what the user is likely to want next (12)012
Names of recent events (12)511
Summary of the user's recent activity (12)512
Event linked to a conversation (4)14
Did the user ever ... (absence, 10)710
Next best action (11)1011
Transaction activity (11)1011
Activity in a time window, latest activity, latest chat, latest conversation, recently viewed items (43)4343
Total (115)81114

The best earlier run of the same set (a v0.8.x build, July 2026) was also 114. The recency tail served 83 of the 115 questions (none on 0.10.2). The one miss listed one event name more than the expected answer; its prompt is token-identical to two earlier runs of the same change that scored 115 of 115, so it is answer-model or judge variance.

With the customer's console request shape (view: "holistic", where 0.10.2 already served the tail): 0.10.2 answered 111 of 115. 0.10.3 answered all 77 questions of the eight categories where 0.10.2 had failed any (0.10.2: 73 of the same 77); the other 38, all correct on 0.10.2, were not re-run.

Per answer, no view: about 7,434 input and 291 output tokens (0.10.2: 6,957 and 261); with the answer model used, $0.0445 against $0.0413. Latency with 4 concurrent clients: p50 4.9 s, p90 8.9 s, p99 10.9 s (0.10.2: p50 5.6 s, p90 10.2 s).

Conversation histories (LongMemEval)

The inferred tail must not reach chat histories. On the 38 LongMemEval questions that carry a recency cue ("currently", "recent", "latest", "most recent"), a build that applied the inferred tail everywhere answered 17 of them from the newest 100 turns, and their contexts held 0 of 42 gold evidence turns (0.10.2: 39 of 42). 0.10.3 serves those 17 no tail: 15 of 17 contexts are byte-identical to 0.10.2's and the other 2 hold the same evidence (37 of 42 gold turns in context on both, on a store built for this check). Answers were not re-run on the final build.

On the 30 abstention questions (one store, both binaries, the same answer configuration), no answer changed because of the premise-guard change on the release candidate; the other differences between the two runs had byte-identical contexts (answer-model variance).

Indexed captures under forgets

4 CPUs, 3 GB, real disk syncs, two pairs of runs in reversed order: new-scope wait=indexed p50 77 ms on both; p99 287 ms against 279 ms on 0.10.2 (+7.7 ms, +2.8 %; 95 % bootstrap interval -0.1 to +15 ms). Without forgets p99 was 5.5 ms lower on 0.10.3. No write took over 1 s and every write answered 200.

Other checks

The live Docker suite (six scenarios), the multilingual smoke (42 of 42), the upgrade and rollback probe (72 of 72, "Rollback"), the enrichment-off compaction check (31 LLM calls over 3 passes on 0.10.2, 0 on 0.10.3, 31 again with CORTEX_CONSOLIDATION_ENABLED=1), the rejected-request envelopes and their log fields (8 of 8 failed requests logged with error_code; 0 on 0.10.2), and uptime_seconds.

The final build

The review fixes (the inferred-tail restriction, the tail grounding fallback, the absence-report clause cut and the rejected requests' ids) were checked without answer-model calls (skip_answer_llm) against the candidate on the same stores. On the customer-journey set the answer context of 114 of 115 questions is byte-identical to the candidate's in both request shapes, and the tail serves the same 83 (no view) and 85 (view: "holistic") questions; the 115th context is ranked and also differs between two runs of the candidate itself. That question is the only one whose candidate answer the clause cut shortens; it answered correctly in 2 of 2 runs on the final build. The release commits after the final build change documentation only.

Environment variables

VariableChange
CORTEX_ANSWER_CACHENew. 1 turns the answer cache on (default off).
CORTEX_ANSWER_CACHE_TTL_SECSNew. Default 300, at most 540.
CORTEX_ANSWER_CACHE_MAXNew. Default 1,000 entries, at most 100,000.
CORTEX_ANSWER_KIND_COVERAGENew. Event-kind coverage, default on; 0/off/false turns it off.
CORTEX_UPDATER_URL, CORTEX_UPDATER_TOKENNew. The updater sidecar and its shared token.
CORTEX_CONSOLIDATION_ENABLEDUnset now follows enrichment (was: on). 1 opts in; 0/false/off turns consolidation off.
CORTEX_RECENCY_ORDERUnchanged switch; the tail it controls now serves granular, lineage and raw too.
CORTEX_RECENCY_INFERENCEUnchanged switch; an inferred order outside holistic takes the tail only on typed event streams.

docs/generated/env-vars.md has the full rows.

Error codes

HTTPCodeWhereChangeRetriable
400INVALID_BODYany v1 route that reads a JSON bodyWas a plain-text 400: the body is not JSON, has trailing bytes, could not be read or is not UTF-8.no
400INVALID_QUERYroutes with a query stringNew: a query parameter that does not parse, or a missing required one. A repeated ?scope= on GET /v1/events/{id} is one.no
400INVALID_PATH_PARAMSroutes with a path parameterNew: a path parameter that does not parse.no
400, 422INVALID_REQUESTany v1 routeNew: any other plain-text 400 or 422.no
415UNSUPPORTED_MEDIA_TYPEroutes with a JSON bodyWas plain text: a missing or non-JSON Content-Type.no
422INVALID_BODYroutes with a JSON bodyWas plain text on most routes: an unknown field, a wrong type, a missing field.no
422DOWNGRADE_REFUSEDPOST /internal/ui/updateNew.no
503UPDATER_NOT_CONFIGUREDPOST /internal/ui/updateNew.no

Startup warnings

Rollback

0.10.3 adds no column family, directory or on-disk format. To roll back, stop 0.10.3 with the usual 120 s grace and start 0.10.2 on the same data dir. A rollback brings back 0.10.2's behaviour, including the issues this release fixes. 0.10.2 ignores the stored extract directive, so an event it indexes after the rollback runs every stage again; the answer cache, held in memory, is gone with the process.

What was checked, on one data dir, with a graceful stop between each step (on the release candidate before the review fixes, which change no storage code):

  1. 0.10.2 wrote English, Devanagari and Hinglish captures, a 32 KB turn, a 13 KB event captured on someone else's behalf and a 200-item bulk, and ran a forget-all and a forget by id.
  2. 0.10.3 read all of it, wrote single captures, a 300-item bulk and a multi-chunk Hindi document, and ran a forget-all and a forget by id.
  3. 0.10.2 again opened the store and served everything 0.10.3 had written (the bulk samples, the long document by id); what 0.10.3 had forgotten stayed gone. It wrote, bulk-wrote 100 items and forgot the document by id.
  4. 0.10.3 again read everything, including the writes made during the rollback, and the forgotten items stayed gone.

Every phase stopped cleanly and logged no error. After each phase the column families of every RocksDB store and the data dir's top-level entries were listed: 0.10.3 added none, before or after the rollback (72 of 72 checks). A rollback from 0.10.3 to 0.10.1 or v0.9.13 was not re-run; 0.10.3 stores nothing 0.10.2 does not, so RELEASE_NOTES_0.10.2.md, "Rollback", describes it.

Known issues

New or changed in 0.10.3:

Carried over from 0.10.2 (still present in 0.10.3):

Clients

Notes

Single-node remains the only supported topology.