Skip to main content
Glama
Cloto-dev

CPersona

Official
by Cloto-dev

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
CPERSONA_RRF_KNoRRF smoothing parameter60
CPERSONA_DB_PATHNoSQLite database path./cpersona.db
CPERSONA_SEARCH_MODENoSearch strategy (`rrf` or `cascade`)rrf
CPERSONA_EMBEDDING_URLNoEmbedding server URLhttp://127.0.0.1:8401/embed
CPERSONA_AUTO_CALIBRATENoAuto-calibrate on startupfalse
CPERSONA_EMBEDDING_MODENoEmbedding mode (`http` or `disabled`)http
CPERSONA_CONFIDENCE_ENABLEDNoInclude confidence metadata in resultsfalse
CPERSONA_TASK_QUEUE_ENABLEDNoEnable background task queuefalse
CPERSONA_VECTOR_SEARCH_MODENoVector search moderemote

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
pause_persistenceA

Pause write operations on this MCP server for an opt-in TTL window. While paused, every write tool — store, declare_associations, 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. Blast radius follows session_key (response scope). Pass the same session_key here and on your write calls and the pause covers that key alone (scope: "session"): a session that sends a different key is neither silenced by it nor able to clear it. The key is a partition hint, not a credential — it is compared, never verified — so anyone who sends the same string shares the pause. Omit it and you arm the bucket every keyless caller shares (scope: "process") — on a streamable-HTTP deployment a single process serves every connected client, so a keyless pause silences writes for every other keyless session until resume or TTL elapse, and those sessions get no signal. Under stdio (one process per client) that bucket is the session. 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).

resume_persistenceA

Re-enable persistence immediately, clearing this caller's active no-persist TTL. Returns was_active=true if THIS bucket was paused before the call. It clears only the bucket session_key selects (response scope): with a session_key, your own pause and no other session's; without one, the shared keyless bucket, which on a streamable-HTTP deployment re-enables writes for every other keyless session too.

persistence_statusA

Report whether persistence is currently paused and the TTL remaining (in seconds). It reports the bucket session_key selects (response scope), not the server as a whole: with a session_key it answers for your session only, so paused: false here does not mean no other session is paused. Without one it reflects the bucket every keyless caller shares, which on a streamable-HTTP deployment means paused: true may have been armed by a different keyless session.

get_operating_contextA

Read 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.

storeA

Store 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:, embedded:}, 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, and nodes:{status:'queued'} when the text runs past the embedding window and its overflow-tree nodes were queued for construction — absent when it fits, when the embedding server cannot report tokens, or with the task queue disabled), '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.

declare_associationsA

Declare associative memory after the fact: entities with aliases, and subject–predicate–object relations, recorded verbatim and walked by reconstruct. The server extracts nothing and infers nothing — coverage is exactly what was declared. anchor_ref names the record (mem:<id> / ep:<id>) the declaration is evidenced by: every entity named is recorded as mentioned by it and every relation carries it. Malformed items are reported in dropped and skipped; nothing else in the call is refused for them. retract removes relations by id and mentions by {entity, ref} — the only way a declaration leaves the store. Response: {ok, result:'declared', entities:[{id, name, created}], mentions, relations:[ids], dropped:[{item, reason}], retracted?:{relations, mentions}}. Under pause_persistence nothing is written (result:'skipped', persisted:false).

traverseA

The neighbourhood of a declared entity, as a graph: the entity named, its aliases, the entity -> entity relations declared on it and on what they reach, up to max_hops in either direction, and the refs of the records that mention each entity. Only what was declared (declare_associations, or associations on store); nothing is inferred. No record text: expand a ref with get_contents. entity is a name or an alias, compared after normalization; when it names more than one entity this call can read (a project's and the global pool's), all are starts. ORDER: entities by hops, then by the most recently declared relation that reached them, then by id; mentions by record id; relations most recently declared first. limit bounds both the entities returned and the refs listed per entity. Response: {entity, max_hops, limit, entities:[{id, name, hops, aliases?, mentions?, mentions_omitted?}], relations:[{id, subject, predicate, object, declared_by, declared_at, anchor_ref?}] (subject/object are entity ids from entities; a relation is listed when both ends are), entities_omitted?, bounds?:{omitted:[max_hops | limit]}, reason?:'no_such_entity'}. Relation ids are what declare_associations' retract takes. Records are listed only when this call could read them: the project, channel and source_id filters apply as in recall.

