@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 CHANGED
@@ -1,14 +1,46 @@
1
1
  # Changelog
2
2
 
3
- ## 0.2.0
3
+ ## 0.3.0
4
4
 
5
- First public release as `@seangle/pi-remember`, licensed under Apache-2.0.
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
- - Durable workspace-local and global facts, standalone Markdown notes, and lexical search.
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
- Existing legacy stores migrate automatically when used. Back up stores, review the README's migration and concurrency limits, and upgrade every writer before using the new format.
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 [pi](https://pi.dev/): a curated, plain-file knowledge cache that survives sessions. Save knowledge that was **expensive to obtain** and will matter again—decisions, preferences, lessons, environment quirks, research findings—not todos, debugging state, transcripts, or “the user asked me to X.” Pi's session files (`/resume`) already preserve session continuity.
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
- | Scope | Store directory | Use for |
6
- |---|---|---|
7
- | `local` (default for writes) | `<root>/.pi/memory/` | Knowledge about that workspace, repo, software, server, or configuration |
8
- | `global` | `~/.pi/agent/memory/` | Facts about the user or machine itself: identity, OS, global tool preferences |
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 (or use `/reload` in an existing session) after installation. Use `/memory` for help and ask Pi to remember a durable fact to get started. Local writes require explicit initialization with the `memory_init` tool; global memory needs no initialization.
19
-
20
- Requires Node.js **22.15 or newer**. Tested with Pi **1.4.0** (`@earendil-works/pi-coding-agent`); older Pi versions or other forks may not provide the host APIs this extension uses. Pi loads the TypeScript source directly; no build step or external service is needed.
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
- ```markdown
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
- Confirmed details, rationale, and examples go here.
26
+ ```sh
27
+ pi update npm:@seangle/pi-remember
65
28
  ```
66
29
 
67
- The four frontmatter fields are **`description`, `memory-id`, `created-date`, and `last-modified-date`**. The description is the short catalogue text; it is not a body heading. IDs identify memos independently of their paths.
68
-
69
- `memory_store` retains the parameter name **`title` for backwards compatibility**, but it now means **description**. New descriptions must be nonempty, single-line, and at most 300 characters. Pass Markdown body text as `content`, **without frontmatter**. No H1 is generated; include your own heading in the body if useful.
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
- The snapshot is byte-stable between turns: `before_agent_start` appends the saved block without rereading files or making model calls. Writes/deletions and successful updates/restores to global or current-workspace memory rebuild it; another workspace's local changes do not replace the session's injected local memory. External file edits are not watched—start a fresh session to reliably pick them up.
35
+ ## Stores and context
88
36
 
89
- Each scope has a default **3,000-character snapshot budget**. Oversized content keeps roughly the first 20% and last 80%, replacing the middle with a truncation notice. These are character budgets, not token/byte limits or strict final-output ceilings: the notice, scope headings, and enclosing instructions add overhead. The snapshot can omit entries or cut through an entry; read/search the store before making decisions based on it.
90
-
91
- ### Search
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
- - Unicode-normalized, case-insensitive **exact tokens**, not substring matches; common English stopwords and tokens shorter than two characters are omitted.
96
- - Field weighting favors descriptions and tags/wiki-links; term-frequency saturation and length normalization limit repetition bias.
97
- - Query coverage and contiguous phrase matches boost ranking. Quotes request a phrase boost, **not a mandatory phrase filter**.
98
- - A note's description and body form one document, producing **one hit and one matching-passage snippet per note**, with scope/path/ID and an additional matching-line count where applicable.
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
- For a simple substring filter over fact/catalogue lines and metadata, use `memory_read({query})` instead.
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
- ## Tools
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
- Every tool accepts optional `root`; it selects only local memory. Scope defaults differ intentionally:
57
+ ## Tools and command
107
58
 
108
- | Tool | Parameters / behavior | Default scope |
59
+ | Tool | Purpose | Default scope |
109
60
  |---|---|---|
110
- | `memory_init` | `{root?}`: explicitly initialize local memory in an existing workspace; preserve existing contents | local only |
111
- | `memory_write` | `{content, scope?, root?}`: append one fact (≤300 chars) | `local` |
112
- | `memory_store` | `{title, content, slug?, scope?, root?}`: create a standalone frontmatter note; `title` means description | `local` |
113
- | `memory_read` | `{id?, query?, path?, scope?, root?}`: facts + derived catalogue, substring filter, exact memo lookup, or file read | `both` |
114
- | `memory_search` | `{query, scope?, root?}`: ranked fact/note search with snippets | `both` |
115
- | `memory_update` | `{id, content, scope?, root?}`: revise one existing memo in place; return recovery ID on change | `local` |
116
- | `memory_forget` | `{text, scope?, root?}`: case-insensitive substring deletion by fact text or note description/path/identity metadata, **not note body** | `local` |
117
- | `memory_restore` | `{recoveryId, scope?, root?}`: undo a deletion or update | `both` |
118
- | `memory_status` | `{root?}`: store paths, counts, fact-file sizes, recoveries, and current session snapshot size | both stores |
119
-
120
- New writes use normalized-text duplicate checks against facts and the derived catalogue. Duplicate note storage returns existing references **without creating or overwriting a note**; read and update the existing memo instead. Similar entries in the other scope can produce non-blocking warnings. These are text heuristics, not semantic deduplication or a body-comparison service.
121
-
122
- Read/search outputs are bounded: catalogue/search output uses 20,000-byte / 400-line limits; note/ID reads use 30,000-byte / 600-line limits. Truncation keeps the tail and is reported. For a larger file, use pi's ordinary file-reading tools with offsets.
123
-
124
- ## Workspaces, initialization, and security
125
-
126
- `root` is the **workspace directory containing `.pi`**, not `.pi` or the memory directory. Absolute paths, `~/` paths, and paths relative to the session cwd are accepted. The workspace must already exist; tools never create it. Symlinked workspace roots are canonicalized, and local access is rechecked after mutation-queue waits to avoid following a replaced root.
127
-
128
- Roots are **per call**: accessing another workspace does not change subsequent default calls or the session's injected local memory. Global-only calls ignore `root` entirely and do not depend on local initialization. `scope: 'both'` combines global memory with the selected workspace's local store; it does not search every workspace.
129
-
130
- Before using a local store, call `memory_init({root})` **only when you intend to create memory there**:
131
-
132
- - It exclusively creates an empty `.pi/memory/MEMORY.md` if missing, never truncates existing memory, and never recreates a vanished workspace. Notes/recovery directories stay lazy; format setup may create version/migration files.
133
- - An existing regular `MEMORY.md` already satisfies initialization; legacy data still needs the automatic format migration described below.
134
- - Local tools, including reads/searches and default `both` calls, fail with an initialization instruction if `MEMORY.md` is missing. A notes-only local store still requires that file.
135
- - `memory_status({root})` is the diagnostic exception: it works before initialization and never creates files or migrates the selected store. Its snapshot counts always describe the **current session**, not the selected root. Note counts include recursively discovered valid frontmatter notes. A note-only global store is reported even without a facts file.
136
- - Global memory needs no explicit initialization. Use `scope: 'global'` from an uninitialized workspace rather than creating unwanted local memory just to make a search succeed.
137
-
138
- Example tool calls from a session in another directory:
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
- memory_status({root: '~/homelab'})
142
- memory_init({root: '~/homelab'}) // only if you intend to create its local store
143
- memory_search({root: '~/homelab', scope: 'local', query: 'TV server'})
144
- memory_store({
145
- root: '~/homelab',
146
- title: 'TV server configuration', // stored as description
147
- slug: 'tv-server',
148
- content: '# TV server\n\nConfirmed durable details…',
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
- Safe explicit path resolution rejects traversal/out-of-scope files and symlinks escaping a store. Catalogue discovery skips symlink aliases, and note/recovery writes refuse redirected subdirectories. Migration refuses symlinks within its input paths, including internal aliases. These checks are not a sandbox against hostile concurrent filesystem changes. Memory tools operate on local files with pi's permissions: `root` is not an SSH destination, and stored knowledge never grants remote-access permission. Do not store secrets or credentials.
156
-
157
- ## IDs, updates, deletion, and undo
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
- ### Concurrency and compatibility limits
139
+ ### Other changes
191
140
 
192
- In-process migration calls for the same canonical store share one run. An exclusively created **`.pi-remember-migration.lock`** serializes migrators across processes; existing locks cause a clear refusal, are never stolen, and require manual inspection if left after a crash. Backups are followed by a source-change check before applying the plan.
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
- The migration lock is **not a general cross-process writer lock**. Pi's ordinary file-mutation queues coordinate cooperating operations within the host process, not other pi instances, editors, sync tools, or old binaries. Avoid concurrent writers during migration; a lock cannot make uncooperative writers safe.
149
+ ## Configuration
195
150
 
196
- **Old binaries are not safe to use against a migrated store. There is no backward-writer support or dual-format index.** Upgrade every writer; do not downgrade by deleting the version marker. New code refuses future versions, but old code may not understand the marker at all. Preserve the migration backups before any manual recovery or downgrade work.
197
-
198
- ## Commands
199
-
200
- - **`/memory`** shows status in a notification, without starting an agent turn.
201
- - **`/memory update`** sends an incremental memory-review reminder to the **current session's agent**. When idle it starts a normal turn; when busy it queues a **follow-up**, not an interruption. It uses the available conversation context (including compaction summaries) and normal tools, not a separate summarizer or bulk rewrite. Unknown arguments show usage; `update` is offered in completion.
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
- To keep workspace memory private, add this to the project's `.gitignore`:
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
- ```gitignore
228
- .pi/memory/
229
- ```
163
+ ## Concurrency and safety
230
164
 
231
- Committing memory shares it with the team. Review not just facts/notes but **recovery snapshots and migration backups**, which may retain removed information. Files are plain Markdown/JSON and can be reviewed in git; concurrent merges still require care with unique IDs and metadata.
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
- No build step: pi loads TypeScript via jiti. There are no declared npm dependencies; host imports (`pi`, TypeBox, and TUI) are supplied by pi. For a one-off load, use `pi --extension ./index.ts` from this directory.
171
+ ## Local development
234
172
 
235
173
  ```sh
236
- git clone git@github.com:Esensats/pi-remember.git
237
- cd pi-remember
238
- npm test
239
- # node --experimental-strip-types --test "test/*.test.ts"
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
- Use **Node >=22.15**: tests execute TypeScript directly with Node's experimental type stripping, and integration stubs use `module.registerHooks`. No separate TypeScript build or test runner is required. The tests use temporary fixture stores rather than live memory.
243
-
244
- Source responsibilities:
245
-
246
- | Module | Responsibility |
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.
@@ -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
+ }
@@ -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
+ }