Releases: Cloto-dev/CPersona
Release list
v2.5.2b2 — beta: recall ranking correction (bug-155) and a storage policy the maintenance layer respects (bug-182)
Pre-release (second beta of the 2.5.2 stabilization ladder; the latest final release remains v2.5.1, which pip installs by default — pre-releases require explicit opt-in).
b1 changed what callers see. This rung changes what recall returns and what the maintenance layer does about a configuration it had been misreading. No contract break — the tool responses and the DB schema are the ones b1 published, so upgrade and rollback across this cut need no migration and no caller change.
Ranking correction
-
Vector-less rows no longer out-score real vector hits (
bug-155, HIGH). UnderCONFIDENCE_ENABLED, rows admitted through the FTS or keyword channel reached_apply_recall_scoringwith_cosine=None— even when their embedding was sitting in the DB — and_compute_confidence'sNonebranch (sqrt(time_decay) * …) scored at least as high as its cosine branch (sqrt(norm_cos * time_decay) * …). The keyword channel, documented as a way to down-rank lexical contaminants, was structurally promoting them instead. The fix backfills the cosine for those rows before scoring.Measured on LMEB: +14.27 NDCG@10 on
rsf, 16 of 16 tasks improved,cascadeundisturbed (it does not route through this seam). This is a default-behaviour change: the same query against the same database can come back in a different order — that is the point of the fix, and the reason it ships as a beta rung rather than inside a patch.
Storage policy, respected
CPERSONA_STORE_BLOB=falseis a policy, not a broken pipeline (bug-182, HIGH). The maintenance layer did not know that. On a correctly configured remote deployment — wheredo_storedeliberately writes no local embedding —check_null_embeddingread every NULL row as a dead embedding pipeline:severity=criticalpermanently (>50% NULL by construction), and everycheck_health(fix=true)re-embedded up to 500 memories over HTTP and wrote back the very BLOBs the operator had configured away, which the nextstoremade NULL again. The rule now has one home,config.local_blobs_stored, and the checks read it. Episodes are deliberately out of scope: they have no storage gate and always write a blob.- The empty safety net now says so (
bug-180, LOW). Under remote mode with that same setting, the local cosine scan — documented as the fallback for a/searchoutage — matches nothing, becauseembedding IS NOT NULLis false for every memory by construction. A/searchfault with a working/embedtherefore dropped recall to FTS/keyword silently; the degraded advisory watched only the embed boundary.
Supply-chain surface
- The wheel stops shipping dead vendored code (
bug-181, LOW). Every wheel carried ~2,479 LOC of vendored modules with no import path from any cpersona module (llm_provider.py,mcp_stream_interceptor.py,mgp_utils.py,search.py,semantic_cache.py) — unreachable by construction, yet on every user's disk, enlarging the audited and supply-chain surface (llm_provideralone carries outbound third-party HTTP). They are excluded at the wheel boundary only: the in-tree vendored snapshot stays byte-faithful to upstream, so the sync gate still compares like for like. An AST import-graph gate and awheel-smokeCI job guard the two ends.
Verification
- Full suite: 683 passed, 5 skipped; ruff clean;
verify-issues.sh182 entries, 0 errors, 0 stale; waiver registry clean; CI green on the bump commit (lint,test (3.11),test (3.13),wheel-smoke,verify-issues,mutation-proof). - The cosine backfill carries hand-applied mutation evidence, re-proven after the fix was rebased onto master.
- PyPI artifacts (wheel + sdist) published via OIDC Trusted Publishing with provenance.
Known gap (unchanged from b1)
The automated mutation-diff lane is advisory and still skips everything when the diff exceeds its cap rather than sampling a subset. Of the two changes in this cut, bug-155 (112 in-scope mutants, cap 60) was not executed for that reason; the bug-182/bug-180/bug-181 change (55 in-scope) ran and returned 43 killed / 12 survived. A capped run and a passing run currently render the same colour in the checks list. Tracked — the fix makes large diffs sample instead of opt out.
v2.5.2b1 — beta: the contract breaks (store result / check_health status / source-type enum)
Pre-release (first beta of the 2.5.2 stabilization ladder; the latest final release remains v2.5.1, which pip installs by default — pre-releases require explicit opt-in).
The alpha rungs stayed inside the implementation. This one changes what callers see: it is the first cut of the 2.5.x line to break the public MCP tool contract, which is why it goes through the ladder (RELEASE_LIFECYCLE_STANDARD §2.1). The DB schema is unchanged — upgrade and rollback across this cut need no migration.
Contract breaks (3)
storeanswers whether it stored. Every response now carriesresult:stored(a row was written),skipped(nothing written, nothing wrong — dedup, or persistence paused),rejected(the request was refused).oktracks that verdict instead of being unconditionally true, and the oldskipped: trueflag is gone. Previously a store that wrote nothing — empty content, content that sanitizes to empty — answered{ok: true}, so a caller checking the obvious field read success for a write that never happened.check_healthhas one verdict. Thehealthyboolean is gone;status(healthy/degraded/unhealthy) is the verdict. The two fields disagreed by construction:healthywaslen(issues) == 0, so a database whose only findings were informational reportedhealthy: falsewhilestatusin the same response saidhealthy. The counts are still inissuesandseverity_summary.- One definition of the source-type enum. The canonical set was stated in three places and owned by none of them (a SQL literal in the health check, a tuple in the write seam, prose in the published schema).
utils.CANONICAL_SOURCE_TYPESis now the single definition; thestoreJSON Schema publishes it as anenumand generates its alias sentence from the alias table, so a caller no longer learns the contract by reading a health check.
Also in this cut
- Write-path bounds (
bug-156):sourceandmetadatareach the row as JSON with no size limit, and an episode's summary/keywords never had one — content has been capped since 2.1. Oversized sidecars are now refused, episode prose is capped in the shared prepare seam (so the queue drain obeys the same rule), andupdate_memoryruns the same sanitizer asstoreinstead of only.strip(). - A silent filter, disclosed (
bug-157): everyexternal_contextentry filtered recall while onlyuser/assistantentries were merged into the response, so asystementry could make a memory disappear with nothing saying why. The response now carriescontext_filter_only. - Remote-recall hydration batched (
bug-158): remote mode re-hydrated every hit with its own awaitedSELECT ... WHERE id = ?. Alimit=100recall paid 100 sequential round trips on the read hot path — measured 102 for 102 hits, now 2. - Bounded
/indexpush (bug-159): the write-path POST inherited the embed client's 30s default while the health probe (3s) and remote search (5s) each state their own. It states 10s now. - Descriptions rendered from the code they describe (
bug-160): the check-registry size (claimed 20, was 23), which deep checks honourfix=true, the tools a pause silences, the@autosentinel. Structural CI gates fail when any of them falls behind the implementation again. The shippedcpersona-memoryskill had the same drift and is gated too. - 18 findings from the independent pre-ship review (
bug-162..bug-179), and the outward claims the breaks falsified — README,SUPPORT.mdand the standard saying the tool contract is preserved across line transitions — corrected in the same batch.
Verification
- Behaviour-equivalence goldens were extended to the three layers these breaks touch (handler seam, MCP boundary, health/deep-check) before the breaks landed, with a frozen clock so wall-clock never reaches the golden; pin effectiveness was proven by hand-applied mutation. Each golden line that moved in this cut is one of the documented contract changes.
- Full suite: 664 passed, 5 skipped;
verify-issues.sh178 entries, 0 errors; ruff clean; mutation-proof CI job green; waiver registry clean. - PyPI artifacts (wheel + sdist) published via OIDC Trusted Publishing with provenance.
Known gap
The automated mutation-diff lane was skipped on this cut: the diff exceeded the lane's cap, and the cap currently skips everything rather than selecting a subset (tracked; the fix makes large diffs sample instead of opt out). The contract changes carry hand-applied mutation evidence instead, recorded per test.
v2.5.2a2 — alpha: promotion-gate audit remediation (M-142..L-154)
Pre-release (second alpha of the 2.5.2 stabilization ladder; latest final release remains v2.5.1, which pip installs by default — pre-releases require explicit opt-in).
This cut ships the remediation of the alpha→beta promotion-gate audit (#61): a 78-agent workflow (4 equivalence seams + 16 finder dimensions, every finding adversarially verified through two independent lenses) confirmed the a1 refactor seams behavior-preserving and surfaced 13 defects, all fixed here as registry entries bug-142..bug-154. DB schema unchanged.
Highlights
- Silent-failure fixes:
storeno longer reportsembedded: truewhen the remote index POST returns 4xx/5xx (M-146); one malformed-JSONsourcerow no longer blankscheck_invalid_source_type/check_anonymous_sourcefor the whole agent (M-144);delete_episodenow purges the episode from the remote vector index, symmetric withdelete_memory(M-148). - Isolation correctness: the episode-boundary recall penalty is scoped to the recall's
project_id/channelaxes instead of the whole agent (M-147). - Legacy-data remediation path: pre-v12
msg_idcollisions that permanently blocked the dedup UNIQUE index are now resolved non-destructively (keep newest, blank older unlockedmsg_ids — no row deleted) in both the migration and a newdedup_msg_id_indexhealth check (L-145). - Operational hardening:
checkupCLI exits with a readable error instead of hanging forever on a corrupt/foreign DB (M-143) and no longer advertises a phantom default DB path (M-142); HTTP port and embedding timeout env vars degrade to defaults instead of crashing startup (M-150);pause_persistence/resume_persistence/persistence_statusresponses carryscope: "process"and their descriptions state the HTTP-deployment blast radius (M-151); concurrentset_recall_precisionrollback can no longer clobber another writer (L-149). - Gate integrity:
verify-issues.shswitches to unit-separator serialization with an emitter assertion and a transport count check — a literal|in a registry field can no longer silently drop a check while the gate exits 0 (M-152); the vendored-common sync script gains a post-sync reconciliation gate for the not-yet-upstreamed patches (L-153);scripts/mutation-proof.pynow runs as a blocking CI job (L-154). - Standard v1.3 (#62): the Release Lifecycle Standard gains §2.8 (defect identifiers — one number space, severity-prefixed human-facing references like
M-144, barebug-NNNonly in the tree).
Verification
- Every behavioral fix ships a fail-first regression test (captured failing on the unfixed code for the intended assertion); one fix was independently re-proven by hand-applied mutation during review.
- Full suite: 556 passed, 5 skipped;
verify-issues.sh154 entries clean; ruff green; mutation-proof CI job green (first runs on the merged tree). - The branch code was validated read-only against a production-corpus copy (1,631 memories) before merge: clean run, expected findings only, and the new health check confirmed no legacy
msg_idcollisions in production. - PyPI artifacts (wheel + sdist) published via OIDC Trusted Publishing with provenance.
v2.5.2a1 — alpha: refactor seams under mutation proof + agent-facing API polish
Pre-release (opens the 2.5.2 stabilization-patch ladder; latest final release remains v2.5.1, which pip installs by default — pre-releases require explicit opt-in).
First alpha of the 2.5.2 line ("stabilization / bug-fix patch, no new features" — the ladder runs a1 → beta → final). This cut carries the merged alpha-stage work (#59) plus the pre-release review follow-up (#60). DB schema unchanged; tool-contract changes are deliberate, additive, and documented in the tool descriptions (2.5.x charter).
Highlights
- Refactor seams under mutation proof (#59):
admin_handlersper-record decomposition (_ImportTally/_MergeTally),vector._search_vectorsplit (_search_vector_remote/ local scans),_calibrate_fused_gateextraction — all pinned equivalent to v2.5.1 by a machine-generated golden behaviour harness (tests/behaviour_252.py, 43 equivalence tests) and a mutation-proof script. - Source contract normalization at the write seam (Task #282): known legacy
sourceshapes (lowercase type words, Rust serde externally-tagged dicts, bare'user'/'assistant'strings) are normalized to the canonical{type, id, name}on store; unknown shapes stay verbatim and keep surfacing viacheck_health(invalid_source_type). The fixer is now mapping-based (preserves id/name) instead of the pre-2.5.2 blanket-User overwrite. - Agent-facing API polish:
storeresponses exposeembedded; recall rows exposematch_reason;check_healthgains a 3-tier status surface. - Pre-release review follow-up (#60, bug-139..141 in
qa/issue-registry.json):check_invalid_source_type(fix=True)reports an additivelockedcounter somapped + unmapped + lockedreconciles withcount; the deliberate null-source →{}write-seam convergence is documented in the store tool description; the no-persist branch ofstorecarriesembedded:false(branch onpersisted).
Verification
- Full suite: 523 passed, 5 skipped; lint + issue-registry verification green (CI on the tagged commit).
- Independent read-only code review of the full
v2.5.1..masterdiff: APPROVE-WITH-NITS, no release blockers; all three nits fixed in #60. - PyPI artifacts (wheel + sdist) published via OIDC Trusted Publishing with provenance.
v2.5.1 — server-served operating context
Server-served operating context: CPersona can now carry the operating doctrine itself (project_id registry, agent_id conventions, recall discipline) instead of every client re-deriving it from local prose.
Added
- Operator-owned TOML sidecar (
cpersona/operating_context.py) at~/.cpersona/operating-context.toml, with aCPERSONA_OPERATING_CONTEXT_PATHoverride and aCPERSONA_OPERATING_CONTEXT=offkill switch. Lazy mtime-based reload. An absent or invalid file degrades to fully dormant — a config typo can never take memory down. - Soft layer:
instructions.summaryrides the MCPinitializeresponse through a newToolRegistry(instructions=...)kwarg, so a connecting client receives the doctrine as part of the handshake. - Hard layer: a boundary gate on the six
project_id-accepting tools — registry validation (off/warn/reject) plus an opt-in@autosentinel resolved from[defaults], echoed back asresolved_project_id. Reads warn even underreject(damage asymmetry: a bad read is recoverable, a misfiled write is not). Explicit values are never rewritten, omitted /""semantics are untouched, and library callers bypass the gate by design. get_operating_context— a new read-only tool (28 → 29). The write path stays the filesystem: there is deliberately no MCP write tool for operator config.- check_health: additive
operating_context_parse(warn) andoperating_context_size(info, 3,000-char budget) registry entries.
Contract
No DB schema change. No breaking change: the tool surface is additive (one new tool, additive response fields), consistent with the 2.5.x line declaration in SUPPORT.md. The feature is entirely dormant until an operator creates the sidecar.
Verification
430 passed / 5 skipped, ruff clean, issue registry 110 verified / 0 stale / 0 errors. Full CI green on master before tagging.
Design: docs/OPERATING_CONTEXT_DESIGN.md.
v2.5.0 — final: remote-mode + export cluster closed; 2.5.x enters Current
First final of the 2.5.x line. The 2.5.0 pre-release series (a1→b3) graduates to 2.5.0, which enters the Current tier (passed the full release gate; awaiting production-soak certification to Stable). 2.4.x remains Stable and the marketplace default.
Fixes — the last five open issues (all deferred remote-mode / export)
- bug-046 + bug-075: the remote (
VECTOR_SEARCH_MODE=remote) by-id episode fetch omitted the channel predicate → a channel-scoped recall could return an episode from another channel. The remote fetch now carries the channel axis, symmetric with the local branch. - bug-049:
do_archive_episodenow pushesep:{id}to the remote vector index after commit — archived episodes are no longer absent from remote-mode semantic recall. - bug-050: import and merge now sync their inserted episodes to the remote index.
- bug-073:
do_export_memoriesstreams each table (fetchmany) instead of materialising the whole corpus, removing the OOM risk on large exports. A newread_snapshot()seam gives the header and body one consistent private WAL snapshot.
Zero open bugs remain in the issue registry. Full suite: 386 passed; verify-issues green; ruff clean. No DB schema or MCP tool-contract changes.
v2.5.0b3 — beta: post-b2 CI-incident fix bundle (bug-123, bug-124)
Pre-release (2.5 Experimental line — the 2.4 series remains the Stable line, latest v2.4.40).
Fix-only beta carrying the two defects uncovered by the post-b2 CI-incident investigation — bug-123, bug-124 in qa/issue-registry.json — plus the upstream doc-drift corrections surfaced by the Zenn book fact-check audit (PRs #48, #50). No new features; DB schema and MCP tool contract unchanged.
Highlights
- Same-boot FTS recovery rebuild (bug-123, MEDIUM): the bug-118 recovery path recreated stale FTS triggers and flagged an index rebuild, but the rebuild condition never checked that flag — the actual rebuild was silently deferred to the next boot, leaving FTS search on a stale index for the whole recovered session. The rebuild now fires in the same boot that performs the recovery.
- Orphaned-connection interpreter hang (bug-124, HIGH, test-infra): every aiosqlite connection owns a non-daemon worker thread, so a test that re-pointed
database._db = Nonewithoutclose()left a thread that blocked interpreter exit forever — the suite passed in seconds, then the process hung until GitHub's 6-hour job limit. This is how b2 was merged with its test jobs still pending. Fixes: the offending test now goes through the existingclose_db()helper, the helper's docstring carries the doctrine, andci.ymlgained atimeout-minutes: 15backstop so any future hang fails fast instead of burning 6 runner-hours. - Doc drift (PR #48): LMEB expanded to its correct name (Long-horizon Memory Embedding Benchmark),
archive_episode'shistorydescription now states what the field is actually used for (start/end timestamp extraction; the episode embedding comes fromsummary), andcalibrate_threshold's description reflects the separation-based default. README Quick Start now leads with the bundledcpersona-memoryskill install path.
Deliberate non-change: version stays a fix-only beta — the structural gate for the _db = None orphan pattern (class prevention for bug-124) is tracked separately and will ride a later cut.
Verification before this cut: 352 passed / 5 skipped with clean interpreter exit, ruff clean, verify-issues.sh 124 issues / 0 stale / 0 errors; CI green on the bump commit (all four jobs individually confirmed).
Graduation line: 2.5.0b1 → 2.5.0b2 → 2.5.0b3 → (rc) → 2.5.0 final — the line then enters Current; Stable requires a later production-soak certification event (see SUPPORT.md).
🤖 Generated with Claude Code
v2.5.0b2 — beta: post-b1 audit fix bundle (bug-114..122)
Pre-release (2.5 Experimental line — the 2.4 series remains the Stable line, latest v2.4.40).
Fix-only beta: all 10 confirmed defects from the post-b1 comprehensive audit (cross-provider: Gemini finders + Anthropic finders, refute-framed verification, final adjudication) — bug-114..122 in qa/issue-registry.json. No new features; DB schema and MCP tool contract unchanged.
Highlights
- Naive-timestamp UTC anchoring (bug-114, HIGH):
_parse_timestamp_utcranastimezone()on naivedatetime('now')values, assuming system-local time — on any non-UTC host, recall-boost decay and the episode-boundary factor were silently shifted by the local UTC offset (repro-confirmed: JST, 9 hours). Naive DB timestamps are now anchored as UTC at the parse choke points, and ruff DTZ (flake8-datetimez) joins the lint gate so new naive datetime construction is a CI error. - Episode penalty actually ranks (bug-115, HIGH): under the default configuration (rrf fusion + confidence off) the episode-boundary penalty scaled scores but nothing re-sorted — a ranking no-op. Penalised fusion lists now re-sort; cascade stage order is preserved by design.
- Structural gate hardening (bug-120, HIGH): the isolation gate no longer accepts substring tricks (
OR agent_id IS NULL, commented mentions) and now sees interpolated DML targets (f"DELETE FROM {table}"), closing a class of unscoped-DML invisibility. Five new teeth tests. - verify-issues as a CI gate (bug-122): the 122-entry regression registry is now enforced on every PR/master push (it was hand-run only).
- FTS migration recovery (bug-118): FTS-gated migrations skipped while
CPERSONA_FTS_ENABLED=false(stale trigger bodies, missed contamination rebuild) are detected by a trigger-DDL probe and recovered on boot. - Also: mixed-format orphan-episode false positives (bug-116), preview truncation of ref-less injected rows made content unreachable (bug-117), no-persist skeleton shape drift for calibrate/set_recall_precision (bug-119), empty-content memories dropped by any exclude filter (bug-121).
Deliberate non-change: the datetime('now') schema defaults keep their naive (UTC) format — rewriting them offset-aware would mix formats inside one column and reintroduce the lexicographic-comparison hazard bug-116 fixes. The parse-side naive-as-UTC invariant covers old and new rows.
Verification before this cut: 276 passed / 5 skipped (20 new regression tests in test_audit_2500b2.py), ruff clean with DTZ enabled, verify-issues.sh 122 issues / 0 stale / 0 error.
Graduation line: 2.5.0b1 → 2.5.0b2 → (rc) → 2.5.0 final — the line then enters Current; Stable requires a later production-soak certification event (see SUPPORT.md).
🤖 Generated with Claude Code
v2.5.0b1 — beta: comprehensive audit fix bundle
Pre-release (2.5 Experimental line — the 2.4 series remains the Stable line, latest v2.4.40).
Beta graduation of the 2.5.0 internal-stabilization line. A comprehensive pre-release audit (hybrid: 10 Gemini finders + 3 Anthropic deep finders, every finding adversarially verified; then a differential a2-vs-b1 demonstration and a cross-provider regression hunt over the fix delta itself) landed 28 fixes (bug-086..113) — full details in qa/issue-registry.json.
Highlights
- Read/write connection separation (bug-086/087): the read seam owns a dedicated connection, so WAL snapshot isolation actually applies — dirty reads on the shared connection could acknowledge a store as a duplicate of a row that was later rolled back (silent loss).
- Atomic merge
mode='move'(bug-088): the source wipe runs inside the merge transaction — a writer queued on the FIFO lock could previously lose an acknowledged row deterministically. - Durable export/import (bug-091/092/110): exports write via temp + rename (a mid-export fault no longer destroys the previous backup), imports validate the file body against its header across all three record types (truncated backups roll back), and
recall_count/last_recalled_at/created_atsurvive round-trips. - Locked-row contract (bug-098): all fixers carry the
locked = 0guard —check_health(fix=true)can no longer rewrite a locked memory's fields. - γ-visible write dedup + scope-local temporal scoring (bug-106/107): write dedup matches read semantics; a scoped recall's confidence curve is no longer scaled by unrelated buckets.
- Queue drain hardening (bug-089/090/093/109): single-transaction archive with a claim-token delete, failure dicts route into retry, agent wipes purge queue rows and cannot be resurrected.
- Packaging/CI:
skills/ships in the wheel (cpersona/skills/), ruff joins CI as a gate, 30 new regression tests (test_audit_2500b1.py).
Verification before this cut: 335 passed / 5 skipped, ruff clean, verify-issues.sh 113 issues / 0 stale / 0 error; a differential harness reproduced 5 representative bugs live on v2.5.0a2 and confirmed all 5 resolved here.
Graduation line: 2.5.0a2 → 2.5.0b1 → (rc) → 2.5.0 final — the line then enters Current; Stable requires a later production-soak certification event (see SUPPORT.md).
🤖 Generated with Claude Code
v2.5.0a2
Second pre-release on the 2.5.0 internal-stabilization line. Experimental — plain pip install cpersona still resolves to 2.4.40; opt in with pip install cpersona==2.5.0a2.
On top of 2.5.0a1 (behavioural harnesses + connection C-seam):
- isolation_where() unification (#180) — the 3-axis read predicate (agent/project/channel) is built in one helper; the structural isolation gates now require helper derivation and accept no waivers.
- Recall limit re-layering (#190) — the library clamps only to the vector scan window; the agent-facing 100 cap moved to the recall tools' JSON Schema (
maximum: 100). Deep library callers (bench full-ranking) get real depth; the bench--unclamp_limitpatch is retired. - Recall preview tier (#193) — recall responses carry a
refhandle and return content as a 500-char pure-prefix preview (CPERSONA_RECALL_PREVIEW_CHARS,0disables) withcontent_truncated/content_lenmarkers;full_content: trueopts out, and the newget_contentstool (28 tools total) batch-expands refs to full rows. Design:docs/RECALL_PREVIEW_TIER_DESIGN.md. Consumers that inject recall content verbatim should passfull_content: true(safe against older servers).
No DB schema change (SCHEMA_VERSION 13). Verified: 305 passed / 5 skipped, ruff clean, verify-issues 85 issues 0 stale / 0 errors.
🤖 Generated with Claude Code