pi-memoria 0.1.3 → 0.3.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/AGENTS.md +53 -0
- package/README.md +113 -622
- package/index.ts +285 -255
- package/package.json +28 -69
- package/src/aliases.ts +104 -0
- package/src/config.ts +22 -192
- package/src/database.ts +301 -0
- package/src/errors.ts +55 -0
- package/src/files.ts +29 -0
- package/src/hot-memory.ts +231 -0
- package/src/results.ts +264 -0
- package/src/sessions.ts +246 -843
- package/src/text.ts +48 -0
- package/skills/memoria/SKILL.md +0 -65
- package/skills/memoria-sessions/SKILL.md +0 -69
- package/src/commands.ts +0 -475
- package/src/frontmatter.ts +0 -318
- package/src/index-engine.ts +0 -1070
- package/src/learn.ts +0 -299
- package/src/recall.ts +0 -186
- package/src/rerank.ts +0 -85
- package/src/runtime.ts +0 -1678
- package/src/search.ts +0 -499
- package/src/similarity.ts +0 -374
- package/src/store.ts +0 -1696
- package/src/synonyms.ts +0 -105
- package/src/timeexpr.ts +0 -145
- package/src/tokenize.ts +0 -295
- package/src/tools.ts +0 -938
- package/src/transfer.ts +0 -180
- package/src/types.ts +0 -549
- package/src/usage.ts +0 -188
- package/src/util.ts +0 -141
package/README.md
CHANGED
|
@@ -1,693 +1,184 @@
|
|
|
1
|
+
<!-- prettier-ignore -->
|
|
2
|
+
<div align="center">
|
|
3
|
+
|
|
1
4
|
# pi-memoria
|
|
2
5
|
|
|
3
|
-
|
|
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. Re-filing or consolidating notes also keeps retired
|
|
21
|
-
note ids usable as references.
|
|
22
|
-
- **Zero runtime dependencies.** No database, no server, no embedding service, no
|
|
23
|
-
build step. Retrieval is a BM25 index kept in memory.
|
|
6
|
+
**Global memory for [Pi](https://github.com/earendil-works/pi): startup rules in `MEMORY.md`, durable facts in SQLite FTS5, and cited search across every past conversation.**
|
|
24
7
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
│ layer 3 memoria_recall the agent can search deeper │
|
|
30
|
-
│ layer 4 plain markdown read/grep the files directly │
|
|
31
|
-
└──────────────────────────────────────────────────────────────┘
|
|
32
|
-
```
|
|
8
|
+
[](https://github.com/rcsaquino/pi-memoria/releases)
|
|
9
|
+
[](https://nodejs.org)
|
|
10
|
+
[](https://www.typescriptlang.org)
|
|
11
|
+
[](LICENSE)
|
|
33
12
|
|
|
34
|
-
|
|
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)
|
|
59
|
-
|
|
60
|
-
## Install
|
|
61
|
-
|
|
62
|
-
### Copy it into the extensions directory (simplest)
|
|
63
|
-
|
|
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/
|
|
68
|
-
```
|
|
13
|
+
[How it works](#how-it-works) • [Installation](#installation) • [Hot memory](#hot-memory) • [Tools](#tools) • [Configuration](#configuration) • [Performance](#performance) • [Support](#support-and-contact)
|
|
69
14
|
|
|
70
|
-
|
|
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.3
|
|
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.3
|
|
91
|
-
```
|
|
15
|
+
</div>
|
|
92
16
|
|
|
93
|
-
|
|
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`.
|
|
17
|
+
pi-memoria gives Pi one memory shared across every project: instructions that must apply from the first message go in a capped `MEMORY.md`, everything else in SQLite with FTS5, and past conversations stay searchable through ripgrep. Five tools, local storage only. No embeddings, no network calls, no background model work.
|
|
99
18
|
|
|
100
|
-
|
|
101
|
-
how to maintain the library — where a fact belongs, how to consolidate notes, and
|
|
102
|
-
what never to do.
|
|
19
|
+
## Requirements
|
|
103
20
|
|
|
104
|
-
|
|
21
|
+
- **Node.js 22.19+** (uses `node:sqlite`; tested on 22.19 and 24.20).
|
|
22
|
+
- **Pi** with the current extension API (tested with 0.87.1), Node-based. Standalone/Bun builds are not validated with `node:sqlite`.
|
|
23
|
+
- **ripgrep (`rg`) on PATH** for session search.
|
|
105
24
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
pi -
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
pi install npm:pi-memoria
|
|
110
29
|
```
|
|
111
30
|
|
|
112
|
-
|
|
31
|
+
Restart Pi or run `/reload`. To uninstall, run `pi remove npm:pi-memoria`; stored memory remains until you delete its directory.
|
|
113
32
|
|
|
114
|
-
|
|
33
|
+
<details>
|
|
34
|
+
<summary><strong>Install from a local checkout</strong></summary>
|
|
115
35
|
|
|
36
|
+
```sh
|
|
37
|
+
npm ci --ignore-scripts
|
|
38
|
+
pi install /absolute/path/to/pi-memoria
|
|
116
39
|
```
|
|
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" }]
|
|
119
40
|
|
|
120
|
-
|
|
41
|
+
Keep the checkout in place: a local install references it directly. To try it without installing:
|
|
121
42
|
|
|
122
|
-
|
|
123
|
-
|
|
43
|
+
```sh
|
|
44
|
+
pi -e /absolute/path/to/pi-memoria/index.ts
|
|
124
45
|
```
|
|
125
46
|
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
135
|
-
```
|
|
47
|
+
</details>
|
|
136
48
|
|
|
137
49
|
## How it works
|
|
138
50
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
```
|
|
51
|
+
| Store | What belongs there | How it is retrieved |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `MEMORY.md` | Instructions that must apply before any retrieval cue appears | Loaded at startup and refreshed before every user prompt |
|
|
54
|
+
| SQLite + FTS5 | Preferences, facts, decisions, names, conventions | `memoria_search` |
|
|
55
|
+
| Original Pi JSONL sessions | What was actually said, including old branches and pre-compaction messages | `memoria_sessions`, with file and line citations |
|
|
183
56
|
|
|
184
|
-
|
|
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.
|
|
57
|
+
"Start every sentence with `beep_boop`" belongs in hot memory: even a session starting with "Hello" must obey it. "The user likes apples" belongs in SQLite with tags such as `fruit` and `preferences`: a fruit recommendation supplies the retrieval cue.
|
|
188
58
|
|
|
189
|
-
|
|
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.
|
|
59
|
+
The model decides what to save and when to search; nothing is extracted automatically. Before saving, it searches for related memories, keeps unchanged facts, and edits existing ones when information changes. SQLite rejects repeated identical adds; differently worded paraphrases depend on the model's judgment.
|
|
192
60
|
|
|
193
|
-
|
|
61
|
+
## Hot memory
|
|
194
62
|
|
|
195
|
-
|
|
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
|
-
---
|
|
63
|
+
```md
|
|
64
|
+
- [h_0123456789abcdef] [p=90] Start every sentence with beep_boop.
|
|
227
65
|
```
|
|
228
66
|
|
|
229
|
-
|
|
230
|
-
the note, so retrieval never depends on which name won the file name.
|
|
231
|
-
|
|
232
|
-
When the better name *is* known, `memoria_move` re-files the note:
|
|
233
|
-
|
|
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 → the move reports a conflict. Set `merge: true` to combine the
|
|
237
|
-
facts and links (duplicates skipped, nothing overwritten) and move the old
|
|
238
|
-
file to `.trash/`. A merge refuses to discard aliases or links when their
|
|
239
|
-
limits are full.
|
|
240
|
-
- The previous topic, title and file name are added to the target's aliases, so
|
|
241
|
-
old references keep working.
|
|
67
|
+
Priority is **0–100** (default 50). The **entire file**, including IDs and newlines, must fit in **5,000 UTF-16 code units**, checked before every write. Content is single-line; CR, LF, U+2028, and U+2029 are rejected.
|
|
242
68
|
|
|
243
|
-
|
|
244
|
-
writing: renaming during an unrelated write is how you end up with `bob-2.md`
|
|
245
|
-
and a path the agent read earlier in the session that no longer exists.
|
|
69
|
+
Duplicate adds reuse the existing bullet and consume no capacity. When space is needed, strictly lower-priority bullets are archived to SQLite first (`hot-archive`), then the file is atomically replaced; if that cannot free enough room, the write fails with the live file unchanged.
|
|
246
70
|
|
|
247
|
-
|
|
71
|
+
> [!WARNING]
|
|
72
|
+
> Prefer the tools for edits. External edits can bypass the cap until the next load, and oversized content is never injected.
|
|
248
73
|
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
related: ["mem_1758_ab12"] # pulled into recall with the source note
|
|
252
|
-
supersedes: ["Deploy process"] # this note replaces that one
|
|
253
|
-
---
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
- `related` notes are appended to recall results (`related` marker) and shown by
|
|
257
|
-
`memoria_read`, so following a reference works from either end.
|
|
258
|
-
- A note that another note declares `supersedes` stays searchable but is
|
|
259
|
-
**demoted and flagged** (`SUPERSEDED BY: …`). That is the recommended way to
|
|
260
|
-
change a decision: write the new note with `supersedes` instead of deleting
|
|
261
|
-
history.
|
|
262
|
-
- `memoria doctor` reports dangling links and fact pairs that look contradictory
|
|
263
|
-
(same wording, opposite polarity).
|
|
264
|
-
|
|
265
|
-
### Note format
|
|
266
|
-
|
|
267
|
-
Notes are markdown with optional YAML frontmatter. Frontmatter is written
|
|
268
|
-
automatically on the first `memoria_write`; hand-written notes without it are
|
|
269
|
-
still indexed (the title comes from the first `#` heading, the category from the
|
|
270
|
-
folder).
|
|
271
|
-
|
|
272
|
-
```markdown
|
|
273
|
-
---
|
|
274
|
-
id: mem_1758712445123_a1b2c3
|
|
275
|
-
title: Dietary preferences # broad topic = note title
|
|
276
|
-
topic: Dietary preferences # drives the file name: dietary-preferences.md
|
|
277
|
-
category: preferences
|
|
278
|
-
tags: [alice, dietary, food]
|
|
279
|
-
aliases: ["Dairy", "Food preferences"]
|
|
280
|
-
related: []
|
|
281
|
-
supersedes: []
|
|
282
|
-
created: 2025-09-24T09:14:05.123Z
|
|
283
|
-
updated: 2025-09-24T09:14:05.123Z
|
|
284
|
-
last_used: 1758712445123 # refreshed lazily on recall
|
|
285
|
-
source: agent
|
|
286
|
-
confidence: high
|
|
287
|
-
priority: normal
|
|
288
|
-
summary: Oat milk in coffee; lactose intolerant.
|
|
289
|
-
---
|
|
290
|
-
|
|
291
|
-
# Dietary preferences
|
|
292
|
-
|
|
293
|
-
## Facts
|
|
294
|
-
|
|
295
|
-
- **Drinks** — Drinks oat milk in flat whites; avoids dairy.
|
|
296
|
-
- **Fruit** — Likes apples and pears; dislikes durian.
|
|
297
|
-
```
|
|
74
|
+
## Recall
|
|
298
75
|
|
|
299
|
-
|
|
300
|
-
(`low|normal|high|critical`, boosts ranking), `confidence`, `tags`, `aliases`
|
|
301
|
-
(all indexed), `related`, `supersedes`, `summary` (used in listings and recall),
|
|
302
|
-
`last_used` (maintained by the store), and `category` (kept in sync with the
|
|
303
|
-
folder). Unknown keys are preserved verbatim through updates.
|
|
76
|
+
Keyword search cannot infer every synonym, and session search has no recency or project filter. For "remember when" questions, search sessions and cite the original entries; follow `next_offset`/`next_text_offset`, and check `warnings` and `exhausted`. A partial or failed search is never proof of absence.
|
|
304
77
|
|
|
305
78
|
## Tools
|
|
306
79
|
|
|
307
80
|
| Tool | Purpose |
|
|
308
81
|
|---|---|
|
|
309
|
-
| `
|
|
310
|
-
| `
|
|
311
|
-
| `
|
|
312
|
-
| `
|
|
313
|
-
| `memoria_sessions` | Search
|
|
314
|
-
| `memoria_hot` | Read/add/remove/replace/compact `MEMORY.md`, the always-in-context briefing. |
|
|
315
|
-
| `memoria_list` | Browse categories, titles, ids, tags, aliases and summaries. |
|
|
316
|
-
| `memoria_forget` | Move a note to `.trash/` (recoverable). |
|
|
317
|
-
| `memoria_export` | Dump the store to JSONL, inline or to a file. |
|
|
318
|
-
| `memoria_import` | Restore from JSONL (`mode: merge|replace|skip`, `dry_run`). |
|
|
319
|
-
|
|
320
|
-
The agent's system prompt teaches it when to use them, so you normally do not
|
|
321
|
-
have to ask.
|
|
322
|
-
|
|
323
|
-
## Commands
|
|
324
|
-
|
|
325
|
-
```
|
|
326
|
-
/memoria Store status, index stats, MEMORY.md budget
|
|
327
|
-
/memoria search <query> Ranked results with snippets (--primary/--project/--all)
|
|
328
|
-
/memoria store <text> Quick capture into library/inbox
|
|
329
|
-
/memoria read <id|path> Print a full note
|
|
330
|
-
/memoria move <ref> <topic> [--merge] Re-file a note; opt into merging an occupied target
|
|
331
|
-
/memoria hot Show MEMORY.md
|
|
332
|
-
/memoria topics Review notes: facts, size, usage, merge candidates
|
|
333
|
-
/memoria diff [days] Memories created or updated recently (default 7 days)
|
|
334
|
-
/memoria sessions <query> Search earlier conversations (--days=N --project=X --tools --user)
|
|
335
|
-
/memoria sessions --read <path> <line> [--window=N] Show the surrounding dialogue
|
|
336
|
-
/memoria doctor Consistency problems and housekeeping hints
|
|
337
|
-
/memoria export [file] Dump the store to JSONL (default ./memoria-export-<date>.jsonl)
|
|
338
|
-
/memoria export --hot [file] Write MEMORY.md alone as an "about me" document
|
|
339
|
-
/memoria import <file> Restore from JSONL (--merge|--replace|--skip, --dry-run)
|
|
340
|
-
/memoria learn Extract durable memories from the current session
|
|
341
|
-
/memoria reindex Rebuild the search index from disk
|
|
342
|
-
/memoria index Regenerate library/INDEX.md files
|
|
343
|
-
/memoria paths Print store paths
|
|
344
|
-
/memoria forget <id> Move a note to .trash
|
|
345
|
-
```
|
|
82
|
+
| `memoria_add` | Save a fact to SQLite or a startup rule to hot memory |
|
|
83
|
+
| `memoria_edit` | Change content, tags, priority, or hot text by ID |
|
|
84
|
+
| `memoria_delete` | Remove a memory by ID |
|
|
85
|
+
| `memoria_search` | Search SQLite with keywords, modes, and exact tags |
|
|
86
|
+
| `memoria_sessions` | Search or read original Pi session entries with citations |
|
|
346
87
|
|
|
347
|
-
|
|
88
|
+
Tool replies are compact; oversized results spill to a private `pi-memoria-result-*` file.
|
|
348
89
|
|
|
349
|
-
|
|
350
|
-
override any default. Everything is optional; unknown or malformed values fall
|
|
351
|
-
back to the default rather than failing.
|
|
90
|
+
**Add a fact:**
|
|
352
91
|
|
|
353
92
|
```json
|
|
354
|
-
{
|
|
355
|
-
"autoRecall": true,
|
|
356
|
-
"autoRecallLimit": 6,
|
|
357
|
-
"topicMaxWords": 6,
|
|
358
|
-
"exclude": ["library/scratch"],
|
|
359
|
-
"projectRoot": ".pi/memoria-project",
|
|
360
|
-
"autoLearn": "on-settle",
|
|
361
|
-
"synonyms": { "k8s": ["kubernetes"] }
|
|
362
|
-
}
|
|
93
|
+
{"content":"The user likes apples.","tags":["fruit","preferences"]}
|
|
363
94
|
```
|
|
364
95
|
|
|
365
|
-
|
|
366
|
-
|---|---:|---|
|
|
367
|
-
| `rootDir` | `"$AGENT_DIR/memoria"` | Primary store. Supports `$AGENT_DIR`, `~/`, absolute and cwd-relative paths. |
|
|
368
|
-
| `projectRoot` | `""` | Optional writable per-project store; empty disables it. |
|
|
369
|
-
| `extraRoots` | `[]` | Additional **read-only** stores to search. |
|
|
370
|
-
| `hotLimit` | `5000` | Hard cap for `MEMORY.md`, in characters. |
|
|
371
|
-
| `snippetChars` | `280` | Body excerpt length per hit. |
|
|
372
|
-
| `autoRecall` | `true` | Search the library on every user prompt. |
|
|
373
|
-
| `autoRecallLimit` | `6` | Maximum notes injected per prompt. |
|
|
374
|
-
| `autoRecallMinScore` | `1.4` | Score floor for auto-recall. |
|
|
375
|
-
| `autoRecallMaxChars` | `2400` | Character budget for the injected recall block. |
|
|
376
|
-
| `autoRecallLastTurns` | `1` | Previous turns folded into the recall query. |
|
|
377
|
-
| `autoRecallPriorWeight` | `0.4` | Weight of those previous turns versus the current prompt. |
|
|
378
|
-
| `bodyCacheBytes` | `8388608` | In-memory body cache budget. |
|
|
379
|
-
| `scanIntervalMs` | `2000` | Minimum interval between filesystem freshness scans. |
|
|
380
|
-
| `watcherSettleMs` | `300` | Idle delay after a file event before refreshing (coalesces bursts). |
|
|
381
|
-
| `exclude` | `[]` | Path fragments never indexed. |
|
|
382
|
-
| `defaultCategory` | `"inbox"` | Folder for notes without a category. |
|
|
383
|
-
| `topicMaxWords` | `6` | Longest topic name before falling back to the category. |
|
|
384
|
-
| `topicMaxChars` | `8000` | Largest note before facts spill into a sibling file. |
|
|
385
|
-
| `usageTracking` | `true` | Track hits/writes/last-used in `.index/usage.json`. |
|
|
386
|
-
| `lastUsedWriteIntervalMs` | `21600000` | Refresh a note's `last_used` at most this often (6 h). |
|
|
387
|
-
| `staleAfterDays` | `365` | `doctor` reports notes unused for longer. |
|
|
388
|
-
| `promoteAfterWrites` | `3` | Suggest `memoria_hot` after this many writes with priority ≥ high. |
|
|
389
|
-
| `dedupeThreshold` | `0.62` | Overlap above which two notes are reported as merge candidates. |
|
|
390
|
-
| `relatedHits` | `3` | Related notes pulled into one result. |
|
|
391
|
-
| `relatedBoost` | `0.35` | Score factor for those related notes. |
|
|
392
|
-
| `synonyms` | `{}` | Inline synonym table (merged over `synonyms.json`). |
|
|
393
|
-
| `synonymWeight` | `0.6` | Weight of synonym-expanded query terms. |
|
|
394
|
-
| `timeHints` | `true` | Parse "last week"/"yesterday" in queries into a recency window. |
|
|
395
|
-
| `searchCacheSize` | `32` | LRU entries for repeated identical searches (0 disables). |
|
|
396
|
-
| `rerank` | `false` | Let the session model reorder recall candidates. |
|
|
397
|
-
| `rerankModel` | `""` | Model id (`provider/model`); empty uses the session model. |
|
|
398
|
-
| `rerankTopK` | `12` | Candidates shown to the reranker. |
|
|
399
|
-
| `rerankTimeoutMs` | `2500` | Reranker budget before falling back to lexical order. |
|
|
400
|
-
| `autoLearn` | `"off"` | `off`, `on-settle` (after the agent goes idle) or `on-shutdown`. |
|
|
401
|
-
| `autoLearnMinTurns` | `8` | Minimum user turns before automatic extraction. |
|
|
402
|
-
| `autoLearnMinChars` | `2000` | Minimum transcript size before automatic extraction. |
|
|
403
|
-
| `autoLearnCooldownMs` | `21600000` | Minimum gap between automatic extractions. |
|
|
404
|
-
| `learnChunkChars` | `12000` | Characters per extraction chunk (long sessions are chunked). |
|
|
405
|
-
| `indexFormat` | `"auto"` | `auto` (binary above 20k notes), `json` or `binary`. |
|
|
406
|
-
| `sessionSearch` | `true` | Search saved session transcripts at all. |
|
|
407
|
-
| `sessionRoots` | `[]` | Extra transcript roots (searched before `<agent dir>/sessions`). |
|
|
408
|
-
| `sessionFallback` | `true` | Search transcripts automatically when the library returns nothing. |
|
|
409
|
-
| `sessionScanMs` | `1500` | Wall-clock budget for an explicit transcript search (the automatic fallback uses at most 400 ms). |
|
|
410
|
-
| `sessionExcerptChars` | `400` | Context characters per transcript hit. |
|
|
411
|
-
| `sessionCacheBytes` | `33554432` | In-memory budget for parsed transcripts. |
|
|
412
|
-
| `sessionIncludeTools` | `false` | Include tool calls/results and compaction summaries by default. |
|
|
413
|
-
| `sessionRipgrep` | `true` | Use a ripgrep prefilter to skip match-free transcripts when `rg` is available; without it the slower built-in scanner runs. |
|
|
414
|
-
|
|
415
|
-
## Scopes: personal, project, read-only
|
|
416
|
-
|
|
417
|
-
- **`primary`** (default) — the user-level store at `<agent dir>/memoria`.
|
|
418
|
-
Memories follow you across projects.
|
|
419
|
-
- **`project`** — set `projectRoot` to keep project-specific memories in the
|
|
420
|
-
repository (`"projectRoot": ".pi/memoria-project"`). It is writable: pass
|
|
421
|
-
`scope: "project"` to write there, while reads default to `all`.
|
|
422
|
-
- **`all`** — primary + project + extra roots, read paths merged.
|
|
423
|
-
- **`extraRoots`** — additional stores that are always **read-only**, for a
|
|
424
|
-
shared team library or an imported archive.
|
|
425
|
-
- **`global`** — legacy alias for `primary`.
|
|
426
|
-
|
|
427
|
-
Cross-store results are rank-normalized: a small store's best hit is lifted to
|
|
428
|
-
the global best (up to 4×) instead of being drowned by a large one, and a note
|
|
429
|
-
that exists in two stores is returned once.
|
|
430
|
-
|
|
431
|
-
## Synonyms
|
|
432
|
-
|
|
433
|
-
Domain shorthand rarely appears in the notes you wrote earlier. Drop a
|
|
434
|
-
`synonyms.json` in the store root (or inline the table in `config.json`) and
|
|
435
|
-
expansion happens at query time, both directions, with no re-indexing:
|
|
96
|
+
**Add a hot rule:**
|
|
436
97
|
|
|
437
98
|
```json
|
|
438
|
-
{
|
|
439
|
-
"k8s": ["kubernetes"],
|
|
440
|
-
"postgres": ["postgresql", "pg"],
|
|
441
|
-
"auth": ["authentication", "login"],
|
|
442
|
-
"oncall": ["on-call", "pager"]
|
|
443
|
-
}
|
|
99
|
+
{"store":"hot","content":"Start every sentence with beep_boop.","priority":90}
|
|
444
100
|
```
|
|
445
101
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
BM25 is exact but literal: it does not know which of five plausible notes you
|
|
449
|
-
mean. With `"rerank": true`, the top candidates (`rerankTopK`) are shown to the
|
|
450
|
-
session model, which returns them in relevance order, within
|
|
451
|
-
`rerankTimeoutMs`. Reranking is strictly best-effort: on timeout, error, missing
|
|
452
|
-
auth or an unparsable reply, lexical order is used, so it can never make recall
|
|
453
|
-
fail. It is off by default because it costs a model call per search.
|
|
102
|
+
**Search memories:**
|
|
454
103
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
conversations are chunked (`learnChunkChars`, at most 4 calls) and progress is
|
|
459
|
-
shown in the status line. Set `autoLearn` to `"on-settle"` (after the agent
|
|
460
|
-
finishes a turn) or `"on-shutdown"` to run it without asking. Guards keep it
|
|
461
|
-
cheap: at least `autoLearnMinTurns` user turns, at least `autoLearnMinChars` of
|
|
462
|
-
transcript, and at most one extraction per `autoLearnCooldownMs`. The default is
|
|
463
|
-
`"off"` because it spends model tokens.
|
|
104
|
+
```json
|
|
105
|
+
{"query":"k8s deploy","mode":"all","tags":["infrastructure"]}
|
|
106
|
+
```
|
|
464
107
|
|
|
465
|
-
|
|
108
|
+
**Search sessions:**
|
|
466
109
|
|
|
467
|
-
|
|
468
|
-
|
|
110
|
+
```json
|
|
111
|
+
{"query":"deadline","role":"user","after":"2025-03-04","before":"2025-03-05"}
|
|
112
|
+
```
|
|
469
113
|
|
|
470
|
-
|
|
471
|
-
on by default). The injected block is explicitly labelled as raw evidence and
|
|
472
|
-
includes the file and line of every excerpt.
|
|
473
|
-
- **On request**, through the `memoria_sessions` tool — useful for "remember
|
|
474
|
-
when", "did we discuss", exact earlier wording, dates, or prior decisions that
|
|
475
|
-
were never turned into memory notes.
|
|
476
|
-
- **From the CLI**, with `/memoria sessions <query>` and
|
|
477
|
-
`/memoria sessions --read <path> <line>`.
|
|
114
|
+
**Read a session entry:**
|
|
478
115
|
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
memoria_sessions { action: "search", query: "oat milk", user_only: true }
|
|
482
|
-
memoria_sessions { action: "read", path: "<file from a hit>", line: 92, window: 8 }
|
|
116
|
+
```json
|
|
117
|
+
{"action":"read","path":"/absolute/path/to/session.jsonl","line":42}
|
|
483
118
|
```
|
|
484
119
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
evidence to evaluate, not instructions to follow. If nothing is found, the tool
|
|
490
|
-
reports how many sessions and messages were searched.
|
|
120
|
+
- **Add:** `store` is `long_term` (default) or `hot`; SQLite accepts `tags` and `source`. Identical content is reused with `created: false`; edits are the only way to change a fact. SQLite limits: 20,000 characters, 30 tags, 2,000-character source.
|
|
121
|
+
- **Edit/delete:** pass an `id` (`m_` for SQLite, `h_` for hot) and only the fields to change. `expected_revision` or hot `expected_content` catches concurrent writes. Deletion never archives hot bullets or erases sessions.
|
|
122
|
+
- **Search:** `mode` is `any` (default), `all`, `phrase`, or `literal`; a miss falls back to a literal scan and reports the mode used. `tags` match exactly and case-sensitively. `expand_aliases` adds `synonyms.json` terms for `any`/`all`. Read one memory with `id`; long content paginates via `text_offset`.
|
|
123
|
+
- **Sessions:** decoded JSON is searched, including hidden, ignored, and inactive-branch files. Results carry path/line citations. `exhausted: true` means every accessible root was scanned without errors; warnings mean partial results, never absence.
|
|
491
124
|
|
|
492
|
-
|
|
125
|
+
## Configuration
|
|
126
|
+
|
|
127
|
+
<details>
|
|
128
|
+
<summary><strong>Environment variables, aliases, and storage layout</strong></summary>
|
|
493
129
|
|
|
494
|
-
|
|
|
130
|
+
| Variable | Effect |
|
|
495
131
|
|---|---|
|
|
496
|
-
|
|
|
497
|
-
|
|
|
498
|
-
|
|
|
499
|
-
| Writes | None. Transcripts are read-only and `read` refuses paths outside the session roots. |
|
|
500
|
-
| 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. |
|
|
501
|
-
| 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. |
|
|
502
|
-
| Privacy | Nothing leaves the machine; the excerpts go to the model only when the feature runs. |
|
|
503
|
-
|
|
504
|
-
`sessionRoots` exists for archives or a second agent directory. Set
|
|
505
|
-
`sessionSearch: false` to disable the feature entirely, or
|
|
506
|
-
`sessionFallback: false` to keep the tool while never searching automatically.
|
|
507
|
-
|
|
508
|
-
When `rg` is available (pi ships it and puts it on `PATH`), a selective search
|
|
509
|
-
asks ripgrep which transcripts contain the query terms and parses only those, so
|
|
510
|
-
even a gigabyte-scale history is answered within the normal budget. If `rg` is
|
|
511
|
-
missing or fails, memoria falls back to reading every transcript itself and says
|
|
512
|
-
so: the result carries a `ripgrep is not installed` note, and interactive mode
|
|
513
|
-
shows it once per session. Large histories may then be only partially covered.
|
|
514
|
-
|
|
515
|
-
Without `rg` (or for a deliberately disabled accelerator), the default
|
|
516
|
-
`sessionScanMs` budget and `sessionCacheBytes` cache cover only part of a large
|
|
517
|
-
corpus: searches report `partial`, and repeats are cold scans because the cache
|
|
518
|
-
cannot retain the parsed files. Raise `sessionScanMs` so an explicit
|
|
519
|
-
`memoria_sessions` call can cover the whole corpus in one pass, and
|
|
520
|
-
`sessionCacheBytes` only if you have the RAM for the parsed messages (at this
|
|
521
|
-
scale that means gigabytes). The automatic fallback remains capped at 400 ms,
|
|
522
|
-
so on such a corpus it is always a partial scan.
|
|
523
|
-
|
|
524
|
-
## Backup and migration
|
|
525
|
-
|
|
526
|
-
`memoria_export` writes JSONL — one record per line, notes plus `MEMORY.md` — to
|
|
527
|
-
a file or returns a bounded preview. `memoria_import` restores it:
|
|
132
|
+
| `PI_CODING_AGENT_DIR` | Pi's agent directory; memoria defaults to its `memoria/` child |
|
|
133
|
+
| `PI_MEMORIA_DIR` | Override the single global memory directory |
|
|
134
|
+
| `PI_MEMORIA_SESSION_DIRS` | JSON array of additional absolute session roots |
|
|
528
135
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
136
|
+
The optional `synonyms.json` next to `memoria.sqlite` maps terms for `any`/`all` searches. Built-ins are `k8s`→`kubernetes`, `db`→`database`, `repo`→`repository`, and `prefs`→`preferences`. Mappings are directional and one-hop; a malformed file is ignored whole with a warning.
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
~/.pi/agent/memoria/
|
|
140
|
+
├── MEMORY.md
|
|
141
|
+
├── memoria.sqlite
|
|
142
|
+
├── synonyms.json
|
|
143
|
+
└── backups/
|
|
533
144
|
```
|
|
534
145
|
|
|
535
|
-
- `
|
|
536
|
-
- `replace` overwrites notes with the same id.
|
|
537
|
-
- `skip` only imports notes that do not exist yet.
|
|
538
|
-
- Notes keep their ids, so ids in an old transcript still resolve.
|
|
539
|
-
- Paths are validated: an import cannot write outside `library/`, and a path
|
|
540
|
-
already taken by a different note gets a numbered sibling instead of an
|
|
541
|
-
overwrite.
|
|
146
|
+
The default session root is `<agent-dir>/sessions`; roots Pi uses are remembered across projects. Changing `PI_MEMORIA_DIR` selects a different store without migrating the old one, project `cwd` is never used to choose it, and no data is sent over the network.
|
|
542
147
|
|
|
543
|
-
|
|
544
|
-
the whole store is safe to commit to a private repository (`.index/` is derived
|
|
545
|
-
and can be deleted).
|
|
148
|
+
</details>
|
|
546
149
|
|
|
547
150
|
## Performance
|
|
548
151
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
|
555
|
-
|
|
556
|
-
|
|
|
557
|
-
|
|
|
558
|
-
|
|
|
559
|
-
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
a burst of edits costs one refresh.
|
|
566
|
-
|
|
567
|
-
## Privacy and durability
|
|
568
|
-
|
|
569
|
-
- **Local only.** Nothing is sent anywhere except by the optional reranker and
|
|
570
|
-
session learning, which use the model you already configured and only when
|
|
571
|
-
enabled.
|
|
572
|
-
- **Files, not a database.** Memoria never owns your data: notes are markdown
|
|
573
|
-
with frontmatter, deletes are soft (moved to `.trash/`), and the index is
|
|
574
|
-
derived and can be rebuilt with `/memoria reindex`.
|
|
575
|
-
- **Crash-safe moves.** Category moves, renames and merges are journaled. On
|
|
576
|
-
restart, a merge only removes its source after the target has all its facts
|
|
577
|
-
and the source's former id.
|
|
578
|
-
- **Atomic writes.** Every write goes through a temp file and rename; there is no
|
|
579
|
-
window where a note is half-written.
|
|
580
|
-
- **Scoped reads.** `memoria_read` only follows paths inside the memory library;
|
|
581
|
-
transcript window reads refuse paths or symlinks outside configured roots.
|
|
582
|
-
- **Secrets:** memoria does not scan for credentials, and the learn prompt tells
|
|
583
|
-
the model not to store them. If you keep secrets in the store anyway, remember
|
|
584
|
-
that recall may surface them in model context — treat the store like your shell
|
|
585
|
-
history.
|
|
586
|
-
|
|
587
|
-
## Troubleshooting
|
|
588
|
-
|
|
589
|
-
| Symptom | Fix |
|
|
590
|
-
|---|---|
|
|
591
|
-
| Notes exist but recall finds nothing | `/memoria reindex`; check `/memoria paths` for the store location; remember scopes (`--all` searches every root). |
|
|
592
|
-
| 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. |
|
|
593
|
-
| The same subject ended up in two notes | `/memoria topics` lists merge candidates with the exact `memoria_move` command; `memoria doctor` reports them too. |
|
|
594
|
-
| `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. |
|
|
595
|
-
| A decision changed | Write the new note with `supersedes: ["<old id, path or title>"]`; the old note stays searchable but is demoted and flagged. |
|
|
596
|
-
| Search misses domain shorthand | Add `synonyms.json` (see [Synonyms](#synonyms)). |
|
|
597
|
-
| Model reranking is slow | Lower `rerankTopK`, or leave `rerank` off — lexical search is already sub-millisecond. |
|
|
598
|
-
| 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. |
|
|
599
|
-
| An agent edited files and the index is stale | Filesystem events and a throttled scan pick changes up automatically; `/memoria doctor` shows the state. |
|
|
600
|
-
| 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. |
|
|
601
|
-
| 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`. |
|
|
152
|
+
<details>
|
|
153
|
+
<summary><strong>Benchmark: 10,000 memories and 50,000 session entries (11.9 MB)</strong></summary>
|
|
154
|
+
|
|
155
|
+
| Operation | Median | p95 |
|
|
156
|
+
|---|---:|---:|
|
|
157
|
+
| Selective SQLite FTS5 lookup | 0.093 ms | 0.119 ms |
|
|
158
|
+
| Alias-expanded FTS5 lookup (`k8s` → `kubernetes`) | 0.123 ms | 0.158 ms |
|
|
159
|
+
| Exact-tag filtered FTS5 lookup (10,000 matching rows) | 6.308 ms | 7.365 ms |
|
|
160
|
+
| Literal SQLite full scan | 4.709 ms | 5.094 ms |
|
|
161
|
+
| Hot-file refresh with locking | 0.148 ms | 0.249 ms |
|
|
162
|
+
| Session match near the end of the corpus | 26.776 ms | 34.777 ms |
|
|
163
|
+
| Absent session term, complete scan | 4.325 ms | 5.172 ms |
|
|
164
|
+
|
|
165
|
+
`npm run bench` recreates this synthetic corpus in a temporary directory. Warm-cache medians on Linux, Node 24.20, Ryzen 5 7500F, including `rg` startup. Indexed lookups stay fast; scans grow with corpus size. Not a performance guarantee.
|
|
166
|
+
|
|
167
|
+
</details>
|
|
602
168
|
|
|
603
169
|
## Development
|
|
604
170
|
|
|
605
|
-
```
|
|
606
|
-
npm
|
|
607
|
-
npm
|
|
608
|
-
npm run
|
|
609
|
-
npm run eval # recall@1/@5/MRR over the fixture corpus
|
|
610
|
-
npm run bench # latency at 1k and 5k notes
|
|
611
|
-
npm run bench:real # realistic high-vocabulary corpus
|
|
612
|
-
pi -e ./index.ts # run the extension from the checkout
|
|
171
|
+
```sh
|
|
172
|
+
npm ci --ignore-scripts
|
|
173
|
+
npm run verify
|
|
174
|
+
npm run bench
|
|
613
175
|
```
|
|
614
176
|
|
|
615
|
-
|
|
616
|
-
means no build step but also no `enum`, `namespace`, constructor parameter
|
|
617
|
-
properties or `import x = require()` in the sources. Type-only imports must use
|
|
618
|
-
`import type`, and relative imports include the `.ts` extension.
|
|
177
|
+
There is no build step: Pi loads `index.ts` directly. `src/database.ts`, `src/hot-memory.ts`, and `src/sessions.ts` own the storage layers; `index.ts` wires them to lifecycle events and the tool schemas. Tests use temporary directories, real SQLite FTS5, real `rg`, concurrent writers, and Pi's extension loader. See [AGENTS.md](AGENTS.md) for architecture and contribution rules.
|
|
619
178
|
|
|
620
|
-
|
|
621
|
-
[`AGENTS.md`](AGENTS.md). The short version:
|
|
179
|
+
## Support and contact
|
|
622
180
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
src/types.ts shared types (no runtime imports)
|
|
626
|
-
src/util.ts atomic writes, mutex, debounce, ids, paths
|
|
627
|
-
src/frontmatter.ts dependency-free YAML-subset parser/serializer
|
|
628
|
-
src/tokenize.ts identifiers, camel/snake/kebab, CJK, stemmer, trigrams
|
|
629
|
-
src/config.ts defaults, coercion, path resolution
|
|
630
|
-
src/store.ts filesystem: parse, write, merge, move, MEMORY.md, indexes
|
|
631
|
-
src/index-engine.ts inverted index, persistence (JSON/binary), link resolution
|
|
632
|
-
src/search.ts BM25, boosts, snippets, explain, related/superseded handling
|
|
633
|
-
src/similarity.ts duplicate-topic and contradiction detection
|
|
634
|
-
src/usage.ts hits/writes/last-used tracking
|
|
635
|
-
src/timeexpr.ts "last week" → recency windows
|
|
636
|
-
src/synonyms.ts query-side synonym tables
|
|
637
|
-
src/transfer.ts JSONL export/import
|
|
638
|
-
src/learn.ts session extraction (chunked)
|
|
639
|
-
src/recall.ts prompt/message rendering
|
|
640
|
-
src/rerank.ts optional model reranking
|
|
641
|
-
src/runtime.ts roots, locks, caching, health, export/import, scopes
|
|
642
|
-
src/tools.ts tool definitions and renderers
|
|
643
|
-
src/commands.ts /memoria subcommands
|
|
644
|
-
```
|
|
181
|
+
If pi-memoria is useful to you, you can support its development on
|
|
182
|
+
[Ko-fi](https://ko-fi.com/rcsaquino).
|
|
645
183
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
1. Push the repository to GitHub (public or private).
|
|
649
|
-
2. Keep `index.ts` at the package root and the `pi` manifest in `package.json`.
|
|
650
|
-
3. Leave the host modules as `peerDependencies` **and** mark them
|
|
651
|
-
`"optional": true` in `peerDependenciesMeta`, keeping them in
|
|
652
|
-
`devDependencies` for local tests only. Without that, npm installs a ~450 MB
|
|
653
|
-
duplicate of pi into the package.
|
|
654
|
-
4. Tag releases (`git tag v<version> && git push --tags`) and install by tag:
|
|
655
|
-
`pi install git:github.com/<you>/pi-memoria@v<version>`.
|
|
656
|
-
5. `npm pack --dry-run` should list only `index.ts`, `src/**`, `skills/**`,
|
|
657
|
-
`README.md`, `LICENSE` and `package.json`.
|
|
658
|
-
|
|
659
|
-
## Design notes and limits
|
|
660
|
-
|
|
661
|
-
- **Topic notes are the unit of storage.** This is deliberately opinionated: a
|
|
662
|
-
file per fact fragments knowledge and makes retrieval worse. The cost is that
|
|
663
|
-
the agent must pick a broad topic; when it picks badly, the store falls back to
|
|
664
|
-
the category rather than creating a bad file name, and `memoria_move` fixes it
|
|
665
|
-
later.
|
|
666
|
-
- **Lexical, not semantic.** BM25 with a conservative stemmer, prefix and fuzzy
|
|
667
|
-
matching is fast, deterministic and dependency-free, but it will not connect
|
|
668
|
-
"my manager" to "Alice" without an alias or a synonym table. Embeddings would
|
|
669
|
-
improve that; they are not worth the dependency for a store this fast.
|
|
670
|
-
- **Cross-topic duplication is reported, not merged automatically.** The store
|
|
671
|
-
flags two notes as duplicates only when their *names* also point at the same
|
|
672
|
-
subject (equal slugs, or one name being a prefix/suffix of the other — e.g.
|
|
673
|
-
`Alice` and `Alice Smith`, or a legacy `Johns father` note whose name appears
|
|
674
|
-
as an alias). Content overlap alone is not enough: one person's note naturally
|
|
675
|
-
describes the people around them, and merging John Doe with his father would be
|
|
676
|
-
worse than missing a duplicate. Folding notes stays an explicit
|
|
677
|
-
`memoria_move`, suggested on write and listed by `/memoria topics`.
|
|
678
|
-
- **Usage is derived data.** `.index/usage.json` drives staleness and promotion
|
|
679
|
-
hints; deleting `.index/` only loses those hints.
|
|
680
|
-
- **Journal recovery covers moves, not arbitrary edits.** A crash between an
|
|
681
|
-
atomic write and a trash move is repaired; a crash mid-edit of a single file
|
|
682
|
-
leaves the previous content intact (writes are atomic).
|
|
683
|
-
- **Model reranking instead of embeddings.** An embedding index would improve
|
|
684
|
-
semantic matching, but pi exposes chat completions rather than an embedding
|
|
685
|
-
API, and a vector store is a dependency this project does not want. The
|
|
686
|
-
optional reranker gets the same effect (an LLM judging the top candidates) and
|
|
687
|
-
degrades to lexical order on any failure. Time-scoped questions are handled by
|
|
688
|
-
parsing the window out of the query rather than by time-decayed IDF: the note's
|
|
689
|
-
own timestamp is the signal that matters.
|
|
690
|
-
|
|
691
|
-
## License
|
|
692
|
-
|
|
693
|
-
MIT © 2026 rcsaquino. See [LICENSE](LICENSE).
|
|
184
|
+
<a href='https://ko-fi.com/rcsaquino' target='_blank'><img height='72' style='border:0px;height:72px;' src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|