CortexDB · docs

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

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.

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.

Upgrade steps

  1. 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.
  2. Enrichment-on deployments: run scripts/derived_corruption_audit.py against 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.
  3. 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.
  4. Record exactly which build you deploy (image digest or binary sha256, and the git_sha that GET /v1/admin/version reports), and keep the current v0.9.13 artifact for a rollback.
  5. 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 to no (docker update --restart=no <container>): under always, a Docker daemon restart starts a stopped container again, and two builds would race for the same port and data dir.
  6. Start 0.10.1 with a 120 s stop grace, wait for GET /v1/admin/ready, and on incremental-layer deployments for belief_reconcile.done. Check that capabilities[] lists the ten names under "Capabilities".
  7. Enrichment-on deployments: follow docs/DERIVED_REDERIVE_RUNBOOK.md with 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.
  8. For each billing tenant that uploaded text blobs, run POST /v1/admin/index-audit/source-blob-links (dry run first), then review GET /v1/admin/index-audit/orphan-blobs (see "Erasure").
  9. 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:

CapabilityWhat it addsRequest fieldsResponse fields
blob_text_decode_v1Charset-safe decoding of text blobsnone (behaviour)422 BLOB_TEXT_ENCODING_UNKNOWN
budget_excerpt_v1No context entry exceeds the budget; oversized events served as excerpts; the context receiptnonelayers.events[].content._partial_reason: "budget_excerpt", _original_bytes, _excerpt_ranges; context_contributors rows with evicted_from_layers
capture_language_v1Language, script and time-zone hints on captureslanguage, scripts[], context.timezone on /v1/experience and /bulklanguage, scripts, language_source, context.timezone on event reads and recall items
erasure_blob_cascade_v1Erasure deletes the source blob; the raw residual passnonesource_blob_ids on events; purge on /v1/forget; purge.blobs, purge.retained_shared_blobs, log_residual_cleared_at, log_residual_pending
matched_fragments_v1Which chunks of a long event matchednonelayers.events[].matched_fragments[]; the context_fragments trail entry
query_variants_v1Extra phrasings of the questionquery_variants[] on /v1/recall and /v1/recall/streamper-variant ranks in the diagnostics side table
recall_diagnostics_v1Per-channel recall diagnosticsdiagnostics: "full" (existing field)diagnostics.items, drops, channel_lists, items_truncated
recall_language_v1Language and script hints on recalllanguage, scripts[] on /v1/recall and /v1/recall/streampack warnings[]
refers_to_v1The times a memory talks aboutcontext.refers_to[] on captures; temporal.refers_during on recall and answercontext.refers_to on events
temporal_lenient_v1Lenient time phrases and calendar time zonestemporal.natural_mode, temporal.timezone on recall and answerthe 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)

Recall hints and stoplists (recall_language_v1)

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)

Time: lenient phrases, calendar zones and referenced times (temporal_lenient_v1, refers_to_v1)

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

Documents: matched fragments and chunk inspection

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:

SwitchDefaultEffectPre-fix value
CORTEX_EVIDENCE_ROLE_CLASSIFIERpackerTimestamp- and role-aware evidence classifier for the answer packer and the multi-session preamble (full: every consumer)legacy
CORTEX_CONTEXT_STRIP_ECHOpairedDrop 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_OVERSIZEDonSkip an event that does not fit and keep packing (affects every ranked /v1/recall)0
CORTEX_FACET_PACK_POLICYtieredIn a byte-congested merge of the primary and facet recalls, focused dialogue and fact rows firstlegacy
CORTEX_CONTEXT_ASSISTANT_EXCERPTSoffOpt-in: long assistant turns render only their matched chunk spans (CORTEX_CONTEXT_ASSISTANT_EXCERPT_MAX_BYTES, default 600)0
CORTEX_ABSTAIN_SELF_RESCUElegacy passThe abstention self-rescue; guarded (opt-in) skips it when the abstention denies the question's premise; 0 never rescuesunset
CORTEX_RECALL_BUDGET_EXCERPTSonNew in 0.10.1: budget excerpts, below0

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.

"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}]
}

Measured on test shapes (deterministic mock embeddings, the build before this change against 0.10.1):

ShapeBefore0.10.1
One 8 KB document, max_tokens: 2000 (7,000-byte budget)8,227-byte block, empty events layer6,874 bytes; a 6,839-byte excerpt in the layer
One 32 KB document, max_tokens: 100032,803 bytes (9.4 times the budget)3,493 bytes
Mixed pack whose 8 KB answer did not fit, max_tokens: 2000188 bytes, answer missing6,620 bytes, answer as a 6,390-byte excerpt
One 9 KB Devanagari document, max_tokens: 20009,303 bytes6,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)

/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

Regression checks

These are regression checks with answer-free replays and a blind benchmark harness, not published scores.

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

One subject per supersession

Stable belief keys and deterministic ids

Extraction reads the original text

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:

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

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

Blobs (erasure_blob_cascade_v1)

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:

