CortexDB 0.10.1 — multilingual memory, erasure that reaches every copy, derived-data repair, and recall contexts that stay inside their budget
0.10.1 is the first published 0.10 release. 0.10.0 was prepared but never tagged or published,
so this page covers everything that changed since v0.9.13, the last published release.
RELEASE_NOTES_0.10.0.md is kept for reference only; everything on it is repeated here, with the
fixes made after it.
Summary
- Multilingual capture and recall. Memories are captured and recalled in the language they were written in, never translated. Captures carry a language tag, a script list, a time zone and the times they refer to; recall takes language and script hints, up to four query variants (for example an English phrasing of a Hinglish question), lenient time phrases and a referenced-time boost. The measured launch set is English plus Latin-script Hinglish; every other script is captured, tagged and recalled, but cross-script recall is not yet at the quality bar.
- Capabilities.
GET /v1/admin/versionnow listscapabilities[]. Every new request field belongs to one capability; gate on the list, never on the version string. - Erasure that reaches every copy.
/v1/forgetand/v1/erasuresnow delete the uploaded blob an event came from, compact the erased bytes out of the storage files, keep an erased fact erased across a crash, report only what they actually removed, select exactly what existed before the request, and cost what they erase rather than what the store holds (a forget by id on a 5M-event store: about 65 s without the new erasure index, under 0.3 s with it). - Facts and beliefs (enrichment on). Negation and cessation are kept, different people are no longer merged, non-Latin names get their own entity ids, facts keep their original wording with a byte-exact quote, and beliefs have stable keys and deterministic ids.
- Repair of data written by earlier builds. New operator routes re-derive facts and beliefs that v0.9.13 and earlier wrote wrongly, from the event log, with no wipe and no re-ingestion.
- Answer and recall context.
/v1/answercontexts built from whole events are packed correctly again; since 0.10.1 no context entry takescontext_blockpast its token budget (an oversized event is served as an excerpt of its best-matching chunks), and every event the block renders is named in the response. - Documents. Recall of a long event says which chunks matched, as byte ranges into the stored text; a new operator route shows how any event was chunked.
- Also: code-plane access and safety fixes, admin-console fixes,
/v1/answerabstention detection, usage-ledger fixes, new operator routes and metrics, and the client versions released with 0.10.1:cortexdbai0.12.0 (PyPI) and 0.10.0 (npm),cortexdb-connectors0.2.22,cortexdb-mcp0.8.0 andcortexdb-cli0.6.0 (see "Clients").
Upgrade from v0.9.13
Before you upgrade: what existing clients and operators will notice
Read this list even if you only use English. Several contracts change by default.
-
Gate new request fields on
capabilities[]. A server that does not list a capability refuses its fields with422 INVALID_BODY: v0.9.13 refuses each one as an unknown field (it has nocapabilitieskey at all, which means none of them), and 0.10.1 names the fields indetails.unsupported_fieldsand the missing names indetails.required_capabilities. -
Text blobs that are not valid text are refused. A text blob with no
charset=whose bytes are neither UTF-8 nor UTF-16 with a byte-order mark now fails the write with422 BLOB_TEXT_ENCODING_UNKNOWNinstead of being stored as U+FFFD replacement text. In/v1/experience/bulkone such blob fails the whole batch. Declare the charset on upload (Content-Type: text/plain; charset=windows-1252) and it decodes. -
/v1/recallcontext_blockbytes change, English included, in four cases:- an assistant turn's echo of the user turn (
In reply to user: ...) is dropped when that user turn is rendered in the same context; - an event too large for the remaining budget is skipped and packing continues with the smaller events behind it, instead of the context ending there;
- no entry takes the block past
budgets.max_tokensany more, the first one included: v0.9.13 admitted the first-ranked event whole whatever its size (a 32 KB answer recalled atmax_tokens: 1000came back as a 32,803-byte block); - an event that did not fit whole is served as an excerpt of its best-matching chunks in the room still free at its rank position.
Otherwise the English bytes of
context_blockare unchanged: a pack of English events in which every event fits whole and no assistant turn echoes a rendered user turn is the same byte for byte. (A non-English event with a capture time zone gains a local-time bracket after its UTC one.) - an assistant turn's echo of the user turn (
-
layers.events[].content.textcan be an excerpt. An excerpted event's item carries the same excerpt, flagged as a partial view:content._partial: true,content._partial_reason: "budget_excerpt",content._original_bytesandcontent._excerpt_ranges. Treatcontent.textas the whole text only when_partialis absent;GET /v1/events/{id}returns the whole event. -
The provenance trail names every event the block renders. When the cross-layer knapsack evicts a rendered event from
layers.events, itscontext_contributorsrow carriesevicted_from_layers: true, and that row is the only place the response names it. Check forget, erasure and citation logic against the union oflayers.eventsand those rows. -
Packs that v0.9.13 returned empty may now answer, with a warning. When the query names an entity no retrieved event mentions, an all-English pack over an all-English scope is still emptied. The pack is now kept, with a
warnings[]entry and adiagnostics.filters_appliednote, when the query and the memories are in different scripts, the request carries a non-English hint or variant, or the scope holds at least two non-English memories. -
/v1/forgetreports only what it removed. It returns a top-levelpurgeobject (the shape/v1/erasuresreports assummary.purge); a repeat forget returns zeros, a disabled graph adds no phantom document, and a scope-wide forget of 300 events reports 300, not 600. -
Forget warnings name the real reason. The "use
cascade=redact_events" warning fires only for the whole-scopederived_onlyshape. Amemory_idsrequest whoselayersleave outeventsgets "layers did not name "events" ..." instead; no selector request is told to useredact_eventsany more (dropping the selector would have turned a one-id forget into a scope wipe). -
A forget or erasure has one cutoff. Everything the server stamped before it is removed and everything after it is kept. A capture sent at the same instant as a forget-all can land on either side; send captures you need to keep after the forget returns.
-
New retriable errors around destructive work:
409 DESTRUCTIVE_OPERATION_IN_PROGRESSon captures that would register a new scope inside a running forget or erasure (it used to be a permanent-looking403 POLICY_DENIED),409 SCOPE_STATE_CHANGEDon scope edits there,503 SERVER_SHUTTING_DOWNon forgets and erasures during a graceful stop, and409 REDERIVE_IN_PROGRESSwhile a repair job runs. Retry them afterRetry-After. The full list is under "Error codes". -
Forgetting an event id that belongs to another scope answers 200 with every count 0, like a missing id, instead of
502 FORGET_BACKEND_FAILED(which revealed that the id exists elsewhere). -
?wait=capturedon an inline or transcript blob write behaves likewait=indexed: the write keeps its blob lease until the event's link to the blob is published. -
Non-Latin names get new entity ids. A name with any non-ASCII character is now
ent_<ascii-base>-<16 hex>, orent_u-<16 hex>with no ASCII letters. Pure-ASCII names keep their ids byte for byte, and ids stored by v0.9.13 (ent_Jos_) still resolve on reads and forgets. -
Enrichment-on deployments get new derived output and spend. Facts, beliefs, belief ids and the extraction prompt change (see "Facts and beliefs"); the prompt is about 23% larger per English extraction call, plus one short model call per doubted polarity.
-
Give the server 120 s to stop. A graceful stop waits for running forgets and erasures (up to 60 s), persists, then drains the erasure residual pass (up to 20 s). Docker's default 10 s cuts that short: use
docker stop -t 120,stop_grace_period: 120s(both compose files in this repository set it) orTimeoutStopSec=120in a systemd unit. -
Code plane (
CORTEX_CODE_PLANE, off by default). A repository bound under a scope no roster governs is no longer served to per-actor tokens (404, left out of listings) until that scope is registered; a per-actor token can no longer register a repository without a governed scope; every registered repository is re-indexed once on the first start. -
Admin console and telemetry figures move.
events_storedcounts each memory once, so it halves on unified-log stores; not-configured503s no longer count inerrors_total; a console restart exits with code 75; streamed answers now book their input tokens in the usage ledger, so the answer lane's recorded cost for streamed answers goes up (it was undercounted). -
directives.extract: ["beliefs"]or["understanding"]now run enrichment. They used to switch the enrichment lane off, discarding the work they asked for.extract: []still turns enrichment off for the event. (The directive is honored on/v1/experience/bulkonly; see "Known issues".)
Plan the first start
A v0.9.13 data dir needs no migration step: 0.10.1 opens it and does the one-time work in the background.
- Erasure index backfill. The first start files every existing event-log row into the new
erasure index, in the background and resumable. Measured: 6.6-7.1 s for a 1M-event store and
53 s for 5M, both overlapped with the start, whose ready time did not move beyond noise. Until
erasure_index.wal.readyistrueonGET /v1/admin/metrics, erasures read the whole store as before. Every start also rebuilds the erasure retry candidates and the idempotency reverse index in the background. - Belief sweep and reconcile (incremental-layer deployments,
CORTEX_V1_LAYERS_INCREMENTAL). The first start removes builder beliefs whose topic key moved and rebuilds them under the new keys. The rebuild runs in the auto-layer ticks, at most 2,000 topics per scope per tick with one embedding call per belief (about 5 minutes for 1,400 topics in our tests), so swept beliefs are missing until it finishes. It is finished whenGET /v1/admin/layers/statsreportsbelief_reconcile.done: true(dirty_topicscounts what is still queued).doneneedsCORTEX_V1_LAYERS_AUTOon (without it nothing is seeded or swept: rebuild withPOST /v1/beliefs/build); it never turns true while aCORTEX_V1_LAYERS_AUTO_EXCLUDE_SCOPESprefix covers a scope with facts (build those scopes by hand); and a failed build drops its topics, so compareby_scope.<scope>.belief_errors_totalbefore and after. Deployments without incremental layers keep their old beliefs until aPOST /v1/beliefs/buildof each scope rebuilds them under the new keys. Beliefs you declared withPOST /v1/beliefsare never swept. - Code plane re-index (only with
CORTEX_CODE_PLANEon): every registered repository re-indexes once (extractor generation 4).
Upgrade steps
- Read "Before you upgrade" above, "Known issues" and "Rollback" below, and the "Erasure raw
residual", "Erasure index" and "Answer context" sections of
PRODUCTION_CONFIG.md. - Enrichment-on deployments: run
scripts/derived_corruption_audit.pyagainst the running v0.9.13 server and keep the report. It is read-only, uses GET routes only, and is the input of the repair in step 7. Take it before the upgrade: after 0.10.1 rebuilds the beliefs, a colliding legacy entity id can stop showing up in a new audit. - Rehearse on a copy if you can: restore a cold backup of the data dir on a separate host, start 0.10.1 on it with the production environment, and time the first start (the reconcile above is the slow part). Never start a second server on a production data dir, and never build the binary on a production host.
- Record exactly which build you deploy (image digest or binary sha256, and the
git_shathatGET /v1/admin/versionreports), and keep the current v0.9.13 artifact for a rollback. - Pause ingestion, stop v0.9.13 with a grace of at least 120 s, and take a cold backup
(
scripts/cold_backup.py). If you keep the old container for a rollback, set its restart policy tono(docker update --restart=no <container>): underalways, a Docker daemon restart starts a stopped container again, and two builds would race for the same port and data dir. - Start 0.10.1 with a 120 s stop grace, wait for
GET /v1/admin/ready, and on incremental-layer deployments forbelief_reconcile.done. Check thatcapabilities[]lists the ten names under "Capabilities". - Enrichment-on deployments: follow
docs/DERIVED_REDERIVE_RUNBOOK.mdwith the audit report of step 2 (see "Repairing derived data written by earlier builds"). An apply there is one-way for a rollback to v0.9.13, so decide on the rollback window first. - For each billing tenant that uploaded text blobs, run
POST /v1/admin/index-audit/source-blob-links(dry run first), then reviewGET /v1/admin/index-audit/orphan-blobs(see "Erasure"). - Resume ingestion. Update clients to gate the new fields on
capabilities[].
If you ran a pre-release 0.10.0 or multilingual branch image
Replace the binary or image; nothing is migrated beyond what the first start of a 0.10 build
already did. With enrichment on, the pre-release builds could store a negation twice, drop a
predicate's negation, or supersede a fact with one about another subject (fixed in 0.10.1, see
"Extraction fixes in 0.10.1"). Run scripts/derived_corruption_audit.py (tool version 2 reports
negation_doubled and negation_dropped) and repair what it lists with the re-derivation route.
Recall contexts differ from the pre-release builds wherever an event is longer than the room it
gets.
Capabilities
GET /v1/admin/version returns {version, crate_version, git_sha, built_at, capabilities}.
capabilities is a sorted, append-only list of API-shape names; a breaking change would ship as a
new _v2 name beside the old one, never by changing a listed one. 0.10.1 lists ten:
| Capability | What it adds | Request fields | Response fields |
|---|---|---|---|
blob_text_decode_v1 | Charset-safe decoding of text blobs | none (behaviour) | 422 BLOB_TEXT_ENCODING_UNKNOWN |
budget_excerpt_v1 | No context entry exceeds the budget; oversized events served as excerpts; the context receipt | none | layers.events[].content._partial_reason: "budget_excerpt", _original_bytes, _excerpt_ranges; context_contributors rows with evicted_from_layers |
capture_language_v1 | Language, script and time-zone hints on captures | language, scripts[], context.timezone on /v1/experience and /bulk | language, scripts, language_source, context.timezone on event reads and recall items |
erasure_blob_cascade_v1 | Erasure deletes the source blob; the raw residual pass | none | source_blob_ids on events; purge on /v1/forget; purge.blobs, purge.retained_shared_blobs, log_residual_cleared_at, log_residual_pending |
matched_fragments_v1 | Which chunks of a long event matched | none | layers.events[].matched_fragments[]; the context_fragments trail entry |
query_variants_v1 | Extra phrasings of the question | query_variants[] on /v1/recall and /v1/recall/stream | per-variant ranks in the diagnostics side table |
recall_diagnostics_v1 | Per-channel recall diagnostics | diagnostics: "full" (existing field) | diagnostics.items, drops, channel_lists, items_truncated |
recall_language_v1 | Language and script hints on recall | language, scripts[] on /v1/recall and /v1/recall/stream | pack warnings[] |
refers_to_v1 | The times a memory talks about | context.refers_to[] on captures; temporal.refers_during on recall and answer | context.refers_to on events |
temporal_lenient_v1 | Lenient time phrases and calendar time zones | temporal.natural_mode, temporal.timezone on recall and answer | the temporal_natural_ignored warning |
fact_surface_v1 is known to the server but not listed: the fact fields it will promise
(below) are returned already, but do not depend on them until the name appears.
Multilingual capture and recall
Nothing is translated. You send the original text plus hints; the server keeps both and never rewrites the text.
Capture tags (capture_language_v1)
- Fields.
language(BCP-47, for examplehi-Latn),scripts[](ISO 15924, at most 8) andcontext.timezone(an IANA zone name) on/v1/experienceand each/v1/experience/bulkitem. Values are canonicalized (HI-latnbecomeshi-Latn,devabecomesDeva, duplicate scripts are dropped). A malformed value is422 INVALID_ENVELOPE.timezonemust be an exact IANA name from the bundled tzdata list (canonical zones and backward links):IST,+05:30andAsia/Mumbaiare refused. - Every event gets one language tag, decided in this order: your
language(andscripts), then a non-Englishsource.language, then alang:<tag>label (for examplelang:hi-Latn), then script detection (und-Deva,und-Arab, ...; Tamil, Telugu and Thai map tota,teandth), then a Latin-script Hinglish heuristic (hi-Latn), then English. Mixed text lists every script, the dominant one first; digits, punctuation, currency signs and emoji add no script. - What is stored and returned. Non-English events return
language,scriptsandlanguage_source(hint,source,labelordetected) onGET /v1/events/{id}, recalllayers.eventsitems and/v1/export. An event that is English by default stores no tag, so English events are byte-identical to v0.9.13; an explicit"language": "en"is stored. - Not part of write identity. The tags, the time zone and
refers_toare excluded from the idempotency identity, so the same body with or without hints replays with no409. A replay whose hints differ keeps the first write's tags and returnswarnings: ["conflicting_hint_on_replay: ..."]on the202, on thewait200and on each bulkresults[]item. - Erasure clears
context.timezoneandcontext.refers_to; the language tag stays, because it classifies the erased text without quoting it.
Recall hints and stoplists (recall_language_v1)
languageandscripts[]on/v1/recalland/v1/recall/stream(validated like the capture hints; malformed values are422 INVALID_BODYwithdetails.field).- They select the query stoplist used by BM25 and the lexical re-ranking stages: English with no
hint and with an
en,hiorhi-Latnhint (a Hinglish list measured a recall loss); nothing stripped forfil,id,vi,und-tags, every other language, or ascriptshint withoutLatn. The query text alone never changes the stoplist. /v1/answerdoes not takelanguageorscripts.
Advisory entity grounding
When the query names an entity (a capitalized or quoted anchor) that no retrieved event mentions,
the grounding gate used to empty the pack. It still does for English over an all-English scope.
It now keeps the pack and adds a warning when any of these holds, checked in this order:
non_en_hint (the recall language is not English), non_en_variant (a query variant's is not),
script_mismatch (a retrieved event has a script the query, its variants and the scripts hint
lack), scope_census (the scope subtree holds at least two distinct non-English memories). The
pack carries warnings: ["entity_grounding_advisory: query entities [...] not found in retrieved memories; pack kept (<reason>) - results may be about a different entity"], and
diagnostics.filters_applied gets entity_grounding:advisory:<reason> under summary and
full. The census counts live captures at once and folds history in with a bounded background
scan per scope, so right after a restart the first recall of such a scope may still be enforced.
Query variants (query_variants_v1)
query_variants[]on/v1/recalland/v1/recall/stream: at most 4 entries of{text, language?, script?}, each at most 4,096 bytes. More entries, empty text or longer text is422 INVALID_BODYwithdetails {field, max_items, max_text_bytes}. A variant that repeats the query or an earlier variant is dropped silently.- Variants are embedded in the same provider call as the question and each runs its own vector and BM25 lists, merged with the primary results round-robin (primary first). Reranking still scores against the question only. A variant can rescue a grounding miss but never cause one.
- Cost: about +100 ms at p50 with two variants on text-embedding-3-small, one provider round trip.
CORTEX_QUERY_VARIANTS_FUSION(interleavedefault,depth_cap) andCORTEX_QUERY_VARIANTS_DEPTHare for evaluation;deep_rrfis a negative control, never a production setting.
Time: lenient phrases, calendar zones and referenced times (temporal_lenient_v1, refers_to_v1)
temporal.natural_modeon/v1/recall,/v1/recall/streamand/v1/answer:strict(the default) keeps the422 UNPARSEABLE_TEMPORALfor a phrase the server cannot parse;lenientdrops the phrase, applies no time filter, and addstemporal_natural_ignored: unparseable temporal phrase "..." ignored (natural_mode=lenient); no time filter was appliedto the pack'swarnings[].temporal.timezone(IANA): calendar phrases such astoday,yesterday,this weekandlast monthresolve on that zone's calendar. Without it the result is exactly the UTC one.context.refers_to[]on captures: the times the memory talks about, as local dates (YYYY-MM-DD) or date-times (YYYY-MM-DDTHH:MM[:SS], no offset) infrom,toorcandidates[], with azone(elsecontext.timezone) and aprecision(minute,hour,day,week,monthoryear). An ambiguous day word ("kal" is yesterday or tomorrow) keeps every reading incandidates. Invalid entries are422 INVALID_ENVELOPE.temporal.refers_during {from, to, zone?}on recall and answer is a boost, not a filter: the retrieved memories whoserefers_to(or, without one, capture time) falls in the window move to the front; nothing is added or removed. Date bounds compare calendar dates in each memory's own zone; a bound with a time compares instants. A malformed window is422 INVALID_BODY(details.field: "temporal.refers_during"). The trail records the boost (refers_during_boost), and on a small scope whose pack is the whole corpus in time order the order is left alone.temporal.valid_duringis documented as what it always was: a filter on capture time (observed_at).- A non-English event with a capture
context.timezonegets a local-time bracket after the UTC one in its context prefix ([2026-09-23 20:30 UTC] [2026/09/24 (Thu) 02:00 Asia/Kolkata] ...). English bytes are unchanged. /v1/answerdoes not return the packwarnings[], so a lenient note is not visible there.
Per-channel recall diagnostics (recall_diagnostics_v1)
diagnostics: "full" adds a side table: items[] (each returned event, then the ranked tail,
with its final and fused rank, its rank and score in every channel (vector, BM25, fact, semantic
fact), its rank in every variant, matched_via and reorder deltas), drops[], channel_lists
and items_truncated (at most 200 rows). summary and full still need diagnostics.read, and
none still serializes as {"time_ms":{}}. The layers, context_block and citations are the
same at every level.
Charset-safe text blobs (blob_text_decode_v1)
When a capture references a text blob, the bytes are decoded in this order: a UTF-16 byte-order
mark (stripped) or a UTF-8 one (kept, so existing blobs keep their write identity); a declared
charset=utf-16, utf-16le or utf-16be; strict UTF-8; then the declared charset= label.
Anything else fails the write with 422 BLOB_TEXT_ENCODING_UNKNOWN (retriable: false,
details {blob_id, content_type, reason, charset}); no path stores U+FFFD for bytes that did not
encode it. A UTF-32 byte-order mark, ISCII labels and a declared charset=utf-8 over invalid
bytes are refused. POST /v1/blobs itself is unchanged.
Entity ids and enrichment of non-Latin text
- Entity ids of names with any non-ASCII character are
ent_<ascii-base>-<16 hex>(for exampleent_jose-...) orent_u-<16 hex>, a hash of the normalized name (NFC, lowercase, invisible format and direction marks removed; Arabic diacritics too). v0.9.13 replaced every such character with_, so different people in a non-Latin script shared one id (ent_______). - Ids stored before (
ent_Jos_) still resolve onGET /v1/beliefs?about=, the fact ledger and/v1/forget. A legacy id that names more than one person resolves to nothing on a forget (loggedforget_entity_id_ambiguous), so forgetting one person never deletes another's facts; on?about=it returns every belief stored under it. CORTEX_ENRICHMENT_SKIP_NON_EN=1(opt-in) skips LLM enrichment for text containing letters of a non-Latin script; such an event stays searchable as a raw event. Latin-script Hinglish is always enriched.- A triple whose predicate normalizes to nothing or to non-ASCII is rejected and counted, never stored with an empty key.
- Counters:
cortexdb_enrichment_non_en_script_events_enriched_total,cortexdb_enrichment_non_en_script_events_skipped_totalandcortexdb_enrichment_triples_rejected_total{code=...}on/metrics, and the same values underenrichment_multilingualonGET /v1/admin/metrics.
Documents: matched fragments and chunk inspection
matched_fragments(matched_fragments_v1). The write path splits an event longer than one chunk (about 500 bytes) into overlapping chunks. Each chunk now records where its text lies in the stored text, and a recall item of a multi-chunk text event listsmatched_fragments[]: the chunks a retrieval channel returned for this recall, best first, at most 8, each{chunk_id, text_source, byte_range, locator?, language?, matched_via}.byte_rangere-slices the event's storedcontent.text(UTF-8 bytes);locatoris the section path;text_sourceisevent_textorconverted_text(a text blob the server decoded);matched_viaisvectororbm25. Order is the signal; later entries can be any chunk a channel returned. Thecontext_fragmentsprovenance entry names the chunkscontext_blockserved.- Events written before 0.10.1 have no fragments until they are rewritten or re-derived by an
index-audit repair; their items carry no
matched_fragments. GET /v1/admin/events/{id}/chunks(trusted operator plusdiagnostics.read, confined to the caller's tenant and the event's scope; an unknown, foreign or erased id is the same 404) shows how one event was chunked: each chunk's id, its index text (exactly what BM25 and the vector were built from), sizes, whether it has a vector, and its fragment (storedwith the byte range,implicitfor a single-chunk event, ornonefor an event written before).CORTEX_CHUNK_FRAGMENTS=0stops writing fragment rows (default on). Rows hold offsets only, never text, and are erased with their chunk.- Chunking itself is unchanged: the chunk bodies that are indexed are the same bytes as before, and a single-chunk event gets no fragment row and no response field.
Recall and answer context
Answer-context packing restored (the whole-event context fix)
Since v0.9.10 context_block is built from whole events, and the /v1/answer context packer had
not been adjusted to that: derived fact rows behind a timestamp were ranked as structure and cut
first, assistant replies carrying an echo of the user turn were counted as user evidence, and one
oversized event ended the context. 0.10.1 fixes all three. Dialogue with named speakers
("Caroline: ...") ranks level with fact rows, and in a congested multi-topic answer each topic's
focused dialogue and fact rows are packed before the rest. Each fix has a switch:
| Switch | Default | Effect | Pre-fix value |
|---|---|---|---|
CORTEX_EVIDENCE_ROLE_CLASSIFIER | packer | Timestamp- and role-aware evidence classifier for the answer packer and the multi-session preamble (full: every consumer) | legacy |
CORTEX_CONTEXT_STRIP_ECHO | paired | Drop an assistant turn's In reply to user: echo when the quoted user turn is rendered in the same context (all: always) | 0 |
CORTEX_CONTEXT_RENDER_SKIP_OVERSIZED | on | Skip an event that does not fit and keep packing (affects every ranked /v1/recall) | 0 |
CORTEX_FACET_PACK_POLICY | tiered | In a byte-congested merge of the primary and facet recalls, focused dialogue and fact rows first | legacy |
CORTEX_CONTEXT_ASSISTANT_EXCERPTS | off | Opt-in: long assistant turns render only their matched chunk spans (CORTEX_CONTEXT_ASSISTANT_EXCERPT_MAX_BYTES, default 600) | 0 |
CORTEX_ABSTAIN_SELF_RESCUE | legacy pass | The abstention self-rescue; guarded (opt-in) skips it when the abstention denies the question's premise; 0 never rescues | unset |
CORTEX_RECALL_BUDGET_EXCERPTS | on | New in 0.10.1: budget excerpts, below | 0 |
Setting every value in the last column restores v0.9.13's packing for these stages
(PRODUCTION_CONFIG.md, "Answer context", has the line to paste).
Budget excerpts (budget_excerpt_v1, new in 0.10.1)
The render places whole events in rank order while they fit, as before, and skips one that does not. Then each event it left out gets the room still free at its rank position: whole, if an echo strip freed enough room since, otherwise as an excerpt.
- The excerpt is built from the event's best-matching chunks (the fragments behind
matched_fragments, plus any chunk a channel returned), taken best first while they fit and served in document order. An event with no fragment rows (written before 0.10.1) gets its head, cut at a character boundary. - The slices are verbatim text joined by
\n[…]\n, led by[…]\nwhen text before the first slice is left out and closed by\n[…]when text after the last one is. Echo stripping does not apply inside an excerpt. - An excerpt needs at least 512 bytes of room unless nothing else was placed.
- The excerpt is the same text in
context_blockand in the event'slayers.eventsitem, whosecontentkeeps itskindandroleand becomes a partial view:
"content": {
"kind": "message",
"role": "assistant",
"text": "[…]\nSection 3: …\n[…]\nSection 40: …\n[…]",
"_partial": true,
"_partial_reason": "budget_excerpt",
"_original_bytes": 32768,
"_excerpt_ranges": [{"start": 775, "end": 1015}, {"start": 9692, "end": 9932}]
}
_original_bytesis the stored text's byte length;_excerpt_rangesare the half-open byte ranges of the stored text the excerpt carries, in document order. The knapsack charges the excerpt, not the whole event.matched_fragmentsbyte ranges keep indexing the stored text, andcontext_fragmentsnames the chunks the excerpt's exact ranges cover. The stored event is untouched.- The holistic
/v1/recallrendering (the# Memory contextwrapper) cuts its first evidence entry to the room its synthesized sections leave, closed by\n[…], and reports the coverage as partial. - For the whole text, read
GET /v1/events/{id}or raisebudgets.max_tokens. The default recall budget is 4,000 tokens (about 14,000 bytes of context;CORTEX_RECALL_DEFAULT_MAX_TOKENSchanges it).
Measured on test shapes (deterministic mock embeddings, the build before this change against 0.10.1):
| Shape | Before | 0.10.1 |
|---|---|---|
One 8 KB document, max_tokens: 2000 (7,000-byte budget) | 8,227-byte block, empty events layer | 6,874 bytes; a 6,839-byte excerpt in the layer |
One 32 KB document, max_tokens: 1000 | 32,803 bytes (9.4 times the budget) | 3,493 bytes |
Mixed pack whose 8 KB answer did not fit, max_tokens: 2000 | 188 bytes, answer missing | 6,620 bytes, answer as a 6,390-byte excerpt |
One 9 KB Devanagari document, max_tokens: 2000 | 9,303 bytes | 6,943 bytes |
With CORTEX_RECALL_BUDGET_EXCERPTS=0, 23 of 23 recalls over one store were byte-identical to
the build before; with the default, the 11 that differed were exactly the packs with an
oversized event and at least 512 bytes of room.
Every event in the context is identifiable (the context receipt, new in 0.10.1)
- The cross-layer knapsack now evicts the events
context_blockdoes not render before any it renders. When the render is a rank prefix this is the same as before; when an oversized event was skipped mid-list, that event goes before the smaller rendered ones behind it. - The provenance entry
{"phase": "context_contributors", "context_contributors": [{"event_id", "rank"}, ...]}names the eventscontext_blockrenders, after the ranked candidates and any recovered user turn. A rendered event the knapsack evicted fromlayers.eventscarriesevicted_from_layers: true. Those rows are outside the 128-row cap, one per evicted event, so an exhaustive ororder: "recency"block with a small explicitmax_tokenscan carry hundreds. rankhas two meanings in one list: on the rows v0.9.13 already wrote (the ranked candidates,recovered_user_turn) it is the retrieval rank; on the added rows it is the event's position in the events layer before the knapsack.- On a 12-turn test conversation with one 10.6 KB reply at
max_tokens: 2000, the build before this change left 2 to 10 of the block's events (depending on the query) unnamed anywhere in the response; 0.10.1 names all of them. budgets.max_tokens(API reference §8.3 and §9.2) is documented as the code applies it: layers named inincludeare funded first, inincludeorder, then facts, beliefs, episodes, understanding and events, and within the events layer the events the block does not render leave first.CORTEX_CROSS_LAYER_KNAPSACK=0turns the knapsack off.
/v1/answer merges excerpts across its recall passes (new in 0.10.1)
/v1/answer merges the contexts of several recall passes run at different budgets (primary,
supplemental, adaptive, fact-join and facet passes), so one pass can serve an event as an excerpt
while another serves it whole. The answer context treats the two as one event: the whole text is
kept once, in the excerpt's place when the excerpt came first, and the excerpt is dropped.
Excerpts of the composer and of CORTEX_CONTEXT_ASSISTANT_EXCERPTS, which carry the same […]
mark, are not budget excerpts and merge as before.
/v1/answer and /v1/compose diagnostics and abstentions
diagnostics.degraded_sourcesnames what the answer ran without, as stable codes only, never error text:coordinator_recall,wal_supplement,derived_<lane>(for examplederived_facts),artifact_search,index_divergence,derived_backfill,facet_plan_unavailable,facet_recall_failed,ms_executor_fallback, elseunknown. The answer is still returned; read a non-empty list as "this answer saw less evidence than a healthy run would". The error stays in the server log.- "No such information" answers are detected as abstentions; general nouns ("issue", "problem")
are no longer treated as unsupported premises. New diagnostics:
premise_unsupported_terms(question words with no literal support in the context) andextractive_retry_outcome(answered,still_abstainedorpremise_ungrounded). - A
/v1/answerrequest carrying the evaluation-onlyfacet_plan_overrideis refused with422 EVAL_OVERRIDE_DISABLEDunless the server runs withCORTEX_EVAL_FACET_PLAN_OVERRIDE=1. Never set aCORTEX_EVAL_variable on a production server.
Regression checks
These are regression checks with answer-free replays and a blind benchmark harness, not published scores.
- LongMemEval-S, full 500 questions, blind server harness, enrichment off: the multilingual
work passed a non-inferiority check against our previous baseline run under the same
configuration. That run predates the
/v1/answerabstention changes above and 0.10.1's context changes; the context changes were checked with the replays below. - 0.10.1's context changes, answer-free replays (embeddings only, no answer model) against the
build before them: on LongMemEval-S (all 500) the contexts hold the same gold evidence in every
question type (a repeat run moved single turns both ways, the replay's own noise), and 21 of the
500 contexts carry an excerpt, none next to its own whole text. On a conversation store with
one event per session, where excerpts fire most, the contexts hold more gold turns than before
in every question category (most of the added turns are inside excerpts); six turns that the
previous contexts reached only in their last, already truncated session no longer fit (see
"Known issues"). With
CORTEX_RECALL_BUDGET_EXCERPTS=0the contexts are the previous ones.
Facts and beliefs (enrichment-on deployments)
Nothing in this section runs unless enrichment is on (CORTEX_ENRICHMENT_DELAY_SECONDS set).
With enrichment off (the default) there are no facts or beliefs, and English recall packs are
byte-identical to v0.9.13's by our regression gate. The event log is never rewritten: every
change below is derived output.
Negation, cessation and contradiction
- Extraction marks every claim
affirmative,negatedorceased(stored as the fact'spolarity), with a deterministic English fallback (not,never,n't,no longer,... anymore) for claims the model left untagged. - A denial never retires the claim it denies. An affirmative and a denial of the same claim
are a contradiction: neither supersedes, one durable conflict row is opened with both facts as
members, and the belief is
Contradictedwith the denial countedAgainst. - A denial of another value never retires anything, on any relation: "Meera doesn't eat
non-veg" no longer supersedes "Meera eats fish", and
works_at | not Globexno longer retiresworks_at | Acme. - A cessation is an update. "Priya no longer lives in Delhi", "doesn't work at Acme anymore" and "isn't working there any more" supersede the earlier claim with no conflict; a claim made again after its cessation supersedes the cessation.
- A restated value supersedes nothing. Two facts stating the same value are two Supports.
- Do-support denials fold to the affirmative predicate (
did_not_go_togroups withwent_to). - A claim whose polarity is in doubt gets one short verifier call (below).
One subject per supersession
- The consolidator no longer merges facts about different people (Priya / Riya, Amit / Amita):
duplicates,staleandresolvedverdicts between facts whose named entities differ are dropped (code=consolidation_guard_names). - Neither the write-time resolver nor the consolidator retires a fact with one about another subject: two triples need the same subject (letter case and a leading article aside), and two sentence facts that lead with different names ("The service centre in Chennai is open ..." / "... in Hyderabad ...") are two subjects however alike the rest.
- This changes updates stated only as sentences: a sentence whose subject is not a name ("User
lives in Boston", "I ...", "The order ships from Pune") and whose value is a proper noun no
longer supersedes the earlier sentence; both stay live. An update extracted as a triple
(
lives_in | Denver) still supersedes.
Stable belief keys and deterministic ids
- Facts and beliefs share one topic key,
(subject, predicate), used byPOST /v1/beliefs/build, the incremental builder, the fact store and the resolver alike. Subject and predicate go through a versioned Unicode fold (NFC, lowercase; on ASCII exactly the old key); the predicate is the built-in ontology's canonical form, soemployed atgroups withworks_at.intends_toand the future forms becomeplans_to, the diet variantsdiet,flight_atflight_time. - Every fact-derived belief id is derived from
(scope, topic): the same facts in any order give the same beliefs and ids.POST /v1/beliefs/buildused to mint new random ids on every call. - Beliefs carry
claim_keybesideclaim({subject, predicate, value, polarity, rules_version}), the form the belief groups and compares on.claimkeeps the latest supporting fact's own words. - A multi-valued relation (
likes,owns,plans_to, ...) gets one belief per value; synonyms of a multi-valued relation ("likes tea", "enjoys hiking") never supersede each other. - Place exonyms are one value (Dilli/Delhi, Bombay/Mumbai, Bangalore/Bengaluru, Calcutta/Kolkata, Madras/Chennai), for comparison only; the stored text is never rewritten.
/v1/conflictsitems carryconfidence: avalue_disagreementwhose values are within edit distance 2 of each other ("Jon" / "John") opens at0.5. A later stated update of an observed value is a correction, not atemporal_overlapconflict.- The per-person
disposition_profilerollup needs two distinct dispositions. - A cross-language restatement stays two Supports: a consolidator
duplicatesverdict between a Hinglish and an English fact is not applied (code=consolidation_cross_language_kept). - API reference §7.4.2 has every rule.
Extraction reads the original text
- Original wording. Facts keep subjects, objects and text in the source script; predicates
are English snake_case. Every triple and fact asks for a
quote, kept only when it is a byte-exact span of the capture's own text (quotes_rejected_not_in_sourcecounts the rest). - English glosses are keys only. A non-English fact gets a derived English
text_en/object_engloss, stored in the fact's metadata and as one key-only search row that recall resolves to the fact. The gloss never appears incontext_block, citations orlayers.events, and erasing the source removes it. - Dates. The extraction's date anchor is the capture's
observed_at(local wall time when the capture named acontext.timezone), not the write time. A caller'srefers_toentry is the date for its phrase. Each dated triple recordsdate_basis(refers_to,verbatim,extractororambiguous); an ambiguous day keeps every reading indate_candidatesand stamps neither on the fact. - Polarity re-check. A claim whose polarity a trigger doubts (a gloss that disagrees, a Hindi
negation cue in its clause the model ignored, an English quote that may deny it, a negated
antonym such as a negated
dislikes) gets one re-check call to the enrichment model, whose verdict is stored (polarity_check: rechecked). Without an answer the fact keeps the extractor's polarity (unchecked), or, for a gloss mismatch, is stored at confidence 0.6 (unresolved) so it cannot retire a confident fact. - Fact fields. Facts on recall
layers.factsand/v1/factsmay carrylanguage,polarity,quote,date_basis,date_candidatesandgloss {text_en?, object_en?, derived: true, model?, prompt_version?}. These are additive and optional;fact_surface_v1, which will promise them, is not listed yet. CORTEX_ENRICHMENT_SCOPE_QUIET_MS(opt-in, default off) holds a busy scope's captures until the scope has been quiet that long (at mostCORTEX_ENRICHMENT_SCOPE_QUIET_MAX_MS, default 30 s), so close captures share one extraction call, at the cost of capture-to-fact latency.
Extraction fixes in 0.10.1
Upgrade drills of the 0.10.0 candidate on enrichment-on test stores found three defects in the facts it wrote. They are fixed in 0.10.1:
- A negation stored twice. A denial the model wrote with an antonym ("does not want SMS
updates any more" as
dislikes | SMS updates, negated) is folded back to the claim it denies (wants | SMS updates, negated and ceased), including when it is phrased as a cessation, when an order number or a name puts object words in another clause, and for denial verbs the ontology does not map (rejects,refuses,declines,hates,lacks). "can't stand coffee" is a dislike. A negated triple whose object states its own denial ("wants | no delivery on Sunday") or an exclusion ("all purchases except gift card purchases") gets one verifier re-check, whose verdict is stored. An opener followed by a digit, the numero "No." or a capitalized name ("No. 45, 2nd Cross", "No Cost EMI", "Nothing Phone 2a") is not a denial at all, and a negated claim that also ceased ("I no longer live at No. 45") is never sent to that re-check: it keeps its negation and its cessation. - A predicate's negation dropped. An "affirmative" verdict on a predicate that carries its own
negation (
not_arrived | truefor "my order has not arrived yet") is stored negated; idioms whose negation asserts ("can't wait", "never misses", "don't mind") are left alone. - Supersession across subjects. The resolver's free-text keys and the consolidator's name guard both require one subject (above).
On an enrichment-on test set of about 720 captures, facts with a doubled negation went from 8 to
0, with a dropped negation from 39 to 0, and supersessions across subjects from 37 to 0; the
English regression slice and the Hinglish test set held. GET /v1/admin/metrics reports the
triggers under enrichment_multilingual.fact_surface (polarity_object_denial,
polarity_exclusion_denial, polarity_predicate_negation_kept, polarity_antonym_denial,
polarity_recheck_settled, _unresolved, _unchecked, _flipped and others).
Other derived-layer changes
- A single
POST /v1/experiencewithcontent.kind: "triple"writes its fact, as bulk always did;?wait=indexedreturns after the fact exists. A stored triple is always treated asextract: [], so the LLM extractor never sees it. directives.extractgate. Namingbeliefsorunderstandingnow runs the enrichment lane those stages depend on (they used to switch it off); naming onlyepisodesstill leaves it off, andextract: []still denies every stage. The directive selects work, not output: a lane that runs writes everything it produces, soextract: ["entities"]still yields facts.- More derived rows are erased with their capture: the extraction summary row, the facts'
question-key and gloss rows, each fact's own search rows (a forgotten fact's text could still
be served before), and with
CORTEX_ENTITY_GRAPH=1the capture's graph and resolver rows.
Spend
The extraction prompt is about 23% larger per English knowledge call (3,564 against 2,916 prompt
tokens measured), plus one extraction-model call per doubted polarity. CORTEX_POLARITY_RECHECK=0
records the doubt without the call; CORTEX_POLARITY_QUOTE_TRIGGER_EN=0 turns off only the
English quote trigger. Every call counts against CORTEX_ENRICHMENT_MAX_LLM_CALLS_PER_EVENT.
Price your models in CORTEX_MODEL_PRICES.
Erasure and forget
One cutoff for everything a request removes
A scope-wide forget and every /v1/erasures execute take one server cutoff and the event-log
head, once, before reading any event, and every pass selects by it: the event rows, the scope
tombstone, the content and raw-log pass, the artifact closure and the derived layers (a derived
row built only from captures after the cutoff survives). A capture the server stamped after the
cutoff is never removed. A capture stamped before it whose acknowledgement went out after it is
history the forget selected, and is removed. ?wait=indexed no longer reports a row a concurrent
forget blanked as 502 INDEXING_FAILED.
Honest counts and warnings
/v1/forgetreturnspurge(events,documents,edges,blobs,retained_shared_blobs,twins_redacted,bitemporal_records_erased,wal_rows_redacted,artifacts_cascaded), anddeleted.blobs/deleted.retained_shared_blobswhen non-zero. Every count is a transition this request made;deleted.eventson a scope-wideredact_eventsforget counts each event this request blanked, once.POST /v1/forget/previewcounts only events not yet blanked, counts entity-id and attribute selectors (about_entity,about_subject,predicate,ent_ids) with the execute's own match, and returnsrequestedandmatched.- Forget by entity id:
about_entity,about_subjectand anent_id inmemory_ids(undercascade: "derived_only") resolve the ids/v1/factsand/v1/beliefsshow, inside the request scope and its descendants only. - The
/v1/erasurespreview'sestimated_duration_msis 250 ms plus 80 ms per event, a planning figure.summary.deleted_eventsis the pinned count.
Blobs (erasure_blob_cascade_v1)
- An event made from an uploaded text blob, or a transcript sent in place of one, records the
link (
source_blob_ids, returned on/v1/events, recall items and export). Erasing the event deletes the blob bytes and retires its record unless another live event still links or references it;purge.blobsandpurge.retained_shared_blobsreport it, and the previews count the same blobs. Labels are never trusted as links. - Events written before 0.10.1 have no link. Run
POST /v1/admin/index-audit/source-blob-links(trusted operator; dry run by default, which needsdiagnostics.read; applying needsadmin.compact; unified log only, else409 UNIFIED_WAL_REQUIRED) once per billing tenant to backfill it from thesource_blob:labels. It links a label only when it names a live blob of the event's own tenant and caller, and reports the rest as unverifiable. Then readGET /v1/admin/index-audit/orphan-blobs(read-only,?limit=): the tenant's live blobs no surviving event links or references. A blob inlined before 0.10.1 and not backfilled is listed even while its event is live, so check each before deleting it withDELETE /v1/blobs/{id}(unchanged).
Bytes left in the files (the raw residual pass)
A delete in RocksDB or the full-text index only adds a newer version; the erased bytes stayed
readable in log files, older SST files and index segments until the engines compacted them.
After an erasure a throttled background pass now flushes the stores, compacts the erased keys
out of the content store, the event log and the session fragments, and merges the full-text
segments that still hold a deleted document. GET /v1/erasures/{id} reports
log_residual_cleared_at when the passes covering the job finished, or, while that is null on
a completed job, log_residual_pending with a reason:
reason | Meaning |
|---|---|
pass_pending | The pass has not finished the work this job recorded yet |
fulltext_segment_over_budget | A full-text segment larger than the per-pass budget still holds documents erased under an overlapping scope; it clears only once no segment can still hold them, and on a store whose segments exceed the budget it may never clear (it never clears falsely) |
not_covered | No pass covers this job: CORTEX_ERASURE_RESIDUAL=0, or the legacy dual WAL (CORTEX_UNIFIED_WAL=0) |
A graceful stop drains the pass for up to CORTEX_ERASURE_RESIDUAL_SHUTDOWN_SECS (20 s); what is
left is journaled and resumed at the next start. CORTEX_ERASURE_PERIODIC_COMPACTION_SECS sets a
periodic compaction backstop for rows a crash kept out of the pass. GET /v1/admin/metrics
reports the pass under erasure_residual. docs/ERASURE_COVERAGE.md lists every store that holds
text and what erasure reaches.
An erased fact stays erased after a crash
An erased fact id is recorded in a synced fence before it leaves memory, and every start drops fenced ids from the loaded fact snapshot, so a hard kill before the next snapshot save can no longer bring an erased fact (text included) back. A fence that cannot be read fails the start rather than serve erased facts.
Faster forgets
Redactions are written in batches with one sync per 256 rows, a forget makes one full-text commit, and discovery no longer decodes rows of other scopes. Captures into registered scopes, reads and health checks no longer wait behind a forget (a forget used to hold the scope registry while it ran). Measured forget-all of a 20-event scope under concurrent captures: median 167 ms, against 298 ms on v0.9.13; on a 100,000-event store 369 ms, against 2,471 ms on v0.9.12.
Erasure cost follows what is erased, not the store
A selective /v1/erasures, a /v1/forget by id (a repeat included) and a scope erasure no longer
read the whole store. The raw-log discovery, the blob survival proof, the preceded_by /
supports: scrub and the idempotency cleanup read an erasure index kept in the same write as each
event-log row; a vector delete no longer rescans its whole index shard; and /v1/erasures deletes
its rows in synced batches of 256. Measured on copies of one store under a background load of 20
captures/s and 10 recalls/s, the same release without the index against 0.10.1:
| Request | 1M events, without | 1M, 0.10.1 | 5M events, without | 5M, 0.10.1 |
|---|---|---|---|---|
/v1/forget by id, large scope | 12.2-13.0 s | 0.04-0.37 s | 64.0-66.8 s | 0.09-0.27 s |
| the same forget repeated | 4.4 s | 6 ms | 23.4 s | 90 ms |
/v1/forget, 100 ids | 14.0 s | 0.21 s | 71.2 s | 0.57 s |
/v1/erasures, 1 id | 12.7-13.5 s | 0.05-0.12 s | 67.6-71.1 s | 0.05-0.07 s |
/v1/erasures, 1,000 ids | 25.2 s | 0.64 s | 105.0 s | 2.3 s |
/v1/erasures, 1 blob-backed id | 27.7-27.9 s | 0.06-0.14 s | 137.5-140.1 s | 0.07-0.11 s |
/v1/forget scope-wide, small scope | 21.8-22.0 s | 0.44-0.47 s | 103.5-179.5 s | 2.5-2.6 s |
/v1/erasures whole scope, small scope | 33.3 s | 0.56 s | 175.3 s | 2.8 s |
The first start backfills the index (see "Plan the first start"); until erasure_index.wal.ready
is true on GET /v1/admin/metrics, erasures read the whole store as before. What still grows
with the store: a scope erasure's pass over the content store (0.28 s at 1M events, 1.4 s at 5M),
the bi-temporal closure when bi-temporal records are erased, and, on the legacy dual WAL, every
scan the index replaced. CORTEX_ERASURE_INDEX=0 keeps the index maintained but unread (an A/B
switch and escape hatch); CORTEX_ERASURE_INDEX_BACKFILL=0 skips the backfill.
docs/ERASURE_COVERAGE.md ("What an erasure reads") has the full table.
Concurrency and shutdown
- While a forget or erasure runs it holds a lease on its scope footprint. A capture that would
register a new scope inside it waits up to
CORTEX_DESTRUCTIVE_LEASE_WAIT_SECS(default 120) and then gets409 DESTRUCTIVE_OPERATION_IN_PROGRESS(Retry-After: 5,details {scopes, retry_after_seconds, admission_wait_seconds}); nothing is written./v1/scopesregister, member edit and delete inside the footprint get409 SCOPE_STATE_CHANGED. Captures into existing scopes and everything outside the footprint never wait. - A forget or erasure runs detached from the HTTP request, so a client disconnect cannot abort it or its audit row.
- On SIGTERM or SIGINT the server refuses new
/v1/forgetand/v1/erasuresrequests with503 SERVER_SHUTTING_DOWN(Retry-After: 5) and waits up toCORTEX_SHUTDOWN_DESTRUCTIVE_WAIT_SECS(default 60;0skips the wait) for the ones in flight before the final snapshot persist.
Repairing derived data written by earlier builds
Enrichment-on stores written by v0.9.13 and earlier can hold facts with a lost negation, facts of different people merged, names in two scripts merged, several non-Latin names on one colliding entity id, and beliefs counting facts of the opposite polarity. 0.10.1 stops new damage; these trusted-operator routes repair what is already stored, from the event log, without wipes and without re-ingesting anything:
POST /v1/admin/derived/rederive(admin.compact; dry run by default). Takes ascopeand an audit report fromscripts/derived_corruption_audit.py, orevent_ids,fact_idsor exact supersessionedges;billing_tenant(default the caller's; captures written with minted tokens bill to their own tenant, and a job skips other tenants' events);actions(any ofrevert_supersessions,purge_derived,reenrich,reconsolidate,rebuild_beliefs; default all five);max_events(default 200, at most 2,000);job_id;cold_backup_ack; andenrichment_wait_secs(default 600, at most 3,600). The dry run returns the whole plan (each event's eligibility, every supersession edge considered, what the purge deletes, what it keeps, warnings, and a cost estimate priced fromCORTEX_MODEL_PRICES) and changes nothing. An apply answers202with the job record and aLocationheader. A job resumes from its last completed phase when the same body is posted again with itsjob_id, andcontinue_fromtakes the events amax_eventscut left (once per job:409 JOB_ALREADY_CONTINUEDwithdetails.continued_by). A colliding legacy entity id (ent_______) reaches the facts of every subject it names; the plan lists them inambiguous_entity_idswith a warning.GET /v1/admin/derived/rederive/{job_id}(diagnostics.read) reads a job; a job another caller created is a 404.POST /v1/admin/derived/resolver/unmerge(admin.compact; dry run by default) splits names the entity resolver merged at edit distance 1-2 (Priya into Riya).
An apply reverts the flagged supersessions, purges the derived rows the targeted events alone
support, re-extracts those events with 0.10.1's extraction, reconsolidates the affected entity
ledgers and rebuilds only the affected belief topics. Episodes, caller-declared beliefs and
untouched topics are kept. Concepts it purges are re-synthesized by the runbook's step 6 with
POST /v1/understanding/synthesize. Every request writes a DERIVED_REDERIVE:<decision> audit
row. A job refuses to start next to a running forget or erasure, and a forget or erasure under a
running job's scope root answers 409 REDERIVE_IN_PROGRESS (Retry-After: 30).
scripts/derived_corruption_audit.py (tool version 2) is read-only and uses GET routes only. It
reports negation_lost, negation_introduced_by_supersession (informational),
cross_name_merges, lossy_entity_ids and colliding_entity_ids, belief_polarity_mismatch,
and since tool version 2 negation_doubled and negation_dropped; it compares subject ids
ignoring letter case, separators and a leading article.
Follow docs/DERIVED_REDERIVE_RUNBOOK.md step by step: audit (on v0.9.13, before the upgrade),
quiesce ingestion, cold backup, dry runs, apply, re-audit, re-synthesize concepts, resume. It
also says what the audit cannot see (a denial an older extractor stored as an affirmative, which
superseded another value) and how to check for it by hand, and which re-audit findings are
expected after a repair (the "Known issues" below, and doubled negations the verifier-gated rules
still write when the verifier gives no answer: re-run the job on those once it is healthy).
Rollback floor. Upgrading and dry runs leave the store openable by v0.9.13. An apply does not: v0.9.13 cannot read the repair's log rows and would bring the reverted supersessions back the first time it rebuilds its fact store. After an apply, going back to v0.9.13 means restoring the cold backup taken before it (see "Rollback").
Code plane, tokens, console and usage
- Code plane (
CORTEX_CODE_PLANE, off by default; an English-only plane, so the startup warnsENGLISH_ONLY_PLANE_ENABLEDwhen it is on):- A repository whose scope no roster governs is never served to a per-actor token (404, left out
of listings). Registering one provisions an unregistered team scope with the caller as Owner;
a root is never claimed (
403 SCOPE_REGISTRATION_REQUIRED). Recall, answer and compose code evidence re-check every repository against the current caller. - Credential files are never read; plain directories index only files of a registered grammar;
/v1/code/contextserves only indexed, hash-verified files. Scans are bounded (entries, depth, bytes, wall clock) and never walk pseudo-filesystems. CORTEX_CODE_REPO_ROOTSlimits where repositories may be registered (the request path is canonicalized and must lie inside a root; a value with no usable root refuses every registration). The filesystem root,/proc,/sys,/dev,/run,/boot,/etcand the data dir are always refused (422 REPO_PATH_NOT_ALLOWED,details.reason).- New bindings are named after the repository (
<parent>/repo:<name>-<12 hex>); a binding another repository already holds is refused (409 REPO_SCOPE_IN_USE). - Call graphs are more accurate for C and C++; SCIP imports derive compiler-tier call edges;
certify takes a
pathsfilter (400 INVALID_PATHSfor a bad one); context returns the snapshot generation.docs/CODE_PLANE.mdships in the image.
- A repository whose scope no roster governs is never served to a per-actor token (404, left out
of listings). Registering one provisions an unregistered team scope with the caller as Owner;
a root is never claimed (
- Tokens.
POST /v1/auth/tokensacceptscapabilitiesas an alias ofcapsand validatesscopesentries. - Admin console. Confirm dialogs act on OK;
events_storedcounts each memory once (the figure halves on unified-log stores); settings show unwired lanes as not configured; the update check reads GitHub Releases, is cached, and can be turned off (CORTEX_UPDATE_FEED_URL=off, or pointed at your own mirror); the changelog lists every bundled release, newest first; derivation status reports off when it is off; the boot config dump marks values that came from an admin-UI override; not-configured503s no longer count inerrors_total(they have their ownnot_configured_total); a console restart exits with code 75 (EX_TEMPFAIL), so run the server under a supervisor that restarts on a non-zero exit. - Usage ledger. Streamed answers record their input tokens (they were booked at input 0), and
the streamed diagnostics frame omits token counts it does not know instead of sending
null. - Sample LiteLLM gateway.
deploy/litellm/config.yamlno longer caches embeddings: LiteLLM 1.93.0 answers a partly cached embedding batch with duplicate indices, which the server rejects, so one cache hit failed a whole/v1/experience/bulkbatch. Removeembeddingandaembeddingfromsupported_call_typesin any copy of that config you run. - Tools.
scripts/cold_backup.py restorecreates the target's parent directory.
Operator and admin routes
New routes (all documented in docs/schemas/openapi.json):
| Route | Access | Purpose |
|---|---|---|
POST /v1/admin/derived/rederive | trusted operator, admin.compact | Plan (dry run, default) or run a re-derivation job |
GET /v1/admin/derived/rederive/{job_id} | trusted operator, diagnostics.read | Read a re-derivation job |
POST /v1/admin/derived/resolver/unmerge | trusted operator, admin.compact | Report (dry run, default) or split names the resolver merged |
GET /v1/admin/events/{id}/chunks | trusted operator, diagnostics.read | How one event was chunked, with fragment offsets |
POST /v1/admin/index-audit/source-blob-links | trusted operator, diagnostics.read (dry run) or admin.compact (apply) | Backfill source_blob_ids on events written before 0.10.1 |
GET /v1/admin/index-audit/orphan-blobs | trusted operator, diagnostics.read | Live blobs no surviving event links or references |
Changed routes:
GET /v1/admin/version:capabilities[].GET /v1/admin/metrics:erasure_index(wal.ready,wal.backfill_complete,wal.catchup_pending_offsets,wal.rows_indexed,retry_candidates_ready,retry_candidates, and thediscovery_full_scans/survival_full_scansstill taken),erasure_residual(pending keys, parked segments, the journal, the last pass) andenrichment_multilingual(including thefact_surfacecounters).GET /v1/admin/layers/stats:belief_reconcile(seeded_topics,dirty_scopes,dirty_topics,tick_in_progress,max_topics_per_scope_per_tick,done)./metrics: the three multilingual enrichment counter families.GET /v1/erasures/{id}(now documented):log_residual_cleared_at,log_residual_pending,summary.purge.blobs,summary.purge.retained_shared_blobs.GET /v1/conflicts(now documented):confidence.
Environment variables
Every variable in the table is new since v0.9.13. docs/generated/env-vars.md has the full rows,
and PRODUCTION_CONFIG.md the operator-facing ones. Leave the defaults unless a row says
otherwise.
| Variable | Default | What it does |
|---|---|---|
CORTEX_RECALL_BUDGET_EXCERPTS | on | Budget excerpts and the strict budget (0, off or false restores the pre-0.10.1 render) |
CORTEX_EVIDENCE_ROLE_CLASSIFIER | packer | Answer-context evidence classifier (full, legacy) |
CORTEX_CONTEXT_STRIP_ECHO | paired | Echo strip (all, 0) |
CORTEX_CONTEXT_RENDER_SKIP_OVERSIZED | on | Skip an oversized event and keep packing |
CORTEX_FACET_PACK_POLICY | tiered | Facet merge policy (legacy; byte_share, tiered_byte_share, tiered_facts_late for evaluation) |
CORTEX_CONTEXT_ASSISTANT_EXCERPTS | off | Opt-in assistant-turn excerpts (1, ms) |
CORTEX_CONTEXT_ASSISTANT_EXCERPT_MAX_BYTES | 600 | Per-event cap of those excerpts |
CORTEX_QUERY_VARIANTS_FUSION | interleave | Variant fusion (evaluation: depth_cap, deep_rrf) |
CORTEX_QUERY_VARIANTS_DEPTH | 10 | Depth for depth_cap (evaluation) |
CORTEX_EMBEDDING_CACHE_DISABLE | unset | Measurement only: bypass the provider embedding cache |
CORTEX_CHUNK_FRAGMENTS | on | Write chunk fragment rows |
CORTEX_ENRICHMENT_SKIP_NON_EN | off | Skip LLM enrichment for non-Latin-script text |
CORTEX_POLARITY_RECHECK | on | The polarity re-check call (0 records the doubt without a call) |
CORTEX_POLARITY_QUOTE_TRIGGER_EN | on | The English quote trigger of that re-check |
CORTEX_ENRICHMENT_SCOPE_QUIET_MS | 0 (off) | Hold a busy scope's captures for one batched extraction (at most 60000) |
CORTEX_ENRICHMENT_SCOPE_QUIET_MAX_MS | 30000 | Longest hold of that window |
CORTEX_DESTRUCTIVE_LEASE_WAIT_SECS | 120 | How long a capture waits for a forget's or erasure's lease (1 to 3600) |
CORTEX_SHUTDOWN_DESTRUCTIVE_WAIT_SECS | 60 | How long a graceful stop waits for running forgets and erasures (0 to 3600) |
CORTEX_ERASURE_RESIDUAL | on | The raw residual pass (0 off; a startup warning says so) |
CORTEX_ERASURE_RESIDUAL_SHUTDOWN_SECS | 20 | Residual drain at a graceful stop |
CORTEX_ERASURE_RESIDUAL_MAX_BYTES | 536870912 | SST bytes one pass may rewrite per store |
CORTEX_ERASURE_RESIDUAL_FULLTEXT_MAX_DOCS | 1000000 | Largest full-text segment one pass merges |
CORTEX_ERASURE_RESIDUAL_MAX_PENDING_KEYS | 131072 | Recorded keys waiting per store |
CORTEX_ERASURE_RESIDUAL_MIN_INTERVAL_MS | 30000 | Minimum gap between passes |
CORTEX_ERASURE_RESIDUAL_DEBOUNCE_MS | 1000 | Quiet time before a pass starts |
CORTEX_ERASURE_RESIDUAL_PARKED_RECHECK_SECS | 900 | Recheck interval of a parked over-budget segment |
CORTEX_ERASURE_PERIODIC_COMPACTION_SECS | unset | Periodic compaction backstop for the stores (for example 86400) |
CORTEX_ERASURE_INDEX | on | Read the erasure index (0: maintained, never read) |
CORTEX_ERASURE_INDEX_BACKFILL | on | Backfill the index on a store written before it |
CORTEX_ERASURE_INDEX_BACKFILL_ROWS | 20000 | Rows per backfill step |
CORTEX_ERASURE_INDEX_BACKFILL_PAUSE_MS | 20 | Pause between backfill steps |
CORTEX_CODE_REPO_ROOTS | unset (startup warning) | Directories code-plane repositories may be registered from |
CORTEX_EVAL_FACET_PLAN_OVERRIDE | off | Evaluation only; never in production |
CORTEX_EVAL_DUMP_ANSWER_POOLS | off | Evaluation only (writes customer content to disk); never in production |
Changed: CORTEX_UPDATE_FEED_URL now defaults to the GitHub Releases API of the repository
releases are published to, and accepts off (also 0, false, none, disabled) to turn the
admin console's update check off (TELEMETRY.md, "Update check"); the check is cached for six
hours (fifteen minutes after a failure) and sends no install id, key or usage data.
CORTEX_ABSTAIN_SELF_RESCUE gains the opt-in value guarded; unset or 1 is the previous
pass and 0 still turns it off. CORTEX_QUERY_EMBED_CACHE_CAP (default 512) is unchanged and
now documented.
Error codes
New codes, and existing codes with new triggers:
| Status | Code | Where | Retriable |
|---|---|---|---|
| 422 | INVALID_BODY | A request field whose capability this server does not list (details.unsupported_fields, details.required_capabilities); a malformed recall language, scripts, query_variants, temporal.timezone or temporal.refers_during (details.field) | no |
| 422 | INVALID_ENVELOPE | A malformed capture language, scripts, context.timezone or context.refers_to | no |
| 422 | BLOB_TEXT_ENCODING_UNKNOWN | A capture referencing a text blob whose bytes are not valid in any declared or detectable encoding | no |
| 409 | DESTRUCTIVE_OPERATION_IN_PROGRESS | /v1/experience, /v1/experience/bulk: a new scope inside a running forget's or erasure's footprint, after CORTEX_DESTRUCTIVE_LEASE_WAIT_SECS; a re-derivation job next to one | yes (Retry-After: 5) |
| 409 | SCOPE_STATE_CHANGED | /v1/scopes register, member edit and delete inside that footprint; a capture whose lease wait ended just as the lease was released | yes |
| 503 | SERVER_SHUTTING_DOWN | /v1/forget, /v1/erasures after a graceful stop began; nothing was changed | yes (Retry-After: 5, against the restarted server) |
| 409 | REDERIVE_IN_PROGRESS | A forget, an erasure, their previews or a second re-derivation on a scope root a re-derivation job holds | yes (Retry-After: 30) |
| 409 | ENRICHMENT_OFF | POST /v1/admin/derived/rederive with enrichment off | no |
| 409 | JOB_MISMATCH | A re-derivation job_id reused for other targets, actions or billing tenant | no |
| 409 | JOB_NOT_COMPLETED | continue_from names a job that has not completed | no |
| 409 | JOB_ALREADY_CONTINUED | continue_from names a job another job already continued (details.continued_by) | no |
| 404 | JOB_NOT_FOUND | continue_from names an unknown or foreign job | no |
| 422 | COLD_BACKUP_ACK_REQUIRED | A re-derivation apply with purge_derived and no cold_backup_ack: true | no |
| 422 | NO_ELIGIBLE_EVENTS | A re-derivation apply that would purge or re-enrich, with no eligible event; nothing changed | no |
| 422 | NO_TARGETS | A re-derivation or unmerge request that names nothing to repair, or a continue_from job that left nothing | no |
| 422 | INVALID_AUDIT | A re-derivation or unmerge audit report that cannot be read | no |
| 503 | REDERIVE_UNSUPPORTED | The coordinator cannot run re-derivation | no |
| 500 | REDERIVE_FAILED, REDERIVE_PLAN_FAILED, UNMERGE_FAILED | Planning or preparing a re-derivation or an unmerge failed (the message says why) | after the cause is fixed; a job with a record resumes with its job_id |
| 409 | UNIFIED_WAL_REQUIRED | source-blob-links on the legacy dual WAL | no |
| 500 | SOURCE_BLOB_LINKS_FAILED, ORPHAN_BLOB_REPORT_FAILED, CHUNK_INSPECTION_FAILED | Failures of the new operator routes | yes |
| 422 | EVAL_OVERRIDE_DISABLED | /v1/answer with facet_plan_override on a server without the evaluation flag | no |
| 403 | SCOPE_REGISTRATION_REQUIRED | Code plane: registering a repository directly under a root scope | no |
| 409 | REPO_SCOPE_IN_USE | Code plane: the binding another repository already holds | no |
| 422 | REPO_PATH_NOT_ALLOWED | Code plane: a path outside CORTEX_CODE_REPO_ROOTS or always refused (details.reason) | no |
| 400 | INVALID_PATHS | Code plane: a malformed certify paths filter | no |
Codes no longer returned in these cases: 403 POLICY_DENIED for a capture that waited on a running
forget (now 409 DESTRUCTIVE_OPERATION_IN_PROGRESS); 502 FORGET_BACKEND_FAILED for a forget of
another scope's event id (now 200, every count 0); 502 INDEXING_FAILED for a wait=indexed
capture a concurrent forget blanked.
Startup warnings
Advisory log lines, never a refusal; the defaults log none of them:
ENGLISH_ONLY_PLANE_ENABLED:CORTEX_ARTIFACT_DUAL_WRITE,CORTEX_ARTIFACT_ROUTES,CORTEX_SUMMARY_ROUTESorCORTEX_CODE_PLANEis on; those planes use English-only cues.DOCUMENT_PROVIDER_FORGET_RESIDUE: a server document processor is configured; a forget by id leaves its whole-document derived text behind.DOCUMENT_PROVIDER_NATIVE_DEAD_LETTERS:CORTEX_DOCUMENT_PROVIDER=nativeyields no usable text for PDF, DOCX and the like.ENTITY_GRAPH_ENRICHMENT_FALSE_MERGE: the entity graph is on with enrichment; the graph resolver can merge near-identical names.ENRICHMENT_RESOLVER_ERASURE_RESIDUE: enrichment on with the graph off; an erased capture's entity names stay in the resolver's files.ERASURE_RESIDUAL_DISABLED,ERASURE_RESIDUAL_LEGACY_WAL: the raw residual pass is off, or cannot cover the legacy dual WAL.
Rollback
A v0.9.13 binary opens and serves a data dir 0.10.1 has used: no column family was added (the
erasure index, the fact fence, the re-derivation job records and the fragment rows live in
existing families, under keys earlier binaries never read), and 0.10.1's context changes store
nothing. Stop 0.10.1 with a 120 s grace, start the kept v0.9.13 artifact on the same data dir,
and set the 0.10.1 container's restart policy to no first. Three things end or limit a
rollback:
- After a derived-data repair apply, roll back only by restoring the cold backup taken before it. Restore into a new directory and start v0.9.13 on that one; keep the upgraded directory, and make sure nothing starts either build on it again. Captures and forgets served after the backup must be sent again.
- During a rollback window v0.9.13 erases the way it always did: it deletes no blobs, leaves
the raw residual, ignores the erased-fact fence (a crash can bring back a fact 0.10.1 erased),
records no fence for its own erasures, builds beliefs without claim keys, and reads the new
event fields (
language,scripts,context.timezone,context.refers_to,source_blob_ids) as absent; a row it rewrites in place (a redaction) may lose them. Clients gating oncapabilities[]stop sending the new fields on their own. - Entity-id lookups on v0.9.13 come back only after a belief rebuild. 0.10.1 rebuilds beliefs
under its own entity ids, so after rolling back,
GET /v1/beliefs?about=ent_...with a v0.9.13 id (such asent_Jos_) returns nothing on v0.9.13 until you runPOST /v1/beliefs/buildfor each scope there. That rebuild uses v0.9.13's lossy ids again. Do not rely onent_ids cached across a rollback.
After upgrading again
- Delete the orphan blobs of events erased during the window
(
GET /v1/admin/index-audit/orphan-blobs, thenDELETE /v1/blobs/{id}; check each first, because the report also lists blobs of live events written before 0.10.1), and setCORTEX_ERASURE_PERIODIC_COMPACTION_SECSfor a while to reach the raw residual. - Facts: a fact 0.10.1 erased before the rollback is dropped again at the start. A fact that
v0.9.13 erased or forgot during the window and that a v0.9.13 crash brought back is not:
it is still served. Keep, for every forget and erasure you send during a rollback window, its
body, the time you sent it and its response; the v1 audit log (
CORTEX_V1_AUDIT_PATH) records each request's endpoint, actor, time, status and a peppered body hash, not the body.- Re-issue as it was only a request that names memory ids (
selector.memory_idsofevt_orfact_ids), without itsfrom_preview_idandidempotency_key: it names exactly what it named before. An erasure re-issued this way matches facts by the events they came from even though the events are gone, and reports them insummary.deleted_facts. - Never re-issue any other request as it was (a scope-wide forget, an
about_subject,about_entityorpredicateforget, anent_id, a whole-scope erasure): none has a time bound, so re-issued later it also deletes what was written after the original, and a scope-wide redaction or erasure also moves the scope's forget tombstone. Remove instead exactly what came back: the facts in the request's scope that its selector matches, that were created before it was sent and that no later capture supports, forgotten byfact_id. Take the time of the request from the server (the response'saudit_idorerasure_idis a UUIDv7 that carries it), and leave facts recorded within a couple of minutes of it to a person's review. - Never turn a forget into an erasure: a forget that named only
layers: ["facts"]left its event live on purpose.
- Re-issue as it was only a request that names memory ids (
- Beliefs: on incremental-layer deployments every start sweeps the keyless beliefs a v0.9.13
binary wrote; elsewhere rebuild them with
POST /v1/beliefs/buildfor each scope. - The erasure index catches up the rows v0.9.13 appended during the window, in the background, before erasures read it again.
docs/ERASURE_COVERAGE.md ("Rollback window") has the details.
Known issues
Enrichment-on deployments only (with enrichment off there are no facts or beliefs):
- A belief can list a support of another value and the opposite polarity. For a predicate the
built-in ontology does not know (
habit,habit_frequency,no_longer_worksbesideworks_in), the belief builder groups facts of different values into one belief, so "prefers email updates" can list "does not want SMS updates any more" among its supports. Neither fact is wrong; the grouping is. The audit reports these asbelief_polarity_mismatch. - A cessation can retire a fact that is still true. A background consolidation
staleverdict has no same-claim check across a cessation, so "prefers email updates" can be retired by "does not want SMS updates any more" (the audit's informationalnegation_introduced_by_supersession). - Sentence-only updates of a subject that is not a name no longer supersede ("User lives in Boston", then "User lives in Denver": both stay live, where v0.9.13 retired the older one). An update extracted as a triple still supersedes.
- Doubled negations can still be written when the verifier gives no answer. A negated claim
whose object opens with "no", "without", "none" or "nothing" keeps the extractor's polarity when
the re-check is off (
CORTEX_POLARITY_RECHECK=0), no verifier is configured, the call fails, or the event's call budget is spent. The audit lists them asnegation_doubled; re-run a re-derivation on them once the verifier is healthy. - Entity-extraction spend is not priced.
GET /v1/admin/usagerecords the entity-extraction lane with tokens but no model, so it is always underunpriced_callsandCORTEX_MODEL_PRICEScannot price it. Read the provider's bill for the total. - Erased names stay in the enrichment resolver with the entity graph off (the default); the
startup warning
ENRICHMENT_RESOLVER_ERASURE_RESIDUEsays so. - An English denial the extraction model writes as an affirmative is re-checked only for a
triple on a relation the built-in ontology knows whose quote puts the negation before the
object; otherwise it is stored affirmative, and the audit cannot see it either
(
docs/DERIVED_REDERIVE_RUNBOOK.md, "What the audit cannot see").
Everyone:
- An excerpt can take the room of the last whole event in a full
/v1/answercontext. When the recall passes bring more than the request-wide budget holds, an excerpt placed at its event's rank can move the lowest-ranked whole event, already cut short, out of the context.CORTEX_RECALL_BUDGET_EXCERPTS=0restores the previous contexts. - A client that merges several
/v1/recallblocks itself can recognize a budget excerpt from itslayers.eventsitem (_partial_reason: "budget_excerpt") only while the event is still in the layer; the server's own/v1/answermerge knows every excerpt it placed. - Cross-script recall is below the quality bar. A Latin-script query over memories in Devanagari or another script often misses; send a query variant in the memories' script.
directives.extractis honored onPOST /v1/experience/bulkonly. On a single-eventPOST /v1/experienceit is lost on the default unified WAL and enrichment runs as if none were set; send a one-item bulk request until that is fixed.directives.embedis accepted and validated but not enforced.- Blob backfill is per billing tenant.
source-blob-linksandorphan-blobsare confined to the caller's billing tenant, and the static operator key isdefault: the pre-0.10.1 blobs of captures written with minted tokens of another tenant cannot be linked or reviewed by the operator in this release. - Scope-wide
purge.eventscounts derived content rows (a fact, gloss or question-key row stored under the scope) as events; trustdeleted_eventsandredacted_eventsfor a scope. fulltext_segment_over_budgetmay never clear on a store whose full-text segments exceed the pass's budget; it never clears falsely.- A server document processor leaves residue on forget by id: its whole-document derived
text stays recallable (
DOCUMENT_PROVIDER_FORGET_RESIDUE). matched_fragmentslists only chunks a channel returned, so a document found through one chunk cites that chunk, not necessarily the one holding the answer.POST /v1/experience?wait=settledis still accepted but cannot succeed and returns503 SETTLEMENT_UNAVAILABLE. Usewait=indexed.
Clients
These client versions are released with 0.10.1. Every client passes the new response fields through unchanged, and each new request parameter is sent only when you give it, so an older client keeps working against 0.10.1.
| Package | Registry | Previous version | Released with 0.10.1 | What is new |
|---|---|---|---|---|
cortexdbai (Python) | PyPI | 0.11.2 | 0.12.0 | version(), capabilities(); capture language, scripts, timezone, refers_to; recall language, scripts, query_variants and the temporal keys; delete_blob(); event_chunks(); recall diagnostics types; fragment_text / iter_matched_fragments (an excerpted item yields no span, like any partial view) |
cortexdbai (TypeScript) | npm | 0.9.3 | 0.10.0 | The same surfaces: version(), capabilities(), the capture and recall hints, deleteBlob(), eventChunks(), fragmentText / matchedFragments; the excerpt and receipt fields pass through and are documented on the types |
cortexdb-mcp | PyPI | 0.7.5 | 0.8.0 | Matched document spans in memory_search output; clearer code-plane tool output |
cortexdb-cli | PyPI | 0.5.4 | 0.6.0 | cortexdb auth tokens --scope is now where your auth.mint is checked, and --confine CAP:PREFIX carries the token's scopes |
cortexdb-connectors | PyPI | 0.2.21 | 0.2.22 | GitHub: full PR, issue, comment and push bodies, REST-list backfill (GITHUB_BACKFILL_DAYS), object-identity record keys; read connectors/CHANGELOG.md before upgrading, because GitHub records are re-keyed |
Notes
Single-node remains the only supported topology.
Erasure and forget were measured up to 5,000,000 events; 25,000,000 was not measured.
The online re-embed job remains designed and unbuilt; v0.9.12's flagged drop-and-rebuild is the offline path.