pi-memoria 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,366 +1,115 @@
1
- # memoria
2
-
3
- Agent-first long-term memory for [pi](https://github.com/badlogic/pi-mono).
4
-
5
- memoria gives a coding agent a memory it can actually rely on: a plain-markdown
6
- library on disk, a prose briefing that is loaded into every session, and
7
- automatic recall that searches the whole library on every prompt — in well under
8
- a millisecond.
9
-
10
- - **Nothing is lost behind an API.** Every memory is a markdown file you can
11
- read, edit, grep, diff and version.
12
- - **Past conversations are searchable too.** When the library has nothing, the
13
- agent can search the raw transcripts of earlier sessions before saying it does
14
- not remember.
15
- - **Recall is automatic.** Relevant notes are injected before the model answers,
16
- so it does not have to remember to ask.
17
- - **Broad topics, never fact files.** `likes-apples.md` is a defect; facts
18
- accumulate in `food-preferences.md`.
19
- - **Names are handles.** Aliases make every nickname, full name or former name
20
- resolve to the same note, and notes can be re-filed later without breaking
21
- references.
22
- - **Zero runtime dependencies.** No database, no server, no embedding service, no
23
- build step. Retrieval is a BM25 index kept in memory.
1
+ # pi-memoria
24
2
 
25
- ```
26
- ┌──────────────────────────────────────────────────────────────┐
27
- │ layer 1 MEMORY.md always in the system prompt │
28
- │ layer 2 auto-recall best matches injected per prompt │
29
- │ layer 3 memoria_recall the agent can search deeper │
30
- │ layer 4 plain markdown read/grep the files directly │
31
- └──────────────────────────────────────────────────────────────┘
32
- ```
3
+ Long-term memory for [pi](https://pi.dev). Saves preferences, project context and
4
+ past decisions as Markdown files, then retrieves relevant notes in later sessions.
33
5
 
34
- Measured on the bundled corpus: **recall@5 = 100%, recall@1 = 92.5%** over 40
35
- questions (paraphrases, typos, aliases, CJK, identifiers, superseded notes) with
36
- an average query time of **0.16 ms** (`npm run eval`).
37
-
38
- ## Contents
39
-
40
- - [Install](#install)
41
- - [Quick start](#quick-start)
42
- - [How it works](#how-it-works)
43
- - [The memory library](#the-memory-library)
44
- - [Tools](#tools)
45
- - [Commands](#commands)
46
- - [Configuration](#configuration)
47
- - [Scopes: personal, project, read-only](#scopes-personal-project-read-only)
48
- - [Synonyms](#synonyms)
49
- - [Model reranking](#model-reranking)
50
- - [Automatic session learning](#automatic-session-learning)
51
- - [Session recall](#session-recall-past-conversations)
52
- - [Backup and migration](#backup-and-migration)
53
- - [Performance](#performance)
54
- - [Privacy and durability](#privacy-and-durability)
55
- - [Troubleshooting](#troubleshooting)
56
- - [Development](#development)
57
- - [Design notes and limits](#design-notes-and-limits)
58
- - [License](#license)
6
+ Memory lives in your agent directory and follows you across projects. Search runs
7
+ locally, with no database, embedding service or additional runtime dependencies.
8
+ The agent can also search pi's saved conversations when a detail was never saved
9
+ as a note.
59
10
 
60
11
  ## Install
61
12
 
62
- ### Copy it into the extensions directory (simplest)
13
+ Requires pi and Node.js **22.19.0 or later**.
63
14
 
64
- ```bash
65
- mkdir -p ~/.pi/agent/extensions/memoria
66
- cp index.ts package.json ~/.pi/agent/extensions/memoria/
67
- cp -r src skills ~/.pi/agent/extensions/memoria/
15
+ ```sh
16
+ pi install npm:pi-memoria
68
17
  ```
69
18
 
70
- Pi discovers `extensions/memoria/index.ts` on the next start (or after
71
- `/reload`). Three notes:
72
-
73
- - **Do not copy `node_modules/`.** Pi supplies `typebox` and the
74
- `@earendil-works/pi-*` packages to extensions, so the copy works with no
75
- dependencies installed.
76
- - `package.json` is optional for this route; it carries the `pi.extensions` and
77
- `pi.skills` manifest and the peer-dependency metadata used by the package
78
- route.
79
- - The bundled skill is discovered automatically when memoria is installed as a
80
- **package** (`pi install …`). For the copy route, copy it separately if you
81
- want it: `mkdir -p ~/.pi/agent/skills && cp -r skills/memoria ~/.pi/agent/skills/`.
82
-
83
- ### Install as a pi package
84
-
85
- ```bash
86
- pi install git:github.com/rcsaquino/pi-memoria@v0.1.1
87
- pi install npm:pi-memoria # if published to npm
88
- pi install ./path/to/pi-memoria # or from a local checkout
89
- pi list
90
- pi remove git:github.com/rcsaquino/pi-memoria@v0.1.1
91
- ```
92
-
93
- `pi install` records the package in `~/.pi/agent/settings.json` (or
94
- `.pi/settings.json` with `-l`, which loads only after project trust) and clones
95
- the repository to `~/.pi/agent/git/<host>/<owner>/<repo>`. It runs
96
- `npm install --omit=dev` in the clone; this package declares the pi-provided
97
- modules as *optional* peers so npm does not fetch a second, ~450 MB copy of pi.
98
- The installed package is around 700 KB with no `node_modules`.
99
-
100
- The package also ships a skill (`skills/memoria/SKILL.md`) that teaches an agent
101
- how to maintain the library — where a fact belongs, how to consolidate notes, and
102
- what never to do.
19
+ Restart pi or run `/reload` to load the extension and its bundled skills.
20
+ No configuration is required.
103
21
 
104
- ### Try it without installing
22
+ Alternatively, install a tagged release from GitHub:
105
23
 
106
- ```bash
107
- pi --extension /path/to/memoria/index.ts
108
- # from a checkout:
109
- pi -e ./index.ts
24
+ ```sh
25
+ pi install git:github.com/rcsaquino/pi-memoria@v0.2.0
110
26
  ```
111
27
 
112
- ## Quick start
113
-
114
- Nothing to configure. In a session:
28
+ Use one installation method, not both.
115
29
 
116
- ```
117
- You: Remember that I take my coffee with oat milk and I'm lactose intolerant.
118
- Agent: [memoria_write { topic: "Dietary preferences", category: "preferences" }]
30
+ ## Use it
119
31
 
120
- ... days later, in a different project ...
32
+ Ask pi to remember something:
121
33
 
122
- You: Order me a flat white.
123
- Agent: [auto-recall surfaces "Dietary preferences — drinks oat milk; lactose intolerant"]
34
+ ```text
35
+ Remember that I use pnpm for personal projects and prefer tabs over spaces.
124
36
  ```
125
37
 
126
- Other things you can say or run:
38
+ In a later session, relevant notes are added to the agent's context automatically.
39
+ You can also ask directly:
127
40
 
128
- ```bash
129
- /memoria status # where the store is and how healthy it is
130
- /memoria store remember to renew the Datadog contract in August
131
- /memoria search datadog contract
132
- /memoria topics # review notes: facts, size, usage, merge candidates
133
- /memoria doctor # consistency problems and housekeeping hints
134
- /memoria learn # extract durable facts from this session
41
+ ```text
42
+ What package manager do I use for personal projects?
43
+ What did we decide about the deployment last week?
135
44
  ```
136
45
 
137
- ## How it works
138
-
139
- 1. **`MEMORY.md`** lives in the store root and is injected into the system
140
- prompt on every model call, capped at `hotLimit` (5000 characters). It is a
141
- short **prose** briefing — who the user is, how they work, hard constraints,
142
- the state of active work — not a log: no headings, no bullets, no
143
- annotations. Being stable, it does not invalidate the prompt cache.
144
- 2. **Auto-recall** searches the library on every user prompt and injects the
145
- best matches as a `memoria_recall` message, with ids, paths and excerpts. The
146
- previous turn is folded in at a lower weight (`autoRecallPriorWeight`) so a
147
- long earlier message cannot out-vote the current question.
148
- 3. **`memoria_recall`** lets the agent search deeper, with filters for category,
149
- tags, priority, recency, scope and score, plus an optional scoring breakdown.
150
- 4. **Plain markdown**: `library/INDEX.md` and per-category indexes provide a
151
- generated table of contents, and `read`/`grep`/`cat` work on the files.
152
- 5. **Past conversations**: if the library has nothing, the transcripts pi saved
153
- for earlier sessions are searched as a last resort, with file-and-line
154
- citations so anything found can be verified in context.
155
-
156
- Ranking combines BM25 over weighted fields (title, tags, aliases, links,
157
- summary, category, body) with boosts for exact phrases, priority, recency, query
158
- coverage, time windows ("last week"), related notes, and a demotion for notes
159
- that a newer note declares superseded. Matching is resilient: identifiers and
160
- paths match literally, plurals and verb forms match through a conservative
161
- stemmer, `kuber` finds `kubernetes`, one or two typos still hit, and CJK text is
162
- matched with unigrams and bigrams.
163
-
164
- ## The memory library
165
-
166
- ```
167
- <agent dir>/memoria/
168
- ├── MEMORY.md always-injected prose briefing
169
- ├── config.json optional store configuration
170
- ├── synonyms.json optional query-side synonym table
171
- ├── library/
172
- │ ├── INDEX.md generated table of contents
173
- │ ├── people/<topic>.md one note per broad topic
174
- │ ├── projects/<topic>.md
175
- │ ├── preferences/<topic>.md
176
- │ ├── decisions/<topic>.md
177
- │ ├── workflows/<topic>.md
178
- │ ├── knowledge/<topic>.md
179
- │ └── inbox/<topic>.md quick captures, triaged later
180
- ├── .index/ derived: index.json|index.bin, usage.json
181
- └── .trash/ soft-deleted notes
182
- ```
46
+ Useful commands:
183
47
 
184
- `<agent dir>` is `~/.pi/agent` by default, or `$PI_CODING_AGENT_DIR` when pi's
185
- agent directory is customized. Because the store is user-level, memories follow
186
- you across projects: remember something in one repository and recall it in
187
- another.
188
-
189
- Pi's own conversation transcripts live next to the store at
190
- `<agent dir>/sessions/` (plus `sessions-archive/` if you keep one). They are read
191
- by [session recall](#session-recall-past-conversations) and never modified.
192
-
193
- ### Broad topics, never facts
194
-
195
- | User says | Stored as | Not as |
196
- |---|---|---|
197
- | "I like apples" | `library/preferences/food-preferences.md` → `- **Fruit** — Likes apples.` | `likes-apples.md` |
198
- | "I like pears" | appended to the same `food-preferences.md` | `likes-pears.md` |
199
- | "Alice prefers oat milk" | `library/people/alice.md` | `alice-prefers-oat-milk.md` |
200
- | "my dad is Bob" | `library/people/bob.md` → `- **Bob** — John's father, retired.` | `dad.md`, `johns-father.md` |
201
-
202
- Rules enforced by the store, not merely requested from the model:
203
-
204
- - The file name is always `slugify(topic)`. Topics that read like a single fact
205
- (`likes apples`, `I like apples`) or like a **relationship** (`John's father`,
206
- `dad`, `Alice's manager`) are rejected and the fact is filed under the category
207
- name instead (`people.md`), with an explanatory note returned to the agent.
208
- A relationship goes in the fact text; the note is named after the subject.
209
- - Writing to an existing topic **appends** under a `## Facts` section, skipping
210
- duplicates. There is no one-file-per-fact path.
211
- - A note may grow to `topicMaxChars` (default 8000) before new facts spill into
212
- `topic-2.md`.
213
- - `memoria_write` accepts `mode: "new"` to force a sibling file and
214
- `mode: "replace"` to overwrite a note deliberately.
215
- - `memoria doctor` flags any file named after a fact or a relationship.
216
-
217
- ### Names change: aliases first, renames second
218
-
219
- A person is not their name, and the first name you learn is rarely the one you
220
- keep. Notes therefore carry **aliases** in frontmatter, indexed at tag weight:
221
-
222
- ```markdown
223
- ---
224
- topic: Bob
225
- aliases: ["Robert", "Bobby", "John's father"]
226
- ---
227
- ```
48
+ | Command | Purpose |
49
+ | --- | --- |
50
+ | `/memoria` | Show store status and the briefing's size |
51
+ | `/memoria search <query>` | Search saved notes |
52
+ | `/memoria sessions <query>` | Search earlier conversations |
53
+ | `/memoria topics` | Review notes and possible duplicates |
54
+ | `/memoria doctor` | Check for consistency problems |
55
+ | `/memoria paths` | Show storage locations |
228
56
 
229
- Any of `Bob`, `Robert`, `Bobby`, `John's father` — and the file name — recalls
230
- the note, so retrieval never depends on which name won the file name.
57
+ Saving a fact and retrieving it are separate steps. Automatic recall searches
58
+ existing notes; it does not turn every conversation into a memory. The agent
59
+ saves a note when something is worth keeping.
231
60
 
232
- When the better name *is* known, `memoria_move` re-files the note:
61
+ ## What gets remembered
233
62
 
234
- - Target free → the file is **renamed**; the note keeps its `id`, so ids read
235
- earlier in the session stay valid.
236
- - Target exists → facts are **merged** in (duplicates skipped, nothing
237
- overwritten) and the old file goes to `.trash/`. `merge: false` reports the
238
- conflict instead.
239
- - The previous topic, title and file name are added to the target's aliases, so
240
- old references keep working.
63
+ Memoria keeps three sources of context:
241
64
 
242
- Renaming is deliberately an explicit agent action rather than a side effect of
243
- writing: renaming during an unrelated write is how you end up with `bob-2.md`
244
- and a path the agent read earlier in the session that no longer exists.
65
+ - **A short briefing** (`MEMORY.md`): standing preferences and context included
66
+ in the system prompt. Limited to 5,000 characters by default.
67
+ - **Topic notes** (`library/`): people, projects, decisions and other details
68
+ retrieved when relevant. Facts about the same subject accumulate in one file.
69
+ - **Saved conversations**: read-only searches of pi's transcripts, with file and
70
+ line references so the agent can check the surrounding dialogue.
245
71
 
246
- ### Related notes and superseded notes
72
+ For example, several preferences belong in `preferences/dietary-preferences.md`,
73
+ not separate `likes-apples.md` and `likes-pears.md` files. Notes support aliases,
74
+ links and superseded decisions. Renaming a note preserves its identity; merging
75
+ notes requires an explicit request.
247
76
 
248
- ```markdown
249
- ---
250
- related: ["mem_1758_ab12"] # pulled into recall with the source note
251
- supersedes: ["Deploy process"] # this note replaces that one
252
- ---
253
- ```
77
+ The default store is separate from the installed extension:
254
78
 
255
- - `related` notes are appended to recall results (`related` marker) and shown by
256
- `memoria_read`, so following a reference works from either end.
257
- - A note that another note declares `supersedes` stays searchable but is
258
- **demoted and flagged** (`SUPERSEDED BY: …`). That is the recommended way to
259
- change a decision: write the new note with `supersedes` instead of deleting
260
- history.
261
- - `memoria doctor` reports dangling links and fact pairs that look contradictory
262
- (same wording, opposite polarity).
263
-
264
- ### Note format
265
-
266
- Notes are markdown with optional YAML frontmatter. Frontmatter is written
267
- automatically on the first `memoria_write`; hand-written notes without it are
268
- still indexed (the title comes from the first `#` heading, the category from the
269
- folder).
270
-
271
- ```markdown
272
- ---
273
- id: mem_1758712445123_a1b2c3
274
- title: Dietary preferences # broad topic = note title
275
- topic: Dietary preferences # drives the file name: dietary-preferences.md
276
- category: preferences
277
- tags: [alice, dietary, food]
278
- aliases: ["Dairy", "Food preferences"]
279
- related: []
280
- supersedes: []
281
- created: 2025-09-24T09:14:05.123Z
282
- updated: 2025-09-24T09:14:05.123Z
283
- last_used: 1758712445123 # refreshed lazily on recall
284
- source: agent
285
- confidence: high
286
- priority: normal
287
- summary: Oat milk in coffee; lactose intolerant.
288
- ---
289
-
290
- # Dietary preferences
291
-
292
- ## Facts
293
-
294
- - **Drinks** — Drinks oat milk in flat whites; avoids dairy.
295
- - **Fruit** — Likes apples and pears; dislikes durian.
79
+ ```text
80
+ ~/.pi/agent/memoria/
81
+ ├── MEMORY.md
82
+ ├── config.json # optional settings
83
+ ├── library/
84
+ │ ├── people/
85
+ │ ├── projects/
86
+ │ └── preferences/
87
+ ├── .index/ # rebuildable search data
88
+ └── .trash/ # recoverable deleted notes
296
89
  ```
297
90
 
298
- Fields: `topic` (broad grouping, drives the file name), `priority`
299
- (`low|normal|high|critical`, boosts ranking), `confidence`, `tags`, `aliases`
300
- (all indexed), `related`, `supersedes`, `summary` (used in listings and recall),
301
- `last_used` (maintained by the store), and `category` (kept in sync with the
302
- folder). Unknown keys are preserved verbatim through updates.
303
-
304
- ## Tools
305
-
306
- | Tool | Purpose |
307
- |---|---|
308
- | `memoria_recall` | Search long-term memory. Filters by category, tags, priority, recency, scope; options for `include_body`, `min_score`, `explain`, `rerank`, `drop_superseded`, prefix/fuzzy control. |
309
- | `memoria_write` | Add a fact to a broad **topic note** (`topic`, `content`, optional `label`, category, tags, aliases, related, supersedes, summary, priority). Creates the note on first use and appends afterwards. |
310
- | `memoria_read` | Read a full note by id, path or alias, with its aliases, related notes and superseding notes. |
311
- | `memoria_move` | Re-file a note under a better broad topic (`from`, `topic`, optional `category`, `merge`, `keep_alias`). Renames when the target is free, merges when it is not. |
312
- | `memoria_sessions` | Search the transcripts of earlier conversations (`action: "search"`, plus `since_days`, `project`, `include_tools`, `user_only`) and read the surrounding dialogue (`action: "read"`, `path`, `line`, `window`) before quoting it. |
313
- | `memoria_hot` | Read/add/remove/replace/compact `MEMORY.md`, the always-in-context briefing. |
314
- | `memoria_list` | Browse categories, titles, ids, tags, aliases and summaries. |
315
- | `memoria_forget` | Move a note to `.trash/` (recoverable). |
316
- | `memoria_export` | Dump the store to JSONL, inline or to a file. |
317
- | `memoria_import` | Restore from JSONL (`mode: merge|replace|skip`, `dry_run`). |
318
-
319
- The agent's system prompt teaches it when to use them, so you normally do not
320
- have to ask.
321
-
322
- ## Commands
323
-
324
- ```
325
- /memoria Store status, index stats, MEMORY.md budget
326
- /memoria search <query> Ranked results with snippets (--primary/--project/--all)
327
- /memoria store <text> Quick capture into library/inbox
328
- /memoria read <id|path> Print a full note
329
- /memoria move <ref> <topic> Re-file a note under a better broad topic
330
- /memoria hot Show MEMORY.md
331
- /memoria topics Review notes: facts, size, usage, merge candidates
332
- /memoria diff [days] Memories created or updated recently (default 7 days)
333
- /memoria sessions <query> Search earlier conversations (--days=N --project=X --tools --user)
334
- /memoria sessions --read <path> <line> [--window=N] Show the surrounding dialogue
335
- /memoria doctor Consistency problems and housekeeping hints
336
- /memoria export [file] Dump the store to JSONL (default ./memoria-export-<date>.jsonl)
337
- /memoria export --hot [file] Write MEMORY.md alone as an "about me" document
338
- /memoria import <file> Restore from JSONL (--merge|--replace|--skip, --dry-run)
339
- /memoria learn Extract durable memories from the current session
340
- /memoria reindex Rebuild the search index from disk
341
- /memoria index Regenerate library/INDEX.md files
342
- /memoria paths Print store paths
343
- /memoria forget <id> Move a note to .trash
344
- ```
91
+ If you set `PI_CODING_AGENT_DIR`, the store follows that directory. You can read
92
+ and edit notes with a text editor; the index picks up changes automatically.
345
93
 
346
94
  ## Configuration
347
95
 
348
- Create `config.json` in the store root (`~/.pi/agent/memoria/config.json`) to
349
- override any default. Everything is optional; unknown or malformed values fall
350
- back to the default rather than failing.
96
+ Create `~/.pi/agent/memoria/config.json` to override defaults. For example, to
97
+ add a project store and teach search an abbreviation:
351
98
 
352
99
  ```json
353
100
  {
354
- "autoRecall": true,
355
- "autoRecallLimit": 6,
356
- "topicMaxWords": 6,
357
- "exclude": ["library/scratch"],
358
101
  "projectRoot": ".pi/memoria-project",
359
- "autoLearn": "on-settle",
360
102
  "synonyms": { "k8s": ["kubernetes"] }
361
103
  }
362
104
  ```
363
105
 
106
+ The primary store is user-wide. `projectRoot` adds an optional writable project
107
+ store; `extraRoots` adds read-only stores. Reranking is off by default because
108
+ it makes an additional model call per search.
109
+
110
+ <details>
111
+ <summary>All configuration options</summary>
112
+
364
113
  | Key | Default | Meaning |
365
114
  |---|---:|---|
366
115
  | `rootDir` | `"$AGENT_DIR/memoria"` | Primary store. Supports `$AGENT_DIR`, `~/`, absolute and cwd-relative paths. |
@@ -369,9 +118,10 @@ back to the default rather than failing.
369
118
  | `hotLimit` | `5000` | Hard cap for `MEMORY.md`, in characters. |
370
119
  | `snippetChars` | `280` | Body excerpt length per hit. |
371
120
  | `autoRecall` | `true` | Search the library on every user prompt. |
372
- | `autoRecallLimit` | `6` | Maximum notes injected per prompt. |
121
+ | `autoRecallLimit` | `3` | Maximum notes injected per prompt. |
122
+ | `autoRecallMinRatio` | `0.3` | Automatic hits must score at least this fraction of the strongest eligible hit; set to `0` to disable the relative cutoff. |
373
123
  | `autoRecallMinScore` | `1.4` | Score floor for auto-recall. |
374
- | `autoRecallMaxChars` | `2400` | Character budget for the injected recall block. |
124
+ | `autoRecallMaxChars` | `2400` | Strict total character budget, including wrappers and transcript fallback. |
375
125
  | `autoRecallLastTurns` | `1` | Previous turns folded into the recall query. |
376
126
  | `autoRecallPriorWeight` | `0.4` | Weight of those previous turns versus the current prompt. |
377
127
  | `bodyCacheBytes` | `8388608` | In-memory body cache budget. |
@@ -396,11 +146,6 @@ back to the default rather than failing.
396
146
  | `rerankModel` | `""` | Model id (`provider/model`); empty uses the session model. |
397
147
  | `rerankTopK` | `12` | Candidates shown to the reranker. |
398
148
  | `rerankTimeoutMs` | `2500` | Reranker budget before falling back to lexical order. |
399
- | `autoLearn` | `"off"` | `off`, `on-settle` (after the agent goes idle) or `on-shutdown`. |
400
- | `autoLearnMinTurns` | `8` | Minimum user turns before automatic extraction. |
401
- | `autoLearnMinChars` | `2000` | Minimum transcript size before automatic extraction. |
402
- | `autoLearnCooldownMs` | `21600000` | Minimum gap between automatic extractions. |
403
- | `learnChunkChars` | `12000` | Characters per extraction chunk (long sessions are chunked). |
404
149
  | `indexFormat` | `"auto"` | `auto` (binary above 20k notes), `json` or `binary`. |
405
150
  | `sessionSearch` | `true` | Search saved session transcripts at all. |
406
151
  | `sessionRoots` | `[]` | Extra transcript roots (searched before `<agent dir>/sessions`). |
@@ -411,279 +156,69 @@ back to the default rather than failing.
411
156
  | `sessionIncludeTools` | `false` | Include tool calls/results and compaction summaries by default. |
412
157
  | `sessionRipgrep` | `true` | Use a ripgrep prefilter to skip match-free transcripts when `rg` is available; without it the slower built-in scanner runs. |
413
158
 
414
- ## Scopes: personal, project, read-only
415
-
416
- - **`primary`** (default) — the user-level store at `<agent dir>/memoria`.
417
- Memories follow you across projects.
418
- - **`project`** — set `projectRoot` to keep project-specific memories in the
419
- repository (`"projectRoot": ".pi/memoria-project"`). It is writable: pass
420
- `scope: "project"` to write there, while reads default to `all`.
421
- - **`all`** — primary + project + extra roots, read paths merged.
422
- - **`extraRoots`** — additional stores that are always **read-only**, for a
423
- shared team library or an imported archive.
424
- - **`global`** — legacy alias for `primary`.
425
-
426
- Cross-store results are rank-normalized: a small store's best hit is lifted to
427
- the global best (up to 4×) instead of being drowned by a large one, and a note
428
- that exists in two stores is returned once.
429
-
430
- ## Synonyms
431
-
432
- Domain shorthand rarely appears in the notes you wrote earlier. Drop a
433
- `synonyms.json` in the store root (or inline the table in `config.json`) and
434
- expansion happens at query time, both directions, with no re-indexing:
435
-
436
- ```json
437
- {
438
- "k8s": ["kubernetes"],
439
- "postgres": ["postgresql", "pg"],
440
- "auth": ["authentication", "login"],
441
- "oncall": ["on-call", "pager"]
442
- }
443
- ```
159
+ </details>
444
160
 
445
- ## Model reranking
161
+ ## Privacy and limits
446
162
 
447
- BM25 is exact but literal: it does not know which of five plausible notes you
448
- mean. With `"rerank": true`, the top candidates (`rerankTopK`) are shown to the
449
- session model, which returns them in relevance order, within
450
- `rerankTimeoutMs`. Reranking is strictly best-effort: on timeout, error, missing
451
- auth or an unparsable reply, lexical order is used, so it can never make recall
452
- fail. It is off by default because it costs a model call per search.
163
+ **Local storage does not mean model-private memory.** The briefing, recalled
164
+ notes and returned transcript excerpts become part of the configured model's
165
+ context. With a hosted model, that content is sent to its provider. Reranking
166
+ also sends content to the configured model when enabled.
453
167
 
454
- ## Automatic session learning
168
+ - Notes are not encrypted, and memoria does not detect or redact credentials.
169
+ Do not store secrets.
170
+ - Search is lexical (BM25 with aliases, synonyms, prefix and fuzzy matching),
171
+ not embedding-based. It can miss a paraphrase with no matching terms.
172
+ - Recall is bounded. Relevant notes can be omitted by score or context limits;
173
+ transcript searches report `partial` when they run out of scan budget.
174
+ - Reranking can reorder retrieved notes, not find notes that search missed.
175
+ - Deletes move notes to `.trash/`. This is recovery, not secure erasure.
176
+ - Writes use atomic file replacement, but separate pi processes are not
177
+ coordinated as concurrent writers. Keep backups.
455
178
 
456
- `/memoria learn` extracts durable facts from the current session. Long
457
- conversations are chunked (`learnChunkChars`, at most 4 calls) and progress is
458
- shown in the status line. Set `autoLearn` to `"on-settle"` (after the agent
459
- finishes a turn) or `"on-shutdown"` to run it without asking. Guards keep it
460
- cheap: at least `autoLearnMinTurns` user turns, at least `autoLearnMinChars` of
461
- transcript, and at most one extraction per `autoLearnCooldownMs`. The default is
462
- `"off"` because it spends model tokens.
179
+ ## Backup and troubleshooting
463
180
 
464
- ## Session recall (past conversations)
181
+ Back up the store by copying `~/.pi/agent/memoria/` (or
182
+ `$PI_CODING_AGENT_DIR/memoria/`). Notes are plain markdown, so a folder copy is
183
+ the complete backup; `.index/` is derived and can be omitted.
465
184
 
466
- Curated memory only holds what the agent decided to remember. The rest of the
467
- story is in pi's saved transcripts, so memoria can search them:
185
+ If recall misses an existing note, check `/memoria paths`, try a distinctive
186
+ name or phrase, then run `/memoria reindex`. For duplicated subjects, use
187
+ `/memoria topics` and ask the agent to consolidate the appropriate notes.
468
188
 
469
- - **Automatically**, whenever `memoria_recall` finds nothing (`sessionFallback`,
470
- on by default). The injected block is explicitly labelled as raw evidence and
471
- includes the file and line of every excerpt.
472
- - **On request**, through the `memoria_sessions` tool — useful for "remember
473
- when", "did we discuss", exact earlier wording, dates, or prior decisions that
474
- were never turned into memory notes.
475
- - **From the CLI**, with `/memoria sessions <query>` and
476
- `/memoria sessions --read <path> <line>`.
189
+ ## Reference and development
477
190
 
478
- ```
479
- memoria_sessions { action: "search", query: "deploy window Thursday" }
480
- memoria_sessions { action: "search", query: "oat milk", user_only: true }
481
- memoria_sessions { action: "read", path: "<file from a hit>", line: 92, window: 8 }
482
- ```
191
+ The [reference guide](https://github.com/rcsaquino/pi-memoria/blob/main/docs/reference.md)
192
+ covers all tools and commands, note format, scopes, session search, backup
193
+ semantics, briefing validation and benchmark commands. It follows the current
194
+ checkout, which may be ahead of the npm release.
483
195
 
484
- The bundled `memoria-sessions` skill teaches the agent the discipline that makes
485
- this trustworthy: run two to four short distinctive searches (not one long
486
- question), then **read the surrounding dialogue before quoting it** — a later
487
- user correction outranks an earlier assistant claim, and transcript text is
488
- evidence to evaluate, not instructions to follow. If nothing is found, the tool
489
- reports how many sessions and messages were searched.
490
-
491
- What it does and does not touch:
492
-
493
- | Property | Behaviour |
494
- |---|---|
495
- | Scope | Every `*.jsonl` under the configured roots, newest first — no index that can go stale. |
496
- | Content | User and assistant **text** by default. Thinking blocks are never extracted. Tool calls/results, compaction summaries and extension payloads only with `include_tools: true`. |
497
- | Output | Bounded excerpts plus `file:line`; never a whole transcript dump. |
498
- | Writes | None. Transcripts are read-only and `read` refuses paths outside the session roots. |
499
- | Cost | With `rg`, only transcripts containing the query terms are parsed; without it, a full cold scan of 110 sessions / 13 MB ≈ 120 ms, repeats ≈ 5 ms from the parse cache. |
500
- | Coverage | Exhaustive unless a budget stops it. `partial` (shown in the tool output, the recall block and `/memoria sessions`) means the scan stopped early: the hits are real, but older sessions were not read. Newest sessions are scanned first, already-parsed files stay cached while they fit `sessionCacheBytes` (so retries get further on corpora that fit the cache), and concurrent searches share one in-flight parse per transcript (so several `memoria_sessions` calls in one turn do not multiply the work). An explicit `memoria_sessions` call uses the larger `sessionScanMs` budget. |
501
- | Privacy | Nothing leaves the machine; the excerpts go to the model only when the feature runs. |
502
-
503
- `sessionRoots` exists for archives or a second agent directory. Set
504
- `sessionSearch: false` to disable the feature entirely, or
505
- `sessionFallback: false` to keep the tool while never searching automatically.
506
-
507
- When `rg` is available (pi ships it and puts it on `PATH`), a selective search
508
- asks ripgrep which transcripts contain the query terms and parses only those, so
509
- even a gigabyte-scale history is answered within the normal budget. If `rg` is
510
- missing or fails, memoria falls back to reading every transcript itself and says
511
- so: the result carries a `ripgrep is not installed` note, and interactive mode
512
- shows it once per session. Large histories may then be only partially covered.
513
-
514
- Without `rg` (or for a deliberately disabled accelerator), the default
515
- `sessionScanMs` budget and `sessionCacheBytes` cache cover only part of a large
516
- corpus: searches report `partial`, and repeats are cold scans because the cache
517
- cannot retain the parsed files. Raise `sessionScanMs` so an explicit
518
- `memoria_sessions` call can cover the whole corpus in one pass, and
519
- `sessionCacheBytes` only if you have the RAM for the parsed messages (at this
520
- scale that means gigabytes). The automatic fallback remains capped at 400 ms,
521
- so on such a corpus it is always a partial scan.
522
-
523
- ## Backup and migration
524
-
525
- `memoria_export` writes JSONL — one record per line, notes plus `MEMORY.md` — to
526
- a file or returns a bounded preview. `memoria_import` restores it:
196
+ To work on the extension from a clone:
527
197
 
528
- ```
529
- memoria_export { path: "memoria-backup.jsonl" }
530
- memoria_import { path: "memoria-backup.jsonl", dry_run: true }
531
- memoria_import { path: "memoria-backup.jsonl", mode: "merge" }
198
+ ```sh
199
+ npm install
200
+ npm run typecheck
201
+ npm test
202
+ npm run eval
203
+ npm run bench
204
+ pi -e ./index.ts
532
205
  ```
533
206
 
534
- - `merge` (default) adds facts that are missing, never duplicating an id.
535
- - `replace` overwrites notes with the same id.
536
- - `skip` only imports notes that do not exist yet.
537
- - Notes keep their ids, so ids in an old transcript still resolve.
538
- - Paths are validated: an import cannot write outside `library/`, and a path
539
- already taken by a different note gets a numbered sibling instead of an
540
- overwrite.
541
-
542
- The same files remain readable as plain markdown if you stop using memoria, and
543
- the whole store is safe to commit to a private repository (`.index/` is derived
544
- and can be deleted).
545
-
546
- ## Performance
547
-
548
- `node tests/bench.ts 1000 5000` measures a worst-case corpus (50-word
549
- vocabulary, 60-word documents, so every posting list is long);
550
- `node tests/bench.ts 1000 5000 --real` uses a realistic high-vocabulary corpus
551
- (~44k distinct terms at 5k notes).
552
-
553
- | Corpus | Notes | Index size | Cold load | Reload | Search | Prefix | Fuzzy |
554
- |---|---:|---:|---:|---:|---:|---:|---:|
555
- | worst case | 1,000 | 1.3 MB | 219 ms | 23 ms | 0.35 ms | 0.22 ms | 0.32 ms |
556
- | worst case | 5,000 | 6.8 MB | 1.10 s | 109 ms | 0.89 ms | 0.66 ms | 0.94 ms |
557
- | realistic | 5,000 | 8.5 MB | 1.34 s | 134 ms | 1.20 ms | 0.64 ms | 1.02 ms |
558
- | realistic | 20,000 | 34.5 MB | 5.4 s | 543 ms | 5.88 ms | 2.63 ms | 4.30 ms |
559
-
560
- Auto-recall runs on every prompt, which is why the search path is
561
- allocation-light: a dense scratch-buffer BM25 loop, posting lists in memory, and
562
- snippets read only for the final hits. Recency scans are throttled
563
- (`scanIntervalMs`) and coalesced from filesystem events (`watcherSettleMs`), so
564
- a burst of edits costs one refresh.
565
-
566
- ## Privacy and durability
567
-
568
- - **Local only.** Nothing is sent anywhere except by the optional reranker and
569
- session learning, which use the model you already configured and only when
570
- enabled.
571
- - **Files, not a database.** Memoria never owns your data: notes are markdown
572
- with frontmatter, deletes are soft (moved to `.trash/`), and the index is
573
- derived and can be rebuilt with `/memoria reindex`.
574
- - **Crash-safe moves.** Category moves and renames are journaled, so an
575
- interrupted move is completed on the next start instead of leaving two copies.
576
- - **Atomic writes.** Every write goes through a temp file and rename; there is no
577
- window where a note is half-written.
578
- - **Secrets:** memoria does not scan for credentials, and the learn prompt tells
579
- the model not to store them. If you keep secrets in the store anyway, remember
580
- that recall may surface them in model context — treat the store like your shell
581
- history.
582
-
583
- ## Troubleshooting
584
-
585
- | Symptom | Fix |
586
- |---|---|
587
- | Notes exist but recall finds nothing | `/memoria reindex`; check `/memoria paths` for the store location; remember scopes (`--all` searches every root). |
588
- | A note is filed under `people.md` instead of the person's name | The topic was rejected as fact-shaped or relational. Write with the person's name as `topic` and put the relationship in the content. |
589
- | The same subject ended up in two notes | `/memoria topics` lists merge candidates with the exact `memoria_move` command; `memoria doctor` reports them too. |
590
- | `MEMORY.md` is over budget | `memoria_hot { action: "compact" }` drops the least important trailing paragraphs and returns them so you can file them into notes. |
591
- | A decision changed | Write the new note with `supersedes: ["<old id, path or title>"]`; the old note stays searchable but is demoted and flagged. |
592
- | Search misses domain shorthand | Add `synonyms.json` (see [Synonyms](#synonyms)). |
593
- | Model reranking is slow | Lower `rerankTopK`, or leave `rerank` off — lexical search is already sub-millisecond. |
594
- | Want to see what changed lately | `/memoria diff 3` for the last three days, `/memoria topics` for usage, and a session-start notification summarises changes since your previous session. |
595
- | An agent edited files and the index is stale | Filesystem events and a throttled scan pick changes up automatically; `/memoria doctor` shows the state. |
596
- | The user references something from before | The agent should search transcripts (`memoria_sessions`, or `/memoria sessions <query>`) rather than claim it does not remember; `/memoria paths` lists the session roots. |
597
- | Session search finds nothing | Try different wording, a name or a date, or `include_tools: true`; check `sessionRoots` covers the transcript location, and note that a budget-limited scan reports `partial`. |
598
-
599
- ## Development
600
-
601
- ```bash
602
- npm install # dev-only: pi host packages + typescript
603
- npm test # 144 tests, node:test, no build step
604
- npm run typecheck # tsc --noEmit
605
- npm run eval # recall@1/@5/MRR over the fixture corpus
606
- npm run bench # latency at 1k and 5k notes
607
- npm run bench:real # realistic high-vocabulary corpus
608
- pi -e ./index.ts # run the extension from the checkout
609
- ```
207
+ No build step is needed. See
208
+ [AGENTS.md](https://github.com/rcsaquino/pi-memoria/blob/main/AGENTS.md)
209
+ for architecture, invariants and contribution checks.
610
210
 
611
- The extension runs on Node's native type-stripping (`node >= 22.19`), which
612
- means no build step but also no `enum`, `namespace`, constructor parameter
613
- properties or `import x = require()` in the sources. Type-only imports must use
614
- `import type`, and relative imports include the `.ts` extension.
211
+ Report bugs through [GitHub Issues](https://github.com/rcsaquino/pi-memoria/issues).
212
+ Include your pi and Node versions, steps to reproduce, and relevant errors.
213
+ Remove personal memory content and credentials before sharing logs.
615
214
 
616
- Architecture, invariants and the release checklist live in
617
- [`AGENTS.md`](AGENTS.md). The short version:
215
+ ## Support My Work
618
216
 
619
- ```
620
- index.ts lifecycle hooks, system-prompt section, auto-recall, auto-learn
621
- src/types.ts shared types (no runtime imports)
622
- src/util.ts atomic writes, mutex, debounce, ids, paths
623
- src/frontmatter.ts dependency-free YAML-subset parser/serializer
624
- src/tokenize.ts identifiers, camel/snake/kebab, CJK, stemmer, trigrams
625
- src/config.ts defaults, coercion, path resolution
626
- src/store.ts filesystem: parse, write, merge, move, MEMORY.md, indexes
627
- src/index-engine.ts inverted index, persistence (JSON/binary), link resolution
628
- src/search.ts BM25, boosts, snippets, explain, related/superseded handling
629
- src/similarity.ts duplicate-topic and contradiction detection
630
- src/usage.ts hits/writes/last-used tracking
631
- src/timeexpr.ts "last week" → recency windows
632
- src/synonyms.ts query-side synonym tables
633
- src/transfer.ts JSONL export/import
634
- src/learn.ts session extraction (chunked)
635
- src/recall.ts prompt/message rendering
636
- src/rerank.ts optional model reranking
637
- src/runtime.ts roots, locks, caching, health, export/import, scopes
638
- src/tools.ts tool definitions and renderers
639
- src/commands.ts /memoria subcommands
640
- ```
217
+ If pi-memoria is useful to you, please consider supporting my work by
218
+ [buying me a coffee](https://ko-fi.com/rcsaquino). 😊
641
219
 
642
- ### Publishing your own copy
643
-
644
- 1. Push the repository to GitHub (public or private).
645
- 2. Keep `index.ts` at the package root and the `pi` manifest in `package.json`.
646
- 3. Leave the host modules as `peerDependencies` **and** mark them
647
- `"optional": true` in `peerDependenciesMeta`, keeping them in
648
- `devDependencies` for local tests only. Without that, npm installs a ~450 MB
649
- duplicate of pi into the package.
650
- 4. Tag releases (`git tag v0.1.1 && git push --tags`) and install by tag:
651
- `pi install git:github.com/<you>/pi-memoria@v0.1.1`.
652
- 5. `npm pack --dry-run` should list only `index.ts`, `src/**`, `skills/**`,
653
- `README.md`, `LICENSE` and `package.json`.
654
-
655
- ## Design notes and limits
656
-
657
- - **Topic notes are the unit of storage.** This is deliberately opinionated: a
658
- file per fact fragments knowledge and makes retrieval worse. The cost is that
659
- the agent must pick a broad topic; when it picks badly, the store falls back to
660
- the category rather than creating a bad file name, and `memoria_move` fixes it
661
- later.
662
- - **Lexical, not semantic.** BM25 with a conservative stemmer, prefix and fuzzy
663
- matching is fast, deterministic and dependency-free, but it will not connect
664
- "my manager" to "Alice" without an alias or a synonym table. Embeddings would
665
- improve that; they are not worth the dependency for a store this fast.
666
- - **Cross-topic duplication is reported, not merged automatically.** The store
667
- flags two notes as duplicates only when their *names* also point at the same
668
- subject (equal slugs, or one name being a prefix/suffix of the other — e.g.
669
- `Alice` and `Alice Smith`, or a legacy `Johns father` note whose name appears
670
- as an alias). Content overlap alone is not enough: one person's note naturally
671
- describes the people around them, and merging John Doe with his father would be
672
- worse than missing a duplicate. Folding notes stays an explicit
673
- `memoria_move`, suggested on write and listed by `/memoria topics`.
674
- - **Usage is derived data.** `.index/usage.json` drives staleness and promotion
675
- hints; deleting `.index/` only loses those hints.
676
- - **Journal recovery covers moves, not arbitrary edits.** A crash between an
677
- atomic write and a trash move is repaired; a crash mid-edit of a single file
678
- leaves the previous content intact (writes are atomic).
679
- - **Model reranking instead of embeddings.** An embedding index would improve
680
- semantic matching, but pi exposes chat completions rather than an embedding
681
- API, and a vector store is a dependency this project does not want. The
682
- optional reranker gets the same effect (an LLM judging the top candidates) and
683
- degrades to lexical order on any failure. Time-scoped questions are handled by
684
- parsing the window out of the query rather than by time-decayed IDF: the note's
685
- own timestamp is the signal that matters.
220
+ <a href="https://ko-fi.com/rcsaquino" target="_blank" rel="noopener noreferrer"><img height="72" src="https://storage.ko-fi.com/cdn/kofi2.png?v=3" alt="Buy Me a Coffee at ko-fi.com" /></a>
686
221
 
687
222
  ## License
688
223
 
689
- MIT © 2026 rcsaquino. See [LICENSE](LICENSE).
224
+ [MIT](https://github.com/rcsaquino/pi-memoria/blob/main/LICENSE) © 2026 rcsaquino.