recallA

Recall relevant memories using multi-strategy search (vector + FTS5 + keyword). To answer a question from memory, prefer reconstruct, the recommended way to read it: it returns items that quote the rows supporting them, within a character budget. 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. full_content is itself budgeted (200k chars per response, bug-211): rows past the budget degrade to the preview tier and the response carries full_content_budget_chars (absent when the budget never bites). 2.6 additive: a message whose content the preview cut also carries excerpt — the part of the record that matched the query, at most 800 characters (CPERSONA_RECALL_EXCERPT_CHARS), separate passages joined by ' … ' in text order — and excerpt_basis (blocks: the record's block set; lexical: divided at read time, ranked by shared words; start: the record is one block, so its start). Read the excerpt before deciding to expand a row; content stays the record's start. Absent under full_content and on rows shown whole. v2.5.2 additive: each scored message carries match_reason={signal, score, ...} where signal is the branch the quality gate keyed on (rsf > cosine > rrf; confidence only under CPERSONA_CONFIDENCE_ORDERING=legacy — from 2.6.0a7 an enabled confidence score is returned beside each row but neither orders nor gates) and the remaining keys (cosine / rrf / rsf) surface the internal per-retriever contributions present on that row; prior, when present, is the age weight that ordered the row (CPERSONA_PRIOR_AGE_RATE). 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. A response may carry suggestion (absent otherwise, at most once per session): something the server noticed that only the user can decide — that this scope has grown past the scan window with no coarse index for a time cue to search the rest. Relay its message, and run its fix only if the user agrees.

recall_with_contextA

Recall 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 (full_content shares recall's 200k-char response budget, bug-211); a recalled row the preview cut carries excerpt / excerpt_basis as recall's do. 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. context_field_issues={entries:[{index, fields}]} (absent otherwise) names entries whose declared field was not a string: it was read as absent and the entry merged without it. CPERSONA_EXTERNAL_CONTEXT_MODE=reject refuses such a call instead. 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.

get_contentsA

Fetch 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. Bounded twice: at most 20 refs per call, and a 40,000-character budget across the batch (2.5.4a2) that does not move when CPERSONA_MAX_CONTENT_LENGTH does. Rows are never cut to fit — when the budget is spent the remaining refs come back in deferred (absent otherwise) alongside budget_chars; re-fetch them in a second call. A single row larger than the budget is still returned in full, because this tool is the only path back to a row's complete text. RANGES: a ref may instead be an object that names part of its record -- {ref, node: i} or {ref, node: [first, last]} (inclusive) for overflow-tree nodes, e.g. the node.index of a reconstruct quote and its neighbours, or {ref, span: [start, end]} for characters. Offsets are in the stored text (a memory's content, an episode's summary without the '[Episode] ' label). The item then carries that slice as content and range = {span, content_len, and node + of when nodes were named, or block + of when blocks were}; a span end past the text is clamped and range.span says what was served. A ref may also carry revision, the digest a reconstruct quote's expand hands out for the text its offsets were measured in: a record rewritten since refuses rather than serving different characters under the same numbers. A range that cannot be served exactly is never widened to the whole row: it comes back in unresolved (absent otherwise) as {ref, reason}, reason one of invalid_range, no_current_nodes (the record has no complete node set -- short records have none, and a new long one gets them shortly after store), node_out_of_range, span_out_of_range, no_current_blocks, block_out_of_range, stale_revision. Only the slice counts against the budget.

reconstructA

The recommended way to answer a question from memory (10 items unless count says otherwise). Assemble recall ITEMS from the candidate rows a recall produces: units of memory, each traceable to the canonical rows that support it. Reconstruction means select, order and assign roles -- never compose. No model is called and nothing is summarised: content quotes the item's head claim verbatim -- the parts of its record that matched, filled up to a fixed size -- and expands through head_ref via get_contents. HEAD CLAIM: the most relevant row in the item; if newer versions of that record (same message id in the same stored project) are present, their latest version. When max_evidence cuts an item, the head is kept and the most relevant remaining rows fill the rest. Stored rows are never modified. COUNT IS A CEILING, NOT A FILL TARGET AND NOT A SEARCH DEPTH: base = forced ?? requested ?? server default, effective = min(base, maximum), and 0 <= returned <= effective. Every response states effective_count and returned_count. ONE EXCEPTION, WITH THE BLOCK ARM ON: records only that arm reached are held beside the window, as in recall -- up to the block reservation, after the window's items, each marked admission='reservation' and counted in reserved_count (a held item the budget left out is counted in reserved_omitted). They never take or displace a place in the window, so returned_count may exceed effective_count by reserved_count. A RESPONSE SAYS MORE ONLY WHEN THE SERVER DID SOMETHING OTHER THAN WHAT WAS ASKED: requested_count + count_policy {source, clamped, reason} when the count was clamped or operator-forced; requested_budget + budget_policy when the budget was clamped, raised or forced; effective_budget + used_budget when the budget withheld an item or an excerpt; bounds when a bound dropped rows, was reached, or was lowered by the library ceiling; reconstruction.excluded_without_provenance when rows were excluded. A response without them was served as asked. trace=true returns the full audit every time. Fewer items than the window is a NORMAL result and carries shortfall_reason (no_relevant_evidence / below_quality_threshold / exhausted_candidates); a shortfall is never padded with duplicates, fragments, or a cluster split in two. BREADTH IS SEPARATE FROM COUNT: top_k (candidate depth), max_hops (relation hops) and max_evidence are declared independently and none is derived from count -- changing count alone does not move the candidate id set. WHAT THE RESPONSE ADMITS: bounds.omitted names a bound that DROPPED rows the tool held (max_evidence -- each cut item also counts them in claims_omitted -- or max_hops: a declared relation was left unfollowed); bounds.reached names a bound that was only MET (top_k: retrieval returned as many rows as it was allowed; max_evidence: an entity the walk reached is mentioned by more records than were read -- whether more lay beyond is not known). Both are absent when empty. quote_selection: lexical_only appears when no query embedding was available and nodes were ranked by shared trigrams alone; an item whose cut quote is merely the start of its record carries node_unavailable (no_nodes, or not_current when nodes exist but are partial or another model's). ABSENCE IS NOT A VERDICT: a response without these fields does not say its items suffice to answer, that the whole store was searched, or that the rows were checked for contradiction -- conflicts detects one narrow case only. BREADTH BEFORE DEPTH: budget bounds the characters of quoted text -- each item's content and its excerpts -- where count bounds how many items. The quoted text is one fixed sequence: every head in item order, then each item's most relevant remaining excerpt, then the next, and the response is its longest prefix that fits. An excerpt the budget cannot carry is omitted (counted in excerpts_omitted, absent when zero; its claim and ref stay); an item is dropped only when its head does not fit, with shortfall_reason budget_exhausted. Raising the budget alone never removes an item or an excerpt. When budget is omitted the default is the configured default or the window's quote sizes summed, whichever is more, so a count you name is not cut by a budget you did not set; a budget you do name is taken as given. QUOTES: content quotes the head claim and each excerpts[] entry quotes another retained claim, most relevant first; all are verbatim. The head quote is the record's passages that matched the query, taken in ranking order while they fit the item's quote size -- CPERSONA_RECONSTRUCT_QUOTE_CHARS (800) for the first CPERSONA_RECONSTRUCT_FULL_QUOTES (5) items, CPERSONA_RECONSTRUCT_TAIL_QUOTE_CHARS (400) after them -- and shown in text order, passages with text between them joined by ' … ' -- the recall excerpt's filling; ranges gives their character spans in the record, and content_len appears when the record is longer than its quote (trace=true adds quote_basis and content_truncated). A best passage longer than the size is cut and carries context_incomplete and expand ({ref, span}) for get_contents. Excerpts of the other claims -- and the head when CPERSONA_RECONSTRUCT_QUOTE_CHARS=0 -- are quoted as before 2.6 and cut as the preview tier cuts: a long record with overflow-tree nodes is quoted from the node that best matches the query (rank by embedding similarity and by shared character trigrams, fused), and node gives its index, node count and character span in the stored text; a record without nodes is quoted from its start. READ FURTHER IN STEPS, SMALLEST FIRST: a node quote is the start of a node several times its length, and an item whose quote was cut carries expand -- pass it to get_contents as it is to read the rest of that node. If that is not enough, read its neighbours with {ref, node: [index - 1, index + 1]}. Pass the bare ref, the whole record, only when the parts did not answer: a record can be tens of times a node. Nodes are read after items are chosen, so they never change which items come back or their order. No relevance score is returned. ITEM SHAPE (the same for every item): claims carries one entry per retained row, newest first, each with ref, as_of, why (the key that admitted the row; relation:<predicate> when a declared relation did; absent when the search returned the row itself, seed), hops when the relation walk reached the row, and roles when it has any -- sort by as_of for a chronological view. excerpts and excerpts_omitted are absent when empty. trace=true adds reconstruction (policy, candidate / cluster / selected counts), candidate refs, clusters and, for each record quoted by node, node_order -- its best few node indices, best first, as places to read next (an order, not a confidence). Gate fallback remains visible even when count is filled; zero count states count_zero. Retrieval degradation and update notices are delivered unchanged. If the library ceiling clamps top_k, bounds.effective_top_k reports the applied bound, including when the candidate pool is empty. independence_reason says why this is a separate item (absent when nothing joined it to another: singleton); conflicts appears only when two rows cannot be ordered. ROLE DIRECTION: roles[].role names what the REFERENCED row is to this claim (the ref is the subject, the claim is the object): the referenced episode SUPPORTS this claim, the referenced newer row SUPERSEDES it. The vocabulary is fixed at supports / supersedes / corrects / qualifies / contradicts / temporal_predecessor. The server derives supersedes (same message id, time order) and supports (episode span containment); any role word can also be DECLARED as a record -> record relation (declare_associations), whose subject is the ref. Ignore a role you do not know. BUNDLING KEYS are deterministic and never semantic: same message id within the same stored project (unknown project context cannot establish identity), containment in a candidate episode's time span, and adjacent timestamps FROM THE SAME SOURCE within the same project and channel, with the entire burst bounded by the time window (source alone is not a key -- in a single-agent store it is constant and would fold the whole pool into one item), and a declared record -> record relation between two candidates. Sharing a declared entity does not bundle. DECLARED ASSOCIATIONS (declare_associations, or associations on store) are read here and nowhere else, and change nothing when none apply: the names and aliases of entities the query mentions are added to the LEXICAL search only (the query's meaning, and so the vector search, is unchanged; the extra match is a vote, not a pass through the quality gate); and from each item's candidates the relation walk follows declared entity -> entity relations, either direction, up to max_hops, adding records that mention an entity it reached as evidence inside that item -- never as an item, never twice in one response, kept fewest hops first, then most recently declared relation, then lowest record id. This tool is additive: the recall contract is untouched.

get_profileC

Get the current profile for an agent.

update_profileA

Save a pre-computed agent profile to the database. The text passes through the same sanitizer as store, against the profile's own ceiling: it is capped at 2000 characters (CPERSONA_MAX_PROFILE_LENGTH) and the response carries truncated:true when the cap bit — branch on it, the discarded remainder is not stored anywhere else.

archive_episodeA

Archive a conversation episode with pre-computed summary, keywords, and resolved status. All LLM processing is performed by the caller. A summary that runs past the embedding window adds nodes:{status:'queued'} to the response, as on store.

list_memoriesA

List recent memories for an agent (for dashboard display). bug-385: limit is clamped to 500 rows. When the caller asked for more and rows past the cap exist, the response carries budget_rows (the cap), so a capped listing can be told from one that reached the end of the data — reach the rest through export_memories or a narrower filter, not a larger limit. bug-255: within that cap the response holds a 1,000,000-character content budget. Rows are returned newest-first and none is dropped by the budget; once it is spent, later rows LONGER than the preview cap (CPERSONA_RECALL_PREVIEW_CHARS, default 500) degrade to a pure prefix with content_truncated/content_len and a ref that get_contents expands under the row's own agent_id (in an all-agents listing, pair the ref with the row's agent_id field). budget_chars appears iff at least one row was degraded. The effective ceiling is the budget plus one whole row plus the degraded rows' prefixes, so it scales with the preview cap; preview cap 0 disables trimming and the budget with it.

list_episodesA

List archived episodes for an agent (for dashboard display). bug-385: limit is clamped to 200 rows. When the caller asked for more and rows past the cap exist, the response carries budget_rows (the cap), so a capped listing can be told from one that reached the end of the data — reach the rest through export_memories or a narrower filter, not a larger limit. bug-255: within that cap the response holds an 800,000-character budget across summary and keywords together, with the same degradation and ceiling semantics as list_memories — rows past the budget that exceed the preview cap carry pure prefixes plus summary_truncated/summary_len and keywords_truncated/keywords_len; budget_chars appears iff at least one row was degraded. Their ref expands the summary via get_contents (under the row's own agent_id); a full keywords string is only available through export_data.

delete_agent_dataA

Delete ALL data (memories, profiles, episodes) for a specific agent. Used by kernel during agent deletion.

calibrate_thresholdA

Auto-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.

set_recall_precisionA

Set 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.

get_recall_precisionA

Read 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.

delete_memoryB

Delete a single memory by ID. Ownership is enforced when agent_id is provided.

delete_episodeA

Delete a single episode by ID. Ownership is enforced when agent_id is provided.

update_memoryA

Update 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. A new text that runs past the embedding window gets nodes:{status:'queued'}, as on store.

lock_memoryA

Lock a memory to prevent deletion and editing. Ownership enforced when agent_id provided.

unlock_memoryA

Unlock a memory to allow deletion and editing. Ownership enforced when agent_id provided.

get_queue_statusA

Get the status of the background task queue (pending tasks, retry config).

export_memoriesA

Export memories, episodes, and profiles to a JSONL file for backup or portability.

import_memoriesA

Import memories, episodes, and profiles from a JSONL file. Idempotent: memories deduplicate on msg_id (and on content within a project/channel), episodes on their summary within a project/channel.

merge_memoriesA

Merge 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).

check_healthA

Check memory database health (36-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); the one exception is dedup_msg_id_index, whose repair blanks colliding msg_id values under every agent because the UNIQUE index it restores is a global schema object — with an ACL configured that repair demands read-write on '*', so exclude it via checks to stay agent-scoped. critical file-integrity findings are report-only. Two repairs are lossy and irreversible, each against its own cap: oversized memories are cut to CPERSONA_MAX_CONTENT_LENGTH (default 16000 since 2.5.4a2) and the agent's profile row to CPERSONA_MAX_PROFILE_LENGTH (default 2000), keeping the start. Lower either cap and a fix run shortens rows that were within the old one. Some repairs are bounded per run (source canonicalisation classifies at most 10000 rows); a fix response carrying remaining > 0 with a re-run hint has NOT converged — run fix again until remaining stops decreasing. Use checks parameter to run a subset — an unknown name is rejected (ok=false) rather than silently running nothing, and every response echoes checks_run. 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. Read status as a verdict on what is IN the database, not on whether the pipeline that fills it is working: a corpus where every embedding is NULL is internally consistent, so it scores healthy while semantic recall is dead. Nothing here contacts the embedding backend unless fix=true — on a report-only run the liveness findings cannot appear at all, and their absence is not evidence the backend answered. The null_embedding finding carries the reason its repair cannot run; read that before reading status.

get_session_findingsA

Pull the storage-integrity findings on demand (SuperAuditor v1 pull contract, docs/SUPERAUDITOR_STANDARD.md) instead of reading them off check_health. Same detector as check_health(fix=false) over the WHOLE database, delivered as findings: each carries kind (the finding's name: a check registry name, or an escalation tier this seam mints for a runner that grades its own severity, e.g. null_embedding_pipeline_down — a tier is NOT a registry name), check (the registry name that produced it, so check_health(checks=[finding['check']]) re-runs exactly that probe) and a static per-kind severity (critical = the read contract is broken now / warn = two stored facts contradict / info = an observation). check_health's own instance verdict rides along as health_severity; a probe that raised is reported as kind check_crashed instead of failing the pull, so a partial result says which probe is missing. Read-only, never repairs. NOT free, though: the registry runs unfiltered, which includes two whole-database reads (the FTS5 integrity-check over both indexes, and PRAGMA quick_check over the file), so every pull is O(database) on a channel meant to be pulled once a session — budget it by call frequency. There is deliberately no cheap subset: choosing which probes run would be choosing which forgotten state stays forgotten. Findings are NOT filtered by agent_id or project_id — the channel surfaces forgotten state, and slicing it by the caller's bucket would hide exactly the rows that were forgotten (scope a repair with check_health(agent_id=...)). Honest caps: findings holds at most per_kind_limit rows per kind, capped_kinds names every kind that had more (observed, not inferred from count == limit), total and the counts describe the RETURNED set only, and per_kind_limit echoes the limit applied. summary restates the same trimmed set in prose (pass include_summary=false to skip paying for it). On a shared remote transport with no session_key declared the response carries identity_shared: true — this server has no session-scoped probes, so the key is a partition hint, not a filter. _meta.server_version identifies the running instance.

deep_checkA

Deep 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, unnormalized_content, embedding_norm — apply those decisions via merge_memories / delete_memory / calibrate_threshold / update_profile. Use checks parameter to select specific checks.

migrate_channel_axisA

Re-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).

check_updateA

Report whether a newer release of this server exists, and — only if you ask — install it. The check itself runs ONCE per process start, in a background task that nothing waits on, and its verdict is cached for 24h (CPERSONA_UPDATE_CHECK_INTERVAL_SECONDS) in a file beside the database; a bare call here reads that verdict and reaches neither the network nor the disk. state is one of: ok (running the newest release) / newer (a newer final release exists — pre-releases are never proposed) / yanked (every file of the RUNNING version has been withdrawn on PyPI; reason carries the publisher's text) / unlisted (this version is not on the index at all — a development checkout; not a defect) / unknown (no check has completed, e.g. no network) / disabled. install names how this process was installed (uvx / pip / checkout / unknown) and the exact command that would update it. refresh=true performs the fetch now (3s budget) and updates the cache. apply=true runs that command as an argv list (never a shell), returning exit_code and the last 40 lines of output — supported for pip and checkout installs only; under uvx the environment is a cache entry keyed by the launch arguments, so the update belongs in your client's config (uvx cpersona@latest), and an install here would be discarded on the next launch. A checkout parked at a tag (detached HEAD) is likewise refused before anything runs, and answers with the git fetch --tags && git checkout <tag> form to use instead. Updating is NEVER automatic and never a side effect of any other call. A RESTART IS ALWAYS REQUIRED afterwards: this process keeps serving the old code until it is replaced. Unaffected by pause_persistence — an install writes no memory row, so a no-persist session can still repair a withdrawn version. Set CPERSONA_UPDATE_CHECK=false to disable the feature entirely: no fetch, no cache, no notice on recall or check_health, and this tool answers state=disabled.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

TDQS

A3.7/5.0

Scored across 34 tools

Disambiguation4/5

Most tools target clearly distinct operations, and the very detailed descriptions carefully separate recall/reconstruct/recall_with_context and check_health/get_session_findings/deep_check. However, the recall variants and the two overlapping health-reporting seams mean an agent must read carefully to choose correctly.

Naming Consistency5/5

All tool names use consistent snake_case, overwhelmingly in a verb_noun pattern (store, recall, get_profile, delete_memory, pause_persistence, set_recall_precision). There is no camelCase or mixed-convention drift, and the few bare verbs (recall, store) remain readable and conventional.

Tool Count2/5

34 tools is well above the 3–15 sweet spot and past the 25-tool threshold, making the surface heavy for an agent to navigate even if most tools have distinct purposes. Several capabilities are split into read/write/status trios (pause/resume/persistence_status, set/get_recall_precision), which adds further bulk.

Completeness4/5

The surface covers memory CRUD, episodes, profiles, declared associations, recall/reconstruction, health checks, persistence control, precision tuning, import/export/merge, migration, and update checks — a broad lifecycle. Minor gaps remain, such as no standalone delete_profile, but core workflows are well covered.

Maintenance

ActivityActive
ResponsivenessNo issues