@seangle/pi-remember 0.2.0 → 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/CHANGELOG.md +41 -9
- package/README.md +149 -223
- package/dist/command.js +56 -0
- package/dist/context.js +57 -0
- package/dist/index.js +124 -0
- package/dist/tools.js +552 -0
- package/package.json +23 -24
- package/command.ts +0 -36
- package/index.ts +0 -751
- package/inject.ts +0 -45
- package/migration.ts +0 -458
- package/notes.ts +0 -131
- package/search.ts +0 -250
- package/store.ts +0 -730
- package/summary.ts +0 -70
- package/workspace.ts +0 -158
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.3.0
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Breaking replacement of the standalone 0.2.0 implementation, now maintained in
|
|
6
|
+
[Esensats/agents-remember](https://github.com/Esensats/agents-remember).
|
|
7
|
+
The npm name and install command are unchanged.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
- Stable session context with on-demand note retrieval.
|
|
9
|
-
- Explicit local initialization, memo updates, recoverable deletion and undo.
|
|
10
|
-
- Versioned storage with backed-up migration from the legacy note-reference format.
|
|
11
|
-
- Restore-path validation that refuses redirected recovery files and note directories.
|
|
12
|
-
- Pi package metadata and a source-only npm artifact; no build step.
|
|
9
|
+
### Changed
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
- Shared host-independent `@seangle/remember-core@0.1.0` and explicit
|
|
12
|
+
`@seangle/remember-migrators@0.1.0` dependencies.
|
|
13
|
+
- Workspace memory defaults to `.agents/memory`, shared with compatible adapters,
|
|
14
|
+
instead of `.pi/memory`. Global memory remains Pi-owned.
|
|
15
|
+
- Published plain ESM JavaScript instead of runtime TypeScript.
|
|
16
|
+
- Snapshot rebuilt before each run in Pi's named `remember` prompt section;
|
|
17
|
+
unchanged bytes remain stable and external changes appear on the next run.
|
|
18
|
+
- Environment configuration uses `REMEMBER_*`. In particular,
|
|
19
|
+
`PI_REMEMBER_DIR` becomes `REMEMBER_GLOBAL_DIR`.
|
|
20
|
+
- New timestamps use minute-precision UTC; existing valid timestamps are retained.
|
|
21
|
+
|
|
22
|
+
### Migration
|
|
23
|
+
|
|
24
|
+
- New `memory_migrate` tool and `/memory migrate` command.
|
|
25
|
+
- Migration is explicit, supports recognized legacy version-1 stores, preserves
|
|
26
|
+
current fact/note IDs and paths, and reports skipped invalid/duplicate content.
|
|
27
|
+
- Local migration leaves the legacy source intact and refuses existing targets.
|
|
28
|
+
- Global legacy stores require explicit in-place migration with source and target
|
|
29
|
+
set to the global directory.
|
|
30
|
+
- Old recovery history and migration artifacts are not converted. They remain
|
|
31
|
+
at the source; in-place adoption leaves old files present but does not promise
|
|
32
|
+
compatibility. Back up stores and stop all writers before migrating.
|
|
33
|
+
- Populated unmarked stores are refused, not silently adopted. Upgrade pre-v1
|
|
34
|
+
formats with the legacy 0.2.0 implementation first.
|
|
35
|
+
|
|
36
|
+
### Removed
|
|
37
|
+
|
|
38
|
+
- Opt-in automatic exit-summary model calls and `PI_REMEMBER_EXIT_SUMMARY*`
|
|
39
|
+
configuration. `/memory update` remains available for explicit review.
|
|
40
|
+
|
|
41
|
+
See [README.md](README.md) for upgrade instructions and concurrency limits.
|
|
42
|
+
|
|
43
|
+
## 0.2.0 and earlier
|
|
44
|
+
|
|
45
|
+
Standalone implementation and historical changes:
|
|
46
|
+
[Esensats/pi-remember](https://github.com/Esensats/pi-remember).
|
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
# pi-remember
|
|
1
|
+
# @seangle/pi-remember
|
|
2
2
|
|
|
3
|
-
Durable memory for [
|
|
3
|
+
Durable, plain-file memory for the [Pi coding agent](https://pi.dev/): curated
|
|
4
|
+
facts, Markdown notes, lexical search, stable memo IDs, recovery, and a
|
|
5
|
+
byte-stable session snapshot. Save knowledge that was expensive to obtain and
|
|
6
|
+
will matter again—not transcripts, todos, or temporary debugging state.
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`root` defaults to the session working directory. `PI_REMEMBER_DIR` overrides the global directory. Specific software/configuration knowledge belongs in its workspace even when the software is installed machine-wide.
|
|
8
|
+
This is the Pi adapter for
|
|
9
|
+
[agents-remember](https://github.com/Esensats/agents-remember). Starting with
|
|
10
|
+
**0.3.0**, it replaces the standalone implementation while retaining the same
|
|
11
|
+
npm package name. The unscoped `pi-remember` package is unrelated.
|
|
11
12
|
|
|
12
13
|
## Install
|
|
13
14
|
|
|
@@ -15,250 +16,175 @@ Durable memory for [pi](https://pi.dev/): a curated, plain-file knowledge cache
|
|
|
15
16
|
pi install npm:@seangle/pi-remember
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
Restart Pi
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
**Upgrading from a manually installed copy:** remove or move the old extension directory out of `~/.pi/agent/extensions/` before loading the npm package, so the same tools are not registered twice. Keep your memory stores; they live separately from the extension. Review the migration section below before upgrading existing stores.
|
|
23
|
-
|
|
24
|
-
Source and issues: [Esensats/pi-remember](https://github.com/Esensats/pi-remember). The npm package is **`@seangle/pi-remember`**, not the unrelated unscoped `pi-remember` package.
|
|
25
|
-
|
|
26
|
-
## Architecture and file format
|
|
27
|
-
|
|
28
|
-
There is **no database, daemon, embedding service, or durable duplicate index**. Facts live in `MEMORY.md`; detailed notes are standalone Markdown files whose frontmatter is authoritative. A note catalogue is derived from those files when needed, never saved back into `MEMORY.md`.
|
|
29
|
-
|
|
30
|
-
Each store can contain:
|
|
31
|
-
|
|
32
|
-
| Path | Contents |
|
|
33
|
-
|---|---|
|
|
34
|
-
| `MEMORY.md` | One-line facts and their metadata comments; not note references |
|
|
35
|
-
| `notes/<YYYY-MM-DD>-<slug>.md` | Standalone notes with YAML frontmatter and a Markdown body |
|
|
36
|
-
| `pi-remember.json` | Store format marker: `{"version": 1}` |
|
|
37
|
-
| `recovery/<recovery ID>.json` | Recoverable deletions and updates, including note snapshots |
|
|
38
|
-
| `migrations/v0-to-v1-<uuid>/` | Original-file backups and a manifest from each migration attempt |
|
|
39
|
-
|
|
40
|
-
Directories for notes and recovery are created only as needed. An absent store is not created merely by startup or migration checks.
|
|
41
|
-
|
|
42
|
-
### Facts
|
|
43
|
-
|
|
44
|
-
A fact is one line, at most 300 characters, with an eight-hex memo ID and a creation timestamp in the preceding HTML comment:
|
|
45
|
-
|
|
46
|
-
```markdown
|
|
47
|
-
<!-- 2026-10-18T16:10:33Z [a1b2c3d4] -->
|
|
48
|
-
#lesson Use the workspace's own virtual environment.
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
**Facts currently have no last-modified date.** Updating a fact retains its ID and creation comment. Tags (`#lesson`) and wiki-links (`[[topic]]`) are optional text conventions, not required schema. Existing facts without IDs remain readable, but cannot be targeted by the ID-based update tool.
|
|
52
|
-
|
|
53
|
-
### Notes
|
|
19
|
+
Restart Pi or run `/reload`. Requires Node.js **>=22.15** and Pi host APIs tested
|
|
20
|
+
with **`@earendil-works/pi-coding-agent@1.4.0`**. Published code is compiled ESM
|
|
21
|
+
JavaScript; users do not need a build step. Host SDK packages are peer dependencies
|
|
22
|
+
provided by Pi, not bundled copies.
|
|
54
23
|
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
description: "Python environment conventions"
|
|
58
|
-
memory-id: "e5f6a7b8"
|
|
59
|
-
created-date: "2026-10-18T16:10:33.000Z"
|
|
60
|
-
last-modified-date: "2026-10-18T16:10:33.000Z"
|
|
61
|
-
---
|
|
62
|
-
# Optional body heading
|
|
24
|
+
If you already install this npm package, use:
|
|
63
25
|
|
|
64
|
-
|
|
26
|
+
```sh
|
|
27
|
+
pi update npm:@seangle/pi-remember
|
|
65
28
|
```
|
|
66
29
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Choose an optional `slug` of 1–80 letters/digits separated by hyphens, **without a date, path, or extension**. The filename always receives the UTC creation date and `.md`: `notes/2026-10-18-python-environments.md`. Omitting `slug` uses a description-derived slug for older callers. Filename collisions receive numbered suffixes rather than overwriting files.
|
|
72
|
-
|
|
73
|
-
The parser deliberately supports a **small scalar YAML subset**, not arbitrary YAML:
|
|
74
|
-
|
|
75
|
-
- Exactly the four known fields, each once; blank lines and comments are allowed.
|
|
76
|
-
- Plain scalars, single-quoted scalars (doubled apostrophes), or double-quoted scalars with JSON-compatible escapes. Quote ambiguous values.
|
|
77
|
-
- No nested structures, lists, multiline values, tags, anchors, aliases, unknown fields, or duplicate fields.
|
|
78
|
-
- A nonempty single-line description, an eight-hex ID, and valid ISO timestamps with time zones are required.
|
|
79
|
-
- The header, including delimiters, must fit within **16 KiB of UTF-8**. Metadata discovery reads in 512-byte chunks and stops at the closing delimiter, with a hard cap of 16 KiB plus one lookahead byte, rather than loading large note bodies.
|
|
80
|
-
|
|
81
|
-
A file beginning with a frontmatter delimiter but containing an invalid, incomplete, or oversized header is an error, not a legacy-note fallback. Invalid notes can make catalogue operations fail; optional session injection omits the affected scope instead of breaking the session. Ordinary text without frontmatter is treated as legacy input during migration.
|
|
82
|
-
|
|
83
|
-
## Session context and retrieval
|
|
84
|
-
|
|
85
|
-
At session start, pi-remember builds a snapshot containing facts plus a **frontmatter-only derived note catalogue**: description, memo ID, creation date, last-modified date, and path. Catalogue references may look like `#note [[notes/...]] Description`, but these are generated views, not persistent `MEMORY.md` entries. **Note bodies are not injected**; retrieve them explicitly by ID/path or search them on demand.
|
|
30
|
+
A pinned version stays pinned; change its installation specification to upgrade.
|
|
31
|
+
Remove any manually installed or local-path copy first so two extensions do not
|
|
32
|
+
register the same `memory_*` tools. Keep memory stores: they live separately.
|
|
33
|
+
**Read the migration section before upgrading from 0.2.x.**
|
|
86
34
|
|
|
87
|
-
|
|
35
|
+
## Stores and context
|
|
88
36
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
`memory_search` builds a **query-local BM25F-like lexical ranking** over facts and notes. No embeddings, vector database, persistent search index, or model call is involved.
|
|
37
|
+
| Scope | Default path |
|
|
38
|
+
|---|---|
|
|
39
|
+
| Local | `<workspace>/.agents/memory`—shared with compatible agent adapters |
|
|
40
|
+
| Global | `~/.pi/agent/memory`—Pi-owned |
|
|
94
41
|
|
|
95
|
-
|
|
96
|
-
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
- Managed frontmatter and HTML comments are excluded from searchable body text. Dates and IDs are metadata, not body keywords; use exact-ID reads for identity lookup.
|
|
100
|
-
- Up to 20 results are returned. Search reads bodies on demand; injection/catalogue discovery does not.
|
|
42
|
+
Facts live in `MEMORY.md`; notes live in `notes/<date>-<slug>.md` with validated
|
|
43
|
+
`description`, `memory-id`, `created-date`, and `last-modified-date` frontmatter.
|
|
44
|
+
The `remember.json` marker records store format version 1. Updates and deletions
|
|
45
|
+
produce recovery records for conflict-aware undo.
|
|
101
46
|
|
|
102
|
-
|
|
47
|
+
Facts and a derived note catalogue enter the named `remember` system-prompt
|
|
48
|
+
section. Note bodies remain on disk until read or searched. The snapshot is
|
|
49
|
+
rebuilt before each run; Pi patches the transcript only when its bytes change.
|
|
50
|
+
Each scope has a default 3,000-character budget, with middle truncation for large
|
|
51
|
+
stores. Read/search before assuming the snapshot is complete.
|
|
103
52
|
|
|
104
|
-
|
|
53
|
+
Memory is agent-writable background data, **not trusted instructions**. Do not
|
|
54
|
+
store secrets. Add `.agents/memory/` to a workspace's `.gitignore` if memory should
|
|
55
|
+
remain private; recovery records can contain removed information too.
|
|
105
56
|
|
|
106
|
-
|
|
57
|
+
## Tools and command
|
|
107
58
|
|
|
108
|
-
| Tool |
|
|
59
|
+
| Tool | Purpose | Default scope |
|
|
109
60
|
|---|---|---|
|
|
110
|
-
| `memory_init` |
|
|
111
|
-
| `memory_write` |
|
|
112
|
-
| `memory_store` |
|
|
113
|
-
| `memory_read` |
|
|
114
|
-
| `memory_search` |
|
|
115
|
-
| `memory_update` |
|
|
116
|
-
| `memory_forget` |
|
|
117
|
-
| `memory_restore` |
|
|
118
|
-
| `memory_status` |
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
61
|
+
| `memory_init` | Explicitly initialize local memory in an existing workspace | local |
|
|
62
|
+
| `memory_write` | Append one durable fact, at most 300 characters | local |
|
|
63
|
+
| `memory_store` | Store a Markdown note; `title` means its short description | local |
|
|
64
|
+
| `memory_read` | Read facts/catalogue, filter, or fetch a memo by ID/path | both |
|
|
65
|
+
| `memory_search` | Ranked lexical search over facts and note bodies | both |
|
|
66
|
+
| `memory_update` | Replace one memo's content, preserving identity and path | local |
|
|
67
|
+
| `memory_forget` | Recoverably remove matches by text/description/path/metadata | local |
|
|
68
|
+
| `memory_restore` | Undo a deletion or update with its recovery ID | both |
|
|
69
|
+
| `memory_status` | Inspect paths, counts, configuration, and snapshot size | both |
|
|
70
|
+
| `memory_migrate` | Explicitly migrate a supported legacy store | explicit paths or local defaults |
|
|
71
|
+
|
|
72
|
+
Local reads and writes require an initialized store. Global memory needs no
|
|
73
|
+
explicit initialization. Optional `root` selects another existing workspace's
|
|
74
|
+
local memory for that call; it does not change the session workspace or global
|
|
75
|
+
memory. A memo ID identifies knowledge; a recovery ID identifies an undo record.
|
|
76
|
+
Read before updating and preserve unrelated content.
|
|
77
|
+
|
|
78
|
+
Search is lexical, with exact tokens, field weighting, phrase/coverage boosts,
|
|
79
|
+
and one hit per note. No embeddings, database, model calls, or persistent search
|
|
80
|
+
index are involved. Tool output is bounded and truncation is reported.
|
|
81
|
+
|
|
82
|
+
- `/memory` or `/memory status`: show status without starting an agent turn.
|
|
83
|
+
- `/memory migrate`: migrate the current workspace's recognized legacy store.
|
|
84
|
+
- `/memory update`: ask the current agent to review durable knowledge, using a
|
|
85
|
+
follow-up turn rather than interrupting ongoing work. This can incur normal
|
|
86
|
+
model usage; it is not an automatic exit summarizer.
|
|
87
|
+
|
|
88
|
+
## Upgrading from 0.2.x
|
|
89
|
+
|
|
90
|
+
**0.3.0 is a breaking release.** The extension keeps its npm name but now uses
|
|
91
|
+
shared core/migrator packages and a different local-store location.
|
|
92
|
+
|
|
93
|
+
1. Back up important stores and stop other memory writers.
|
|
94
|
+
2. Upgrade the extension and rename any configured environment variables below.
|
|
95
|
+
3. For each workspace, inspect `memory_migrate({dryRun: true})`. With user
|
|
96
|
+
approval, run `/memory migrate` or `memory_migrate({})`.
|
|
97
|
+
4. Review skipped files/warnings and verify facts/notes before relying on the new
|
|
98
|
+
store. Do not delete the source just because migration completed.
|
|
99
|
+
|
|
100
|
+
The supported source is a legacy **version-1** store with `pi-remember.json`.
|
|
101
|
+
If you still have an older/unmarked legacy format, first upgrade it with the
|
|
102
|
+
legacy 0.2.0 extension. The new adapter does not silently adopt populated,
|
|
103
|
+
unmarked stores or automatically convert old formats on reads.
|
|
104
|
+
|
|
105
|
+
### Workspace memory
|
|
106
|
+
|
|
107
|
+
Migration copies current facts and valid notes from `.pi/memory` to
|
|
108
|
+
`.agents/memory`, preserving memo IDs and note paths and leaving the source
|
|
109
|
+
intact. Invalid notes and duplicate IDs are skipped and reported. Existing
|
|
110
|
+
target stores are refused, not merged.
|
|
111
|
+
|
|
112
|
+
**Recovery history and old migration backups are not copied.** Keep the source
|
|
113
|
+
if you need them. Do not mix old and new writers: the separate local stores will
|
|
114
|
+
diverge, and legacy writers do not understand the new format marker.
|
|
115
|
+
|
|
116
|
+
### Global memory
|
|
117
|
+
|
|
118
|
+
The global path stays `~/.pi/agent/memory`, but an existing legacy store still
|
|
119
|
+
needs an explicit, one-time in-place migration. `/memory migrate` handles local
|
|
120
|
+
memory, not global memory. Use explicit tool arguments (adjust paths if configured):
|
|
139
121
|
|
|
140
122
|
```js
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
123
|
+
memory_migrate({
|
|
124
|
+
source: '~/.pi/agent/memory',
|
|
125
|
+
target: '~/.pi/agent/memory',
|
|
126
|
+
dryRun: true,
|
|
127
|
+
})
|
|
128
|
+
// Only after approval and reviewing the plan:
|
|
129
|
+
memory_migrate({
|
|
130
|
+
source: '~/.pi/agent/memory',
|
|
131
|
+
target: '~/.pi/agent/memory',
|
|
149
132
|
})
|
|
150
|
-
memory_read({root: '~/homelab', scope: 'local', id: 'e5f6a7b8'}) // use returned ID
|
|
151
|
-
memory_update({root: '~/homelab', id: 'e5f6a7b8', content: '# TV server\n\nFull revised body…'})
|
|
152
|
-
memory_restore({root: '~/homelab', scope: 'local', recoveryId: '<returned recovery ID>'})
|
|
153
133
|
```
|
|
154
134
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
A **memo ID** is eight hexadecimal characters, stored in a fact's metadata comment or a note's `memory-id`. A **recovery ID** identifies an undo record, not a memo. Use memo IDs with `memory_read`/`memory_update`, recovery IDs with `memory_restore`.
|
|
160
|
-
|
|
161
|
-
- `memory_read({id})` returns exactly one memo, including the complete stored note text subject to output limits. Do not combine `id` with `query` or `path`.
|
|
162
|
-
- IDs or relative paths present in both scopes are ambiguous with `scope: 'both'`; select `local` or `global`. Duplicate IDs within one scope require manual resolution, not a guessed match.
|
|
163
|
-
- `memory_update` requires a prior read. For facts, pass the replacement single line (≤300 chars); for notes, pass the **full revised body without frontmatter**, including any body heading you want to retain. Preserve unrelated/correct details.
|
|
164
|
-
- A note update preserves description, ID, creation date, and path; **`last-modified-date` advances only on an actual body change**. Fact updates do not add a modified date. Blank content is rejected; normalized/trim-equivalent no-ops leave bytes, timestamps, and recovery state untouched.
|
|
165
|
-
- Actual updates save recovery data before replacing the file. Targeted fact updates preserve unrelated bytes; file replacements are atomic and retain permissions. Extension mutations use pi's file-mutation queues. Note updates/undo use the note-file queue; deletion holds all selected note queues before snapshotting and removing them. New notes and recovery records are published atomically without overwriting existing paths.
|
|
166
|
-
- `memory_forget` saves matching entries and full note snapshots before removing them, including the note files themselves. For notes, the generated identity metadata used for matching contains the ID and creation date, **not the last-modified date**. Use a specific ID/path/description to avoid broad deletions.
|
|
167
|
-
- `memory_restore` must use the **same root** as the original change. It searches only the selected stores. Ambiguous recovery IDs, existing recovered paths/IDs, missing targets, and later edits cause refusal; conflicting recovery records are retained.
|
|
168
|
-
- Update undo checks the memo's identity and exact saved “after” content before restoring “before.” Note undo restores the **snapshot's metadata too**, including its previous last-modified date; it does not stamp the undo time. Successful undo consumes its recovery record. Deletion undo restores saved facts/notes without creating persistent note references and retains saved note permissions. Newly created notes/recovery records default to owner-only access (`0600`); new note/recovery directories use `0700`.
|
|
169
|
-
|
|
170
|
-
## Format versions and automatic migration
|
|
171
|
-
|
|
172
|
-
`pi-remember.json` inside each store records **format version 1**. An absent marker (or version `0`) means legacy format: note references were persisted in `MEMORY.md`, and generated notes could have a timestamp comment plus an automatic H1.
|
|
173
|
-
|
|
174
|
-
Migration runs automatically:
|
|
175
|
-
|
|
176
|
-
- At session startup for existing global and current-workspace stores.
|
|
177
|
-
- Lazily before ordinary reads/searches or mutations of another workspace; restore prepares its selected recovery store before changing it.
|
|
178
|
-
- Around initialization/first writes to establish the version of a newly created store. Missing stores are never created by migration itself.
|
|
179
|
-
|
|
180
|
-
**A read/search can therefore write migration data.** Status inspection does not migrate. Startup migration failures warn when UI is available and omit that scope from injection; explicit operations surface the error. Invalid or future versions are refused rather than guessed or overwritten, and version checks are repeated rather than permanently cached.
|
|
181
|
-
|
|
182
|
-
Migration validates its entire conversion plan before making backups or changing memory data. Each attempt creates **`migrations/v0-to-v1-<uuid>/`** containing a manifest (including modes, timestamps, and inference rules) and read-only, byte-for-byte backups of **all affected originals before any original is mutated**. Backups are retained even after success or a partial failure. The version marker is committed last; interrupted work can be retried with a new backup set. This is not a single multi-file transaction or an automatic rollback mechanism.
|
|
183
|
-
|
|
184
|
-
Conversion preserves existing memo IDs, note paths, fact bytes, unrelated index bytes, and file modes. It removes persisted note references and their managed comments from `MEMORY.md`, moving identity/description/dates into notes. Only the recognized generated legacy timestamp marker and its immediate generated H1/blank line are stripped; user-authored body headings remain. Existing valid frontmatter is retained. Unindexed legacy notes become discoverable standalone notes and receive IDs if needed.
|
|
185
|
-
|
|
186
|
-
Legacy notes had no recorded modified date: migration infers it from the original file's filesystem mtime. Creation comes from existing index/generated metadata where available, otherwise from mtime. These are documented inferences, not reconstructed edit history.
|
|
187
|
-
|
|
188
|
-
Recovery records migrate too, including old deletion records and whole-note update snapshots. **Forgotten notes are not resurrected**: legacy files left behind after deletion are snapshotted into recovery and removed from the active store. Recovery conversion happens before note/index edits so retries can retain identities and conflict checks. Malformed records, unsafe/dangling references, duplicate IDs, or conflicting metadata halt migration rather than silently dropping data.
|
|
135
|
+
A recognized in-place store receives only `remember.json`; its existing files
|
|
136
|
+
are left in place. Old recovery files are not converted or guaranteed compatible
|
|
137
|
+
with the new engine. Do not use the old extension against this store afterward.
|
|
189
138
|
|
|
190
|
-
###
|
|
139
|
+
### Other changes
|
|
191
140
|
|
|
192
|
-
|
|
141
|
+
- `PI_REMEMBER_DIR` becomes `REMEMBER_GLOBAL_DIR`.
|
|
142
|
+
- `PI_REMEMBER_INJECT_CHARS_GLOBAL` and `_LOCAL` become
|
|
143
|
+
`REMEMBER_INJECT_CHARS_GLOBAL` and `_LOCAL`.
|
|
144
|
+
- The old opt-in `PI_REMEMBER_EXIT_SUMMARY*` feature is **not included**. Use
|
|
145
|
+
`/memory update` for an explicit review instead.
|
|
146
|
+
- New timestamps use UTC minute precision; existing valid timestamps remain
|
|
147
|
+
readable and are preserved.
|
|
193
148
|
|
|
194
|
-
|
|
149
|
+
## Configuration
|
|
195
150
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
The reminder asks the agent to read/search relevant memory, add missed durable knowledge, correct confirmed errors with `memory_update`, and recoverably remove obsolete entries. It preserves unrelated/correct knowledge, treats age alone as insufficient evidence, avoids secrets/task state, and permits no changes when nothing useful needs saving. The agent briefly reports its changes or that none were needed. **The command does not authorize remote actions or carry prior remote permission forward.** Reload extension changes with `/reload`.
|
|
204
|
-
|
|
205
|
-
## Configuration and opt-in exit summary
|
|
206
|
-
|
|
207
|
-
| Environment variable | Default | Purpose |
|
|
208
|
-
|---|---|---|
|
|
209
|
-
| `PI_REMEMBER_DIR` | `~/.pi/agent/memory` | Global store directory |
|
|
210
|
-
| `PI_REMEMBER_INJECT_CHARS_GLOBAL` | `3000` | Global snapshot character budget, plus truncation/wrapper overhead |
|
|
211
|
-
| `PI_REMEMBER_INJECT_CHARS_LOCAL` | `3000` | Local snapshot character budget, plus truncation/wrapper overhead |
|
|
212
|
-
| `PI_REMEMBER_EXIT_SUMMARY` | off | Enable with `1`, `true`, `yes`, or `on` (case-insensitive) |
|
|
213
|
-
| `PI_REMEMBER_EXIT_SUMMARY_MODEL` | session model | Optional `provider/model-id` (or model ID); unavailable selection falls back to the session model |
|
|
214
|
-
| `PI_REMEMBER_EXIT_SUMMARY_SCOPE` | `auto` | `global`, `local`, or `auto`: local if the session workdir has `.pi`, otherwise global |
|
|
215
|
-
| `PI_REMEMBER_EXIT_SUMMARY_TIMEOUT_MS` | `10000` | Timeout for waiting on the summary model response |
|
|
216
|
-
|
|
217
|
-
Character budgets and timeout must parse as positive integers; invalid/nonpositive values use defaults. Zero does not disable injection. Configuration is loaded when the extension loads; reload/restart after changing it.
|
|
218
|
-
|
|
219
|
-
Exit summarization runs only on session shutdown with reason **`quit`**, when opted in. It sends up to the last 20,000 characters of user/assistant text from the current session branch to the selected model, asks for at most six concise durable fact lines (or `NONE`), filters the response, and appends accepted facts with normal duplicate checks. It is a model call that may incur cost and send conversation text to that provider—not purely local processing. No cheap model is automatically selected; choose one explicitly if desired.
|
|
220
|
-
|
|
221
|
-
A selected but uninitialized local store is **skipped**, not created and not redirected to global. Too little conversation text, missing model/authentication, errors, and timeouts are best-effort skips. The timeout stops waiting for the response; it does **not** cancel the underlying provider request. This opt-in extractor is separate from `/memory update` and does not perform its targeted review/correction workflow.
|
|
222
|
-
|
|
223
|
-
## Git and development
|
|
151
|
+
| Variable | Default |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `REMEMBER_DIR_NAME` | `.agents` (workspace configuration directory, not a store path) |
|
|
154
|
+
| `REMEMBER_GLOBAL_DIR` | `~/.pi/agent/memory` |
|
|
155
|
+
| `REMEMBER_INJECT_CHARS_GLOBAL` | `3000` |
|
|
156
|
+
| `REMEMBER_INJECT_CHARS_LOCAL` | `3000` |
|
|
157
|
+
| `REMEMBER_AUTO_INJECT` | `1` |
|
|
224
158
|
|
|
225
|
-
|
|
159
|
+
Budgets must be positive integers; invalid/nonpositive values fall back to the
|
|
160
|
+
default. Set `REMEMBER_AUTO_INJECT=0` to disable injection, not the tools.
|
|
161
|
+
Configuration loads when the extension loads; restart or `/reload` after changes.
|
|
226
162
|
|
|
227
|
-
|
|
228
|
-
.pi/memory/
|
|
229
|
-
```
|
|
163
|
+
## Concurrency and safety
|
|
230
164
|
|
|
231
|
-
|
|
165
|
+
Pi's mutation queues serialize cooperating operations within one process, not
|
|
166
|
+
across Pi/DSH processes or editors. Avoid simultaneous writes to a shared store,
|
|
167
|
+
including during migration. Path checks are not a sandbox against hostile
|
|
168
|
+
concurrent filesystem changes. Stored paths, credentials, or remembered facts
|
|
169
|
+
never grant permission for remote access.
|
|
232
170
|
|
|
233
|
-
|
|
171
|
+
## Local development
|
|
234
172
|
|
|
235
173
|
```sh
|
|
236
|
-
git clone git@github.com:Esensats/
|
|
237
|
-
cd
|
|
238
|
-
|
|
239
|
-
|
|
174
|
+
git clone git@github.com:Esensats/agents-remember.git
|
|
175
|
+
cd agents-remember
|
|
176
|
+
pnpm install
|
|
177
|
+
npm run check
|
|
178
|
+
npm run test:packed
|
|
179
|
+
pi install ./packages/pi
|
|
240
180
|
```
|
|
241
181
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|---|---|
|
|
248
|
-
| `store.ts` | Facts, standalone note storage/discovery, derived catalogue, IDs, updates, deletion/recovery, safe path lookup |
|
|
249
|
-
| `notes.ts` | Bounded frontmatter parsing/serialization and legacy generated-header handling |
|
|
250
|
-
| `migration.ts` | Version guards, validation/planning, backups, migration lock, conversion of notes and recovery records |
|
|
251
|
-
| `workspace.ts` | Existing-root resolution, explicit initialization, local-access guards |
|
|
252
|
-
| `search.ts` | Query-local lexical ranking and passage selection |
|
|
253
|
-
| `inject.ts` | Stable snapshot construction and middle truncation |
|
|
254
|
-
| `summary.ts` | Exit-summary text extraction, prompt, and response filtering |
|
|
255
|
-
| `command.ts` | `/memory` routing and incremental-review reminder (type-only pi import) |
|
|
256
|
-
| `index.ts` | Host integration: lifecycle, tools, queues, configuration, snapshot refresh, exit-summary model call |
|
|
257
|
-
|
|
258
|
-
Core modules have no pi runtime imports and are tested with plain Node; command behavior uses a stub API, and extension wiring tests stub host imports with Node module hooks.
|
|
182
|
+
Remove an existing npm installation before installing the local path. Local
|
|
183
|
+
packages load in place; rebuild with `npm run build` and run `/reload` after edits.
|
|
184
|
+
Tests use temporary stores, not live memory. `test:packed` additionally installs
|
|
185
|
+
only release tarballs outside the monorepo and reruns the adapter integration
|
|
186
|
+
suite against emitted JavaScript.
|
|
259
187
|
|
|
260
188
|
## License
|
|
261
189
|
|
|
262
|
-
Copyright 2026 Esensats.
|
|
263
|
-
|
|
264
|
-
Licensed under the [Apache License, Version 2.0](LICENSE). Contributions are accepted under the same license.
|
|
190
|
+
Apache-2.0. Copyright 2026 Esensats.
|
package/dist/command.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Incremental review prompt, kept from pi-remember so `/memory update` behaves
|
|
5
|
+
* the same. It asks for evidence-based edits, not a rewrite of the collection.
|
|
6
|
+
*/
|
|
7
|
+
export const MEMORY_UPDATE_PROMPT = `Please review durable memory in light of this conversation and update it only where needed. This is an incremental memory reminder, not a request to overhaul or rewrite the memory collection.
|
|
8
|
+
|
|
9
|
+
- Use the available conversation context (including any compaction summary) to identify durable knowledge that will help in future sessions: established decisions, preferences, hard-won lessons, and confirmed project/server setup or operational details. Save important knowledge you have not already saved.
|
|
10
|
+
- Read the current memory with memory_read and use memory_search for relevant topics; read related notes as needed before changing anything. The injected snapshot may be incomplete. Avoid duplicates and preserve unrelated or still-correct information. Do not scan or rewrite every note just for completeness.
|
|
11
|
+
- Correct or remove information only when this conversation provides evidence that it is outdated or wrong; age alone or not mentioning it is not evidence. Make the smallest targeted changes. Use memory_forget with a specific identifying string for obsolete facts or notes (deletions are recoverable), memory_write for one-line facts, and memory_store for new detailed notes. For memos with IDs, read by ID and use memory_update to edit the relevant file in place instead of forgetting/recreating facts or creating redundant replacement notes; preserve identity, description and unrelated content. Notes carry their own frontmatter; supply only the revised Markdown body, including any body heading worth keeping. Store migration is automatic; do not manually recreate an index.
|
|
12
|
+
- Default to local memory in the current workdir (a shared .agents/memory store that other harnesses may read too), including server-specific knowledge when working in a context directory such as ~/ctx/server-ABC. When the user requests memory in another workspace, use its root on each tool call, including reads and recovery; root is the workspace directory, not its .agents directory. Use memory_status to inspect a store before initialization, and memory_init only when you intend to create local memory in that existing directory; do not initialize a workspace merely to make a search succeed. Global memory is only for user/machine-wide facts, not particular servers, software, repos, or configs. Keep MEMORY.md for one-line facts only; put detailed knowledge in standalone notes with concise descriptions and chosen filename slugs. The date is prepended automatically; descriptions are injected from frontmatter, not duplicated in MEMORY.md.
|
|
13
|
+
- Do not store secrets, credentials, temporary task state, todos, or a transcript/session recap. Work from already available evidence; this command does not authorize remote access or imply that past remote-access permission carries into future sessions.
|
|
14
|
+
- If nothing useful needs changing, leave memory untouched. Briefly report what you added, corrected, or removed, or say that no updates were needed.`;
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
const COMPLETIONS = [
|
|
22
|
+
{ value: "status", label: "status", description: "Show memory store locations, counts, and snapshot size" },
|
|
23
|
+
{ value: "migrate", label: "migrate", description: "Migrate a legacy .pi/memory store into the shared store" },
|
|
24
|
+
{ value: "update", label: "update", description: "Review this conversation and update memory only if needed" },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export function registerMemoryCommand(
|
|
28
|
+
pi ,
|
|
29
|
+
actions ,
|
|
30
|
+
) {
|
|
31
|
+
pi.registerCommand("memory", {
|
|
32
|
+
description: "Show memory status, migrate a legacy store, or review and update memory",
|
|
33
|
+
getArgumentCompletions: (prefix) => {
|
|
34
|
+
const value = prefix.trim().toLowerCase();
|
|
35
|
+
const matches = COMPLETIONS.filter((item) => item.value.startsWith(value));
|
|
36
|
+
return matches.length > 0 ? matches : null;
|
|
37
|
+
},
|
|
38
|
+
handler: async (args, ctx) => {
|
|
39
|
+
const subcommand = args.trim().toLowerCase();
|
|
40
|
+
if (subcommand === "update") {
|
|
41
|
+
// Start a normal turn when idle; otherwise wait for the ongoing work.
|
|
42
|
+
pi.sendUserMessage(MEMORY_UPDATE_PROMPT, { deliverAs: "followUp" });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (subcommand === "migrate") {
|
|
46
|
+
ctx.ui.notify(await actions.migrate(ctx), "info");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (subcommand === "" || subcommand === "status") {
|
|
50
|
+
ctx.ui.notify(await actions.status(ctx), "info");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
ctx.ui.notify("Usage: /memory [status|migrate|update]", "warning");
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resolveStores, } from "@seangle/remember-core";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Deployment policy for the Pi adapter.
|
|
7
|
+
*
|
|
8
|
+
* The workspace store is shared with the DeepSeek Harness (`.agents/memory`), so
|
|
9
|
+
* both harnesses read and write the same facts and notes. The global store stays
|
|
10
|
+
* in Pi's home: machine-wide memory is harness-owned, not a shared file.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
function truthy(value ) {
|
|
21
|
+
return value !== undefined && ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function intEnv(env , name , fallback ) {
|
|
25
|
+
const raw = env[name]?.trim();
|
|
26
|
+
if (!raw) return fallback;
|
|
27
|
+
const value = Number.parseInt(raw, 10);
|
|
28
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Expand a leading `~`; core's workspace resolver does the same for `root`. */
|
|
32
|
+
export function expandHome(input ) {
|
|
33
|
+
if (input === "~") return homedir();
|
|
34
|
+
if (input.startsWith("~/")) return join(homedir(), input.slice(2));
|
|
35
|
+
return input;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function loadConfig(env = process.env) {
|
|
39
|
+
const globalDir = env.REMEMBER_GLOBAL_DIR?.trim();
|
|
40
|
+
return {
|
|
41
|
+
dirName: env.REMEMBER_DIR_NAME?.trim() || ".agents",
|
|
42
|
+
globalDir: globalDir ? expandHome(globalDir) : join(homedir(), ".pi", "agent", "memory"),
|
|
43
|
+
injectCharsGlobal: intEnv(env, "REMEMBER_INJECT_CHARS_GLOBAL", 3000),
|
|
44
|
+
injectCharsLocal: intEnv(env, "REMEMBER_INJECT_CHARS_LOCAL", 3000),
|
|
45
|
+
autoInject: env.REMEMBER_AUTO_INJECT === undefined ? true : truthy(env.REMEMBER_AUTO_INJECT),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Resolve both stores for one workspace root; nothing is created by resolution. */
|
|
50
|
+
export function storesFor(cwd , config ) {
|
|
51
|
+
return resolveStores(cwd, config.globalDir, config.dirName);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Default legacy source for a workspace: pi-remember's own layout. */
|
|
55
|
+
export function legacySource(cwd ) {
|
|
56
|
+
return join(cwd, ".pi", "memory");
|
|
57
|
+
}
|