archive_episode
Archive a conversation episode with pre-computed summary, keywords, and resolved status. All LLM processing is performed by the caller.
Persistent memory for an assistant in a single SQLite file, recalled by hybrid vector, full-text and keyword search.
LABSCO SUMMARY
The FTS5 and keyword layers catch what embeddings miss — identifiers, error strings, exact names — and the trigram index means it works on Japanese and other scripts without spaces. Isolation is built in: agent_id, project_id and channel let several agents share one file without reading each other's memories.
An MCP memory server. It stores facts, session summaries and an accumulated profile in one local SQLite file and retrieves them with a fused vector, FTS5 and keyword search. It never calls a generative model itself — your agent writes the summary and hands over the result.
Python 3.11 or newer; clients run it with uvx cpersona over stdio. Point CPERSONA_DB_PATH at an absolute path so the database does not follow the client's working directory. Vector search needs an embedding server: set EMBEDDING_MODE to http and EMBEDDING_HTTP_URL to its endpoint (CPERSONA_EMBEDDING_MODE and CPERSONA_EMBEDDING_URL are the prefixed forms and win if both are set) — without one it falls back to the lexical layers and says so. CPERSONA_RECALL_MODE picks the fusion strategy, rrf by default.
One command — uvx cpersona
USE CASES
Store facts as they come up, then recall them merged with the current conversation next time.
Archive the conversation with a summary and keywords your agent computed, then list the episodes to confirm it landed.
Recalibrate the threshold from the data, then set the precision level and read back the resolved weight.
Export to JSONL and import it on the other side; duplicates are dropped by message id.
Run the check registry, then the heuristic pass for stale profiles and orphaned episodes.
Counted once with only startup setup. Other versions, modes or optional credentials may expose a different list.
archive_episodeArchive a conversation episode with pre-computed summary, keywords, and resolved status. All LLM processing is performed by the caller.
calibrate_thresholdAuto-calibrate the vector search threshold from the null (random-pair) cosine distribution. Samples random memory pairs and places the threshold ABOVE the null mean so unrelated pairs are rejected. method='separation' (default) learns the operating point from two populations — null pairs vs temporally-adjacent same-session positives (nearest-neighbour fallback when too few exist); method='percentile' uses a quantile of the null distribution (robust to anisotropic models such as bge-m3); method='zscore' uses mean + z*std. No labels used, purely statistical. Adapts to both embedding model and corpus characteristics.
check_healthCheck memory database health (25-check registry, each issue tagged with severity critical/warn/info). Detects contamination, duplicates, oversized content, embedding issues, FTS integrity (count + content-level), schema version/object drift (missing UNIQUE indexes or FTS triggers), SQLite file integrity, project_id naming drift, invalid JSON/timestamps, timestamp format drift, stale tasks, missing profiles, empty content, invalid/anonymous sources. Returns storage stats incl. project_id/channel distributions. Set fix=true to auto-repair (agent-scoped, locked-safe); critical file-integrity findings are report-only. Two repairs are lossy and irreversible: oversized memories AND the agent's profile row are cut to CPERSONA_MAX_CONTENT_LENGTH (default 2000 characters), keeping the start. Lower that cap and a fix run shortens rows that were within the old one. Use checks parameter to run a subset. The verdict is status: healthy / degraded / unhealthy, derived from severity counts (info never degrades). The pre-2.5.2b1 healthy boolean (len(issues) == 0) is gone — it reported False for an info-only database that status called healthy; read issues / severity_summary for the underlying counts.
deep_checkDeep heuristic analysis of memory data quality. Detects issues requiring recovery or judgment (anonymous sources, short/trivial content, stale profiles, orphaned episodes, stale threshold calibration, embedding-space near-duplicate pairs as merge candidates). fix=true applies repairs for: anonymous_source, short_content. Report-only (fix is accepted and ignored): stale_profile, orphaned_episodes, calibration_staleness, near_duplicate — apply those decisions via merge_memories / delete_memory / calibrate_threshold / update_profile. Use checks parameter to select specific checks.
delete_agent_dataDelete ALL data (memories, profiles, episodes) for a specific agent. Used by kernel during agent deletion.
delete_episodeDelete a single episode by ID. Ownership is enforced when agent_id is provided.
delete_memoryDelete a single memory by ID. Ownership is enforced when agent_id is provided.
export_memoriesExport memories, episodes, and profiles to a JSONL file for backup or portability.
get_contentsFetch full, untrimmed content for recall preview refs. Use after a preview-tier recall to expand only the rows that matter instead of opting the whole recall out with full_content=true.
get_operating_contextRead the server-served operating context (v2.5.1): the operator-owned doctrine distributed to every connected client. Without arguments returns the preview tier — context_revision, instructions_summary, project_id registry (+ enforce mode), @auto defaults, and doctrine section names. Pass section to fetch one section's full body. Read-only: the context is edited by the operator on the filesystem (~/.cpersona/operating-context.toml), never via MCP.
get_profileGet the current profile for an agent.
get_queue_statusGet the status of the background task queue (pending tasks, retry config).
get_recall_precisionRead an agent's effective recall precision (knob 3) — the read-back companion to set_recall_precision. Returns the resolved specificity weight (beta) and its named precision level (strict / balanced / lenient, or 'custom' for a raw beta), and flags whether the value is a per-agent override or the global CPERSONA_RECALL_PRECISION default (overridden + global_precision / global_beta). Read-only: it never recalibrates and never persists, so a UI can load the current setting, let the user edit it, and write it back instead of the control being write-only.
import_memoriesImport memories, episodes, and profiles from a JSONL file. Idempotent via msg_id deduplication.
list_episodesList archived episodes for an agent (for dashboard display).
list_memoriesList recent memories for an agent (for dashboard display).
lock_memoryLock a memory to prevent deletion and editing. Ownership enforced when agent_id provided.
merge_memoriesMerge memories, episodes, and profiles from one agent into another. Atomic one-shot equivalent of export→import without intermediate files. Strategy 'skip' deduplicates by msg_id (memories) and summary (episodes).
migrate_channel_axisRe-channel bridge-type memories to their concrete channel (knob2 v2 default flip prep). Memories the kernel filed under the bridge type ('discord') are rewritten to the concrete channel recovered from the stored session_id ('{channel_id}:{user_id}:{chunk}' | '{channel_id}:shared' → channel_id), so per-channel recall can match them. Non-destructive (only the channel column changes) and idempotent (re-running is a no-op once moved). dry_run=true (default) reports the recoverable count, the channels that would be recovered, and an unrecoverable bucket (channel='discord' rows with no snowflake session_id) without mutating. globalize_unrecoverable=true moves the unrecoverable bucket to channel='' (global, matched by every channel-scoped recall) so the flip orphans nothing; default false (report only).
pause_persistencePause write operations on this MCP server for an opt-in TTL window. While paused, every write tool — store, archive_episode, update_memory, delete_memory, delete_episode, delete_agent_data, lock_memory, unlock_memory, update_profile, import_memories, merge_memories, calibrate_threshold, set_recall_precision — returns a no-op response carrying persisted: false, dry_run: true and a reason (with the TTL remaining) instead of writing to the database. persisted: false is the authoritative signal: branch on it, not on an id. Where the success shape has an id, it reads "no-persist" (store, archive_episode); action-specific id keys (deleted_id / updated_id / locked_id / unlocked_id / episode_id) are blanked to null so a truthy echo cannot read as success. migrate_channel_axis is gated differently — it is forced to dry_run and reports repairs_skipped rather than returning a skipped-response, so it carries no persisted key. check_health and deep_check are not blocked but downgrade to fix=false (they answer with repairs_skipped: true). Read tools (recall, list_*, get_profile, etc.) still answer normally, except that recall suppresses its recall_count / last_recalled_at bump — a write that would otherwise move ranking state during a paused session. The pause is PROCESS-WIDE, not per-session (response `scope`: "process"): the flag is one module-global on the server process. On a streamable-HTTP deployment a single process serves every connected client, so pausing here silences writes for ALL connected sessions until resume or TTL elapse — and the other sessions get no signal. Under stdio (one process per client) it is effectively session-scoped. This affects only this MCP server (cpersona); call cscheduler's pause_persistence too if you want both paused. Use for benchmarking, AB testing, or ephemeral exploration where memory contamination must be avoided. Default TTL: 1800 seconds (30 minutes); upper bound: 86400 seconds (1 day).
persistence_statusReport whether persistence is currently paused and the TTL remaining (in seconds). The reported state is PROCESS-WIDE (response scope: "process"): on a streamable-HTTP deployment it reflects the one flag shared by every connected session, so paused: true may have been set by a different session.
recallRecall relevant memories using multi-strategy search (vector + FTS5 + keyword). Message content is returned as a preview tier by default — expand selected rows with get_contents(refs), or opt out wholesale with full_content=true. v2.5.2 additive: each scored message carries match_reason={signal, score, ...} where signal is the branch the ranking / quality gate keyed on (confidence > rsf > cosine > rrf) and the remaining keys (cosine / rrf / rsf) surface the internal per-retriever contributions present on that row. Unscored rows (cascade FTS/keyword) omit match_reason. A response carrying gate_fallback=true (absent otherwise) means every candidate fell below the quality gate and the below-gate lexical matches were returned instead of an empty result — treat them as low-confidence.
recall_with_contextRecall memories and merge with external conversation context. Automatically deduplicates, sorts chronologically, and returns a unified list. Replaces separate recall + manual merge in the caller. Content is preview-tiered by default — see recall's full_content / get_contents. Every external_context entry's content filters the recall (the caller already holds that text), but only role=user / role=assistant entries are merged into messages. When entries of other roles are present the response carries context_filter_only={roles:[...]} — those entries filtered the recall without appearing in the output, whether or not they dropped a memory this time. gate_fallback=true (absent otherwise) is forwarded from the underlying recall: every candidate fell below the quality gate and the below-gate lexical matches were returned instead of an empty result — treat them as low-confidence.
resume_persistenceRe-enable persistence immediately, clearing any active no-persist TTL. Returns was_active=true if persistence was paused before this call. The flag is PROCESS-WIDE (response `scope`: "process"): on a streamable-HTTP deployment this re-enables writes for every connected session sharing the process, not just the caller's.
set_recall_precisionSet an agent's recall precision (knob 3) and recalibrate its quality gate. precision = strict | balanced | lenient maps to a specificity weight beta of 2.0 / 1.0 / 0.5 in the gate separation objective (sensitivity + beta*specificity): strict sits the gate higher (fewer contaminants, more misses), lenient lower (fewer misses, more contaminants). A raw beta > 0 overrides the named level; an empty precision with beta <= 0 clears the per-agent override and returns the agent to the global CPERSONA_RECALL_PRECISION default. The gate is recalibrated at the new beta immediately and persisted, so the change is live without a restart. Precision is a per-agent setting, not a per-recall argument: the gate threshold is precomputed on the separation curve at a fixed beta, so this tool recalibrates once instead.
storeStore a message in agent memory for future recall. Every response carries result — the one field to branch on: 'stored' (a new row was written; {ok:true, result:'stored', id:<row-id>, embedded:<bool>}, embedded true iff a local blob was persisted or the remote index push succeeded — false under EMBEDDING_MODE=none; the response also carries truncated:true when content exceeded the length cap and was shortened), 'skipped' (nothing written and nothing wrong: {ok:true, result:'skipped', reason:...}; the msg_id / content dedup branches echo the pre-existing row's id, the OR IGNORE fallback reason='duplicate (unique index)' omits id by design — TOCTOU seam), or 'rejected' (nothing written because the request was refused: {ok:false, result:'rejected', reason:...} — empty content, content that sanitizes to empty, or an operating-context project_id refusal, which also carries error). Note for pre-2.5.2b1 callers: ok is no longer unconditionally true, and skipped:true is gone — a rejection used to look like a success. reason is human-readable, not a stable machine token. Under pause_persistence the write is skipped (result:'skipped') and the response carries persisted:false (id:'no-persist', embedded:false) — branch on persisted to tell a paused write apart from a dedup hit.
unlock_memoryUnlock a memory to allow deletion and editing. Ownership enforced when agent_id provided.
update_memoryUpdate memory content by ID. Rejects if memory is locked. Ownership enforced when agent_id provided. The new content passes through the same sanitizer as store: it is capped at the content length limit (the response carries truncated:true when the cap bit) and [Memory from ...] annotations are stripped, so content consisting only of those is refused rather than written as an empty row.
update_profileSave a pre-computed agent profile to the database.
BEFORE YOU INSTALL
29 tools when we connected.
uvx on PATH
CONTEXT COST
Rated by the size of the 29 tool definitions that ride along on every turn, not by how many there are
INSTALLATION
uvx cpersona{
"mcpServers": {
"cpersona": {
"command": "uvx",
"args": [
"cpersona"
],
"env": {
"CPERSONA_DB_PATH": "/home/<YOUR_USERNAME>/.claude/cpersona.db",
"EMBEDDING_MODE": "http",
"EMBEDDING_HTTP_URL": "http://127.0.0.1:8401/embed"
}
}
}
}/home/<YOUR_USERNAME> is your own home directory — CPERSONA_DB_PATH should be an absolute path. The two EMBEDDING_ lines point at a local embedding server on port 8401; start one first with uvx --from "cembedding[onnx]" cembedding-download-model --model jina-v5-nano, then EMBEDDING_PROVIDER=onnx_jina_v5_nano uvx --from "cembedding[onnx]" cembedding. No embedding server? Drop those two lines (or set EMBEDDING_MODE=none) and cpersona runs on full-text and keyword search.
claude mcp add-json cpersona '{"type":"stdio","command":"uvx","args":["cpersona"],"env":{"CPERSONA_DB_PATH":"/home/<YOUR_USERNAME>/.claude/cpersona.db","EMBEDDING_MODE":"http","EMBEDDING_HTTP_URL":"http://127.0.0.1:8401/embed"}}' -s user/home/<YOUR_USERNAME> is your own home directory — CPERSONA_DB_PATH should be an absolute path. The two EMBEDDING_ lines point at a local embedding server on port 8401; start one first with uvx --from "cembedding[onnx]" cembedding-download-model --model jina-v5-nano, then EMBEDDING_PROVIDER=onnx_jina_v5_nano uvx --from "cembedding[onnx]" cembedding. No embedding server? Drop those two lines (or set EMBEDDING_MODE=none) and cpersona runs on full-text and keyword search.