reasonMeaning
pass_pendingThe pass has not finished the work this job recorded yet
fulltext_segment_over_budgetA 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_coveredNo 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:

Request1M events, without1M, 0.10.15M events, without5M, 0.10.1
/v1/forget by id, large scope12.2-13.0 s0.04-0.37 s64.0-66.8 s0.09-0.27 s
the same forget repeated4.4 s6 ms23.4 s90 ms
/v1/forget, 100 ids14.0 s0.21 s71.2 s0.57 s
/v1/erasures, 1 id12.7-13.5 s0.05-0.12 s67.6-71.1 s0.05-0.07 s
/v1/erasures, 1,000 ids25.2 s0.64 s105.0 s2.3 s
/v1/erasures, 1 blob-backed id27.7-27.9 s0.06-0.14 s137.5-140.1 s0.07-0.11 s
/v1/forget scope-wide, small scope21.8-22.0 s0.44-0.47 s103.5-179.5 s2.5-2.6 s
/v1/erasures whole scope, small scope33.3 s0.56 s175.3 s2.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

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:

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

Operator and admin routes

New routes (all documented in docs/schemas/openapi.json):

RouteAccessPurpose
POST /v1/admin/derived/rederivetrusted operator, admin.compactPlan (dry run, default) or run a re-derivation job
GET /v1/admin/derived/rederive/{job_id}trusted operator, diagnostics.readRead a re-derivation job
POST /v1/admin/derived/resolver/unmergetrusted operator, admin.compactReport (dry run, default) or split names the resolver merged
GET /v1/admin/events/{id}/chunkstrusted operator, diagnostics.readHow one event was chunked, with fragment offsets
POST /v1/admin/index-audit/source-blob-linkstrusted 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-blobstrusted operator, diagnostics.readLive blobs no surviving event links or references

Changed routes:

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.

VariableDefaultWhat it does
CORTEX_RECALL_BUDGET_EXCERPTSonBudget excerpts and the strict budget (0, off or false restores the pre-0.10.1 render)
CORTEX_EVIDENCE_ROLE_CLASSIFIERpackerAnswer-context evidence classifier (full, legacy)
CORTEX_CONTEXT_STRIP_ECHOpairedEcho strip (all, 0)
CORTEX_CONTEXT_RENDER_SKIP_OVERSIZEDonSkip an oversized event and keep packing
CORTEX_FACET_PACK_POLICYtieredFacet merge policy (legacy; byte_share, tiered_byte_share, tiered_facts_late for evaluation)
CORTEX_CONTEXT_ASSISTANT_EXCERPTSoffOpt-in assistant-turn excerpts (1, ms)
CORTEX_CONTEXT_ASSISTANT_EXCERPT_MAX_BYTES600Per-event cap of those excerpts
CORTEX_QUERY_VARIANTS_FUSIONinterleaveVariant fusion (evaluation: depth_cap, deep_rrf)
CORTEX_QUERY_VARIANTS_DEPTH10Depth for depth_cap (evaluation)
CORTEX_EMBEDDING_CACHE_DISABLEunsetMeasurement only: bypass the provider embedding cache
CORTEX_CHUNK_FRAGMENTSonWrite chunk fragment rows
CORTEX_ENRICHMENT_SKIP_NON_ENoffSkip LLM enrichment for non-Latin-script text
CORTEX_POLARITY_RECHECKonThe polarity re-check call (0 records the doubt without a call)
CORTEX_POLARITY_QUOTE_TRIGGER_ENonThe English quote trigger of that re-check
CORTEX_ENRICHMENT_SCOPE_QUIET_MS0 (off)Hold a busy scope's captures for one batched extraction (at most 60000)
CORTEX_ENRICHMENT_SCOPE_QUIET_MAX_MS30000Longest hold of that window
CORTEX_DESTRUCTIVE_LEASE_WAIT_SECS120How long a capture waits for a forget's or erasure's lease (1 to 3600)
CORTEX_SHUTDOWN_DESTRUCTIVE_WAIT_SECS60How long a graceful stop waits for running forgets and erasures (0 to 3600)
CORTEX_ERASURE_RESIDUALonThe raw residual pass (0 off; a startup warning says so)
CORTEX_ERASURE_RESIDUAL_SHUTDOWN_SECS20Residual drain at a graceful stop
CORTEX_ERASURE_RESIDUAL_MAX_BYTES536870912SST bytes one pass may rewrite per store
CORTEX_ERASURE_RESIDUAL_FULLTEXT_MAX_DOCS1000000Largest full-text segment one pass merges
CORTEX_ERASURE_RESIDUAL_MAX_PENDING_KEYS131072Recorded keys waiting per store
CORTEX_ERASURE_RESIDUAL_MIN_INTERVAL_MS30000Minimum gap between passes
CORTEX_ERASURE_RESIDUAL_DEBOUNCE_MS1000Quiet time before a pass starts
CORTEX_ERASURE_RESIDUAL_PARKED_RECHECK_SECS900Recheck interval of a parked over-budget segment
CORTEX_ERASURE_PERIODIC_COMPACTION_SECSunsetPeriodic compaction backstop for the stores (for example 86400)
CORTEX_ERASURE_INDEXonRead the erasure index (0: maintained, never read)
CORTEX_ERASURE_INDEX_BACKFILLonBackfill the index on a store written before it
CORTEX_ERASURE_INDEX_BACKFILL_ROWS20000Rows per backfill step
CORTEX_ERASURE_INDEX_BACKFILL_PAUSE_MS20Pause between backfill steps
CORTEX_CODE_REPO_ROOTSunset (startup warning)Directories code-plane repositories may be registered from
CORTEX_EVAL_FACET_PLAN_OVERRIDEoffEvaluation only; never in production
CORTEX_EVAL_DUMP_ANSWER_POOLSoffEvaluation 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:

