Upgrading from 2.5 to 2.6¶
This page takes an existing 2.5.x store to 2.6.0 in one pass. Each 2.6 pre-release documented only its own step in its release notes; this page puts the steps from 2.5.12 onward in one place.
2.6.0 is the 2.6 line's first final release (Current in the support policy), and the 2.5 line becomes Candidate: no channel serves it, and it stays reachable by exact version.
Before you start¶
- Back up the database and its calibration file. Follow
Backup and restore: an online
sqlite3 "$CPERSONA_DB_PATH" ".backup 'cpersona-backup.db'", plus a copy of<CPERSONA_DB_PATH>.calibration.json, which lives outside the database. This backup is the only way back to 2.5 (see Going back to 2.5). - Check the embedding server. Building the overflow nodes for records
already stored needs a server that reports token counts: CEmbedding 0.8.0 or
later. An older server answers
count: null, which is not zero. - Install 2.6.0. A plain upgrade now resolves to it; pin the version so you know which one you are running:
pip install 'cpersona==2.6.0'
What the first start does¶
On its first start, 2.6 migrates the database from schema version 13 (every 2.5.x release) to schema version 18 (17 for 2.6.0 to 2.6.2), one step at a time. No stored row is rewritten: each step adds tables and triggers, or one column. A step that fails is not recorded as done, so it is retried on the next start.
| Schema version | Added in | What it adds | Anything to build afterwards? |
|---|---|---|---|
| 14 | 2.6.0a3 | record_nodes: the overflow tree, pieces of a record past the embedding window |
Yes: nodes for long records already stored (below) |
| 15 | 2.6.0a4 | entities, entity_aliases, entity_mentions, relations: declared associations |
No |
| 16 | 2.6.0a5 | record_blocks: block reach |
Built for you: blocks for the records already stored, unless you turn block reach off (below) |
| 17 | 2.6.0a6 | record_block_vectors: one vector per block |
Built with the blocks |
| 18 | 2.6.3a1 | embedding_model on memories and episodes: the label of the model that produced each vector. Rows already stored take an empty label, which means unknown |
No: a vector is labelled when it is next written |
2.6.0a1, 2.6.0a2, 2.6.0a7, 2.6.0a8, 2.6.0b1, 2.6.0b2, 2.6.0, 2.6.1, 2.6.2, 2.6.4a1, 2.6.4a2 and 2.6.4 changed no schema.
After the first start¶
The gate is recalibrated¶
2.6.0a7 changed the scoring version, so a calibration stored by an earlier
version is treated as stale. With the default
CPERSONA_CALIBRATE_ON_MODEL_CHANGE=true, the server recalibrates the global
threshold at startup. If both that and CPERSONA_AUTO_CALIBRATE are off, the
stale gate is not applied and deep_check reports stale_scoring_version
until calibrate_threshold is run.
Per-agent calibrations are not redone. A threshold or gate calibrated for
one agent with calibrate_threshold is discarded with the old scoring version;
that agent falls back to the global threshold and the heuristic gate until you
run calibrate_threshold for it again. Preferences set with
set_recall_precision are kept.
Build the overflow nodes¶
Long records stored before the upgrade have no nodes until they are built.
Until then they are quoted from their start, and node_unavailable: no_nodes
says so. Build them with the health check, which handles 50 records per run;
repeat it until it reports none left:
check_health(agent_id="<id>", fix=true, checks=["missing_nodes"])
The repair never modifies a record, so it also covers locked memories. Run it again after changing the embedding model.
Block reach is on by default¶
From 2.6.0, block reach is on unless you turn it off. It makes text past a long
record's embedding window reachable by search, and reconstruct quotes the
block that matched (design).
- The first start begins a bounded backfill of the records already stored.
It embeds every block through the same embedding server that embeds your
records, one call per batch of blocks; on this project's own store, 4,478
records divided into 70,130 blocks.
check_healthshows the progress asmissing_blocksand moves it along underfix=true. Until a record's blocks exist, recall reaches it through the other arms, as it does with block reach off. - Each block is stored twice, as one bit per dimension and as one byte per dimension: 1,152 bytes of vector per block with a 1,024-dimension model (128 + 1,024).
- Recall reads the index on every query that has a vector, and may return
up to 2 rows beyond
limitfor records only the block arm reached. It has no effect where vector search is remote. - Recall gets slower and larger. On that store, on one machine, the median
recalltook 138 ms with the block arm against 17 ms for 2.5.12 (the query's embedding excluded), and the process peaked at 180 MB against 106 MB. The cost grows with the number of blocks; a store ten times larger has not been measured. - To turn it off, set
CPERSONA_BLOCK_BUILD_ENABLED=false: no embedding calls, no rows and no queued work, and the reader follows it off.CPERSONA_BLOCK_RETRIEVAL_ENABLED=falsealone keeps the index built and unread. Setting the reader on with construction off is a startup error.
If you ran 2.6.0a5 with block construction on, its block sets have no vectors and are rebuilt by the same backfill.
Behaviour that changed¶
Check these against what your deployment relies on. Each is off, or equal to 2.5, unless noted.
- Block reach is on by default (2.6.0). A store queues the record's
blocks and says so (
blocks: {"status": "queued"}), recall can return up to 2 reserved rows beyondlimit, andreconstructquotes the block that matched. Above is how to turn it off. -
reconstructreturns 10 items whencountis omitted (2.6.0; it returned 1). With block reach on, this is the configuration 2.6.0 recommends for answering from memory. A deployment that relied on one item setsCPERSONA_RECONSTRUCT_DEFAULT_COUNT=1; loweringCPERSONA_RECONSTRUCT_MAX_COUNTalone lowers the default with it. -
limitis the number of rows returned (2.6.0a2). How deep fusion looks ismax(limit, CPERSONA_RECALL_DEPTH_FLOOR); the floor defaults to 0, so the ranking is the one 2.5 gave. reconstructneeds the same per-agent read grant asrecall(2.6.0a2). This matters only where access control is configured.- Confidence no longer re-sorts or gates recall (2.6.0a7). With
CPERSONA_CONFIDENCE_ENABLED=true, each row still carries aconfidencevalue, but the order is the fusion order. The profile row (update_profile) used to rise to the top under the old re-sort; it now comes last and a fulllimitcan cut it. If something must reach the agent every time, put it in the client's instructions file or system prompt.CPERSONA_CONFIDENCE_ORDERING=legacyrestores the old order. - The episode-boundary penalty is off by default (2.6.0a7). Deployments
that used it to damp older sessions set
CPERSONA_EPISODE_PENALTY_ENABLED=true. - A time cue can add up to three rows (2.6.0a8). With
time_cue,recallreturns up tolimit+ 3 rows (3 / 2 / 1 by the cue's confidence), and a cue that points only at the last 24 hours is not used (time_cue.ignored). Without a cue nothing changes. declare_associationsis marked destructive (2.6.0b1). Itsretractargument deletes, so a client that asks before running destructive tools now asks before this one.reconstructwalks at most 5 hops, astraversedoes (2.6.0b1). A largermax_hopsis lowered, andbounds.max_hopsstates the value applied.- An alias follows the scope it is declared in (2.6.0b1). A declaration made in a project that gives aliases to a name the global pool already has now registers the project's own entity, so other projects do not read those aliases. Aliases declared this way before 2.6.0b1 stay readable everywhere: the stored data does not record which scope declared an alias.
- Smaller corrections (2.6.0b1): a
storewithoutassociationsno longer answers with an emptyassociationsobject;retractrefusestrueandfalseas ids; a quote carries a qualifier that starts on the next line; an episode'sreconstructquote is measured in the stored summary, without the[Episode]label;check_healthreports and rebuilds node sets with a gap or a missing embedding. session_keyis at most 256 characters (2.6.0b2). Every tool that takes it declaresmaxLength: 256, and a longer key is refused before the call runs, with an input validation error. Keys minted from a process id and its start time, or a UUID, are well under the bound.- A listing the row cap cut says so (2.6.0b2).
list_memoriesandlist_episodesstill clamplimitto 500 and 200 rows. When the caller asked for more and rows past the cap exist, the response carriesbudget_rows(the cap). A client that stops when it gets fewer rows than it asked for should read this key. - How far behind the vector index is counts every row a query reads from the
table (2.6.0b2).
python -m cpersona.vector_index statusaddsrows_read_exactly,excludedandunembedded, andbuildaddsunembedded.check_health'svector_index_tail_growncomparesrows_read_exactlyagainst the rebuild ratio, so afterfix=truefills missing embeddings it can appear withrows_past_watermarkat 0. Rebuilding the index takes in the rows that have since gained an embedding. - Smaller corrections (2.6.0b2): on an in-memory database,
export_memoriesreads a consistent copy; the vector index path scores a window it has to copy one chunk at a time, in the ranges the table scan uses. - A
reconstructbudget below one head quote is raised to it (2.6.0). With the defaultCPERSONA_RECONSTRUCT_QUOTE_CHARSof 800, a smallerbudgetbecomes 800 andbudget_policysays so. It used to be raised only to 500, while the head could run to 800 without the overrun being reported. - A recall excerpt reads the block index only while block retrieval is on
(2.6.0), as
reconstruct's quote does. WithCPERSONA_BLOCK_BUILD_ENABLED=trueandCPERSONA_BLOCK_RETRIEVAL_ENABLED=false,excerpt_basisis nowlexical. - A block set is current only if it respects the node layout and has every
re-rank vector (2.6.0). Where block building is on, sets built before their
record's nodes existed are rebuilt by the sweep and by
check_health(fix=true)after the upgrade. - A non-default
CPERSONA_PRIOR_FAR_WEIGHTis part of the calibration (2.6.0). A deployment that sets it to anything but 1 recalibrates on its first start of 2.6.0, because a gate measured at another weight is no longer restored. At the default nothing changes. - A time cue searches past the scan window through the coarse index by
default (2.6.4a2). With
CPERSONA_CUE_COARSE_ENABLEDunset, the part of a cue's period past the scan window is searched when a coarse index exists, and otherwise left out withtime_cue.remaindersaying so; nothing reads every stored vector. A store within the window sees no change.trueandfalsekeep their meaning (settings). - Rows held beside the answer earn no recall count (2.6.0). With confidence
enabled, the block reservation, the time cue's seats and the propagation seat
no longer raise
recall_count, and theirconfidencereads their own history. - Smaller corrections (2.6.0): a
reconstructquote cut to the preview tier hands over the whole range it began inexpand;shortfall_reasonblames the budget only when it cut an item of the window;bounds.reachedcounts only the rows inside recall's limit; atime_cuepast the representable range is clipped rather than raising, and a unit that is not a string is refused; a failing node or block build no longer holds the other queued tasks; inapiembedding mode the node check no longer reads every record's text.
New in 2.6 and inert unless asked for: the reconstruct tool, the recall trace
(trace=true), the time cue (time_cue), associations declared with
declare_associations or on store, and the propagation seat
(CPERSONA_RECALL_PROPAGATION_SEAT).
Checking the result¶
check_health(agent_id="<id>", checks=["missing_nodes"])reports no records left to build.deep_checkdoes not reportstale_scoring_version.- A few recalls you know the answer to return it.
Going back to 2.5¶
Restore the backup you took before the upgrade (the database and the calibration file), then install 2.5.12. Do not point 2.5 at a migrated database. 2.5.12 opens a newer schema only with a warning that it may misread it; nothing converts a schema version 17 database back.