Personal Recall Plugin
The Personal Recall plugin lets the assistant find and reread **the user's own
earlier conversations**. It works in both directions: two read-only tools the
agent calls when a question points back at earlier work, and — on by default —
a turn-start pass that offers matching conversations before the agent thinks to
ask for them.
It is not the organizational memory, and the two are
independent. Agentic Memory curates team-owned notes, written by a background
model and shared with everyone; Personal Recall reads nothing but the raw
conversations of the person typing, writes nothing at all, and makes no model
call. A deployment can run either, both, or neither.
What it can see
Scope rides on the same conversation-read checks every other conversation read
in the app already uses, so there is no second policy to keep in sync:
- Only the calling user's own conversations. Never another user's — an id belonging to someone else is refused rather than read.
- Only conversations that still exist. Soft-deleted ones are invisible.
- The current workspace by default. Both the tools and auto-recall stay inside the workspace of the conversation they run in, unless asked to look wider (
workspace: "all"on the tool, All my workspaces in the user's settings).
Ranking comes from the event store's existing FTS5/BM25 index — the same one
behind conversation search in the sidebar. Nothing is copied to disk, no
embeddings are built, and there is no curator and no background job.
The tools
| Tool | Parameters | Returns |
|---|---|---|
recall_conversations | query (required); since (YYYY-MM-DD or a full ISO timestamp); workspace (a workspace id, or all); limit (1–50, default 10) | Matching conversations — title, date, highlighted snippet, and the id to read |
read_conversation | conversation_id (required); turns ("3", "3-5", or omitted for the whole conversation); detail (summary, the default, or full) | The rendered turns of that one conversation |
Every hit is a Markdown link to /c/{id}, the app's own conversation route, so
the tool card in the transcript links straight to what was found — clicking a
result is how a user checks what the assistant just read.
Both tools are token-bounded. read_conversation truncates each turn (tightly
under summary, generously under full) and caps its whole output, so an
unbounded range on a very long conversation cannot flood the context window;
narrow turns when that cap gets in the way.
The agent is told to reach for them when the user refers to earlier work or a
previous decision — "like last time", "as we discussed" — and not to go
fishing otherwise.
Recall at the start of a turn
With auto_recall_enabled (the default), the plugin also pushes: at the start
of a turn it looks for the user's own earlier conversations that the current
message is about, and puts a short linked list just ahead of the query, followed
by a transcript card naming what it injected. The note is a **menu, not an
answer** — the agent still has to call read_conversation to use one, and is
told the list was found by keyword and may be irrelevant.
Most turns recall nothing, by design. BM25 is a ranking function, not a
relevance test: it returns its top-N for any query, so a floorless gate would
inject on nearly every turn. The floor here is on the query instead, and it is
derived from the user's own corpus rather than a word list:
- A term must be rare but present. A word in more of the user's conversations than the df ceiling is a stopword for that user and admits nothing; a word nobody has written about can match nothing. No salient term → no recall, which is the common case.
- Each salient term gets its own ranked probe, and the lists are fused, so a conversation two independent rare terms both reach outranks one that a single term found. A single-term hit is admitted only when that term is very rare (at or below
rare_df). - Recency is a prior. The fused score decays with a 60-day half-life toward a floor, so an old but strongly matched conversation is down-weighted rather than erased.
The pass runs once per turn, for the main agent's first step only. It is skipped
for subagents, for scheduled runs (whose "query" is a fixed template that would
recall the same handful of conversations forever), and it never returns the live
conversation, which is in the index and would otherwise match its own query
every turn. A conversation already listed in this chat is not listed again.
What the user controls
Three settings under Settings → Extensions, each with the deployment's own
default filled in:
| Setting | Values | Default |
|---|---|---|
| Recall earlier conversations automatically | on / off | on |
| How eagerly to recall | Focused · Balanced · Broad | auto_recall_default_sensitivity |
| Where to look | This workspace only · All my workspaces | auto_recall_default_scope |
Sensitivity is one dial that moves the whole gate coherently, rather than four
numbers nobody could tune against each other:
| Sensitivity | df ceiling | Lone term admitted at df ≤ | Conversations listed | Token budget |
|---|---|---|---|---|
| Focused — only strong matches | 10 | 2 | 2 | 250 |
| Balanced — the default | 25 | 3 | 3 | 400 |
| Broad — more suggestions, more noise | 60 | 5 | 5 | 700 |
Turning the first setting off leaves the tools in place: the agent can still
pull, it is just not pushed to.
Configuration
Enabled in the default shipped config, which carries this block:
plugins:
- module: codumentor.plugins.personal_recall
class: PersonalRecallPlugin
args:
enabled: true
auto_recall_enabled: true
auto_recall_default_sensitivity: balanced
auto_recall_default_scope: workspace
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Whether the plugin is active. |
target_agents | kind / role / list | all | Which agents get the recall tools. Both are reads, so there is no separate write tier. |
auto_recall_enabled | bool | true | Offer turn-start auto-recall at all. false removes it for every user here and hides its settings; the tools are unaffected. |
auto_recall_default_sensitivity | focused \ | balanced \ | broad |
auto_recall_default_scope | workspace \ | everywhere | workspace |
The rest are pins, not defaults. Each resolves as *shipped preset ←
operator ← user*, and setting one overrides that parameter for every
sensitivity row in the deployment while the user's choice still moves
everything else. Leave them unset — the default — to follow the user.
| Parameter | Type | Default | Pins |
|---|---|---|---|
auto_recall_df_ceiling | int ≥ 1 | unset | How common a term may be and still count as distinctive. |
auto_recall_rare_df | int ≥ 1 | unset | How rare a term must be to admit its hits uncorroborated. |
auto_recall_max_hits | int 1–10 | unset | How many conversations one note may list. |
auto_recall_token_budget | int ≥ 50 | unset | Ceiling on the injected note, in estimated tokens. |
auto_recall_recency_half_life_days | float > 0 | 60 | Fused-score half-life. Not part of the sensitivity rows: it describes the corpus, not an appetite. |
A deployment whose corpus it actually knows something about is the reason pins
exist — a term appearing in 40 conversations may genuinely be distinctive here.
Everywhere else, the preset rows are better than a hand-picked combination.
Notes
- A pin is deployment-wide. It overrides the chosen row for every user, so a pinned
auto_recall_max_hits: 5makes Focused list five conversations too. Pin the one parameter you have evidence about, not the set. - How often does it abstain?
capabilities()["personal_recall"]carries per-instance counters — attempts, injections, and abstentions by reason (no_salient_terms,uncorroborated,no_hits,all_seen, …). That ratio is the number to watch: an auto-recall that fires on nearly every turn is misconfigured, not eager. - Nothing is stored. No new tables, no Markdown copies, no embeddings — the per-user settings row is the only thing this plugin writes.
- No web bundle. The tool calls render on the generic tool card and the recall card on the UI's generic plugin renderer, so nothing ships under
ui/plugins/. - For plugin authors: an injected recall emits the conversation event
plugin.personal_recall.recalled, carrying the listed conversations and their links. - Ships in Community and Full editions.
- Design and phasing:
doc/core-dev/design/experience-distillation.md§4 (Phase 0 the tools, Phase 0.5 auto-recall).