StatusCodeWhereRetriable
422INVALID_BODYA 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
422INVALID_ENVELOPEA malformed capture language, scripts, context.timezone or context.refers_tono
422BLOB_TEXT_ENCODING_UNKNOWNA capture referencing a text blob whose bytes are not valid in any declared or detectable encodingno
409DESTRUCTIVE_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 oneyes (Retry-After: 5)
409SCOPE_STATE_CHANGED/v1/scopes register, member edit and delete inside that footprint; a capture whose lease wait ended just as the lease was releasedyes
503SERVER_SHUTTING_DOWN/v1/forget, /v1/erasures after a graceful stop began; nothing was changedyes (Retry-After: 5, against the restarted server)
409REDERIVE_IN_PROGRESSA forget, an erasure, their previews or a second re-derivation on a scope root a re-derivation job holdsyes (Retry-After: 30)
409ENRICHMENT_OFFPOST /v1/admin/derived/rederive with enrichment offno
409JOB_MISMATCHA re-derivation job_id reused for other targets, actions or billing tenantno
409JOB_NOT_COMPLETEDcontinue_from names a job that has not completedno
409JOB_ALREADY_CONTINUEDcontinue_from names a job another job already continued (details.continued_by)no
404JOB_NOT_FOUNDcontinue_from names an unknown or foreign jobno
422COLD_BACKUP_ACK_REQUIREDA re-derivation apply with purge_derived and no cold_backup_ack: trueno
422NO_ELIGIBLE_EVENTSA re-derivation apply that would purge or re-enrich, with no eligible event; nothing changedno
422NO_TARGETSA re-derivation or unmerge request that names nothing to repair, or a continue_from job that left nothingno
422INVALID_AUDITA re-derivation or unmerge audit report that cannot be readno
503REDERIVE_UNSUPPORTEDThe coordinator cannot run re-derivationno
500REDERIVE_FAILED, REDERIVE_PLAN_FAILED, UNMERGE_FAILEDPlanning 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
409UNIFIED_WAL_REQUIREDsource-blob-links on the legacy dual WALno
500SOURCE_BLOB_LINKS_FAILED, ORPHAN_BLOB_REPORT_FAILED, CHUNK_INSPECTION_FAILEDFailures of the new operator routesyes
422EVAL_OVERRIDE_DISABLED/v1/answer with facet_plan_override on a server without the evaluation flagno
403SCOPE_REGISTRATION_REQUIREDCode plane: registering a repository directly under a root scopeno
409REPO_SCOPE_IN_USECode plane: the binding another repository already holdsno
422REPO_PATH_NOT_ALLOWEDCode plane: a path outside CORTEX_CODE_REPO_ROOTS or always refused (details.reason)no
400INVALID_PATHSCode plane: a malformed certify paths filterno

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:

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 upgrading again

  1. Delete the orphan blobs of events erased during the window (GET /v1/admin/index-audit/orphan-blobs, then DELETE /v1/blobs/{id}; check each first, because the report also lists blobs of live events written before 0.10.1), and set CORTEX_ERASURE_PERIODIC_COMPACTION_SECS for a while to reach the raw residual.
  2. 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_ids of evt_ or fact_ ids), without its from_preview_id and idempotency_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 in summary.deleted_facts.
    • Never re-issue any other request as it was (a scope-wide forget, an about_subject, about_entity or predicate forget, an ent_ 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 by fact_ id. Take the time of the request from the server (the response's audit_id or erasure_id is 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.
  3. Beliefs: on incremental-layer deployments every start sweeps the keyless beliefs a v0.9.13 binary wrote; elsewhere rebuild them with POST /v1/beliefs/build for each scope.
  4. 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):

Everyone:

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.

PackageRegistryPrevious versionReleased with 0.10.1What is new
cortexdbai (Python)PyPI0.11.20.12.0version(), 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)npm0.9.30.10.0The 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-mcpPyPI0.7.50.8.0Matched document spans in memory_search output; clearer code-plane tool output
cortexdb-cliPyPI0.5.40.6.0cortexdb auth tokens --scope is now where your auth.mint is checked, and --confine CAP:PREFIX carries the token's scopes
cortexdb-connectorsPyPI0.2.210.2.22GitHub: 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.