@eleboucher/pi-memini 0.7.4 → 0.7.5

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.
Files changed (3) hide show
  1. package/README.md +206 -103
  2. package/dist/index.js +1117 -235
  3. package/package.json +22 -8
package/README.md CHANGED
@@ -1,36 +1,24 @@
1
1
  # memini + Pi
2
2
 
3
3
  [Pi](https://pi.dev) is an open-source coding agent with a first-class
4
- [extension API](https://pi.dev/docs/latest/extensions). Pi has **no built-in
5
- MCP** capabilities are added through extensions so memini ships a native
6
- extension in [`plugin/`](plugin/) that makes memory both automatic and
7
- tool-callable, with no MCP layer required.
4
+ [extension API](https://pi.dev/docs/latest/extensions). Pi has no built-in MCP
5
+ client, so memini ships a native Pi package in [`plugin/`](plugin/). It adds
6
+ automatic session memory, native `memory_*` tools, lifecycle persistence, and
7
+ compact transcript rendering without an MCP adapter.
8
8
 
9
- ## Recommended: the memory extension
9
+ ## Install
10
10
 
11
- What it wires:
11
+ The package is published as
12
+ [`@eleboucher/pi-memini`](https://www.npmjs.com/package/@eleboucher/pi-memini)
13
+ and requires Pi 0.80.6 or newer (Node.js 22.19 or newer):
12
14
 
13
- - **`before_agent_start`** — searches memini for the user's prompt and injects
14
- the matches as a persistent context message before the agent runs. It excludes
15
- this session's own captured turns (already in live context), so they aren't
16
- echoed back a turn behind; past sessions still recall.
17
- - **`agent_end`** — once the agent finishes a prompt, stores the completed
18
- user/assistant turn back into memini (episodic, tagged `pi`, with the session
19
- id) so it can be recalled later.
20
- - **Explicit tools** — modeled on the tool set Claude Code gets from memini's
21
- MCP server, registered natively via `pi.registerTool`: `memory_recall`, `memory_list`,
22
- `memory_remember`, `memory_forget`. The model can call them on demand even
23
- though the automatic loop already runs.
24
-
25
- ### Install
26
-
27
- The extension is published to npm as
28
- [`@eleboucher/pi-memini`](https://www.npmjs.com/package/@eleboucher/pi-memini).
29
- Pi has no `init`/scaffold command — extensions are just discovered from known
30
- locations or declared in config. Pick one:
15
+ ```sh
16
+ pi install npm:@eleboucher/pi-memini
17
+ ```
31
18
 
32
- **Project / global settings** add the package to `settings.json`
33
- (`.pi/settings.json` for one project, or `~/.pi/agent/settings.json` globally):
19
+ Use `-l` for a project-local install. Pi records global packages in
20
+ `~/.pi/agent/settings.json` and project packages in `.pi/settings.json`.
21
+ Equivalent manual configuration is:
34
22
 
35
23
  ```json
36
24
  {
@@ -38,98 +26,213 @@ locations or declared in config. Pick one:
38
26
  }
39
27
  ```
40
28
 
41
- **Discovery folder** Pi auto-discovers and hot-reloads extensions in
42
- `~/.pi/agent/extensions/` (global) or `.pi/extensions/` (project-local). Drop
43
- the built `dist/index.js` (or the `src/index.ts` source) there.
29
+ Project-local packages load only after the project is trusted. Run `pi config`
30
+ to enable or disable installed resources.
44
31
 
45
- **Quick test** point Pi at a local checkout for one run:
32
+ For development, load the built extension for one run:
46
33
 
47
34
  ```sh
35
+ npm --prefix integrations/pi/plugin run build
48
36
  pi -e ./integrations/pi/plugin/dist/index.js
49
37
  ```
50
38
 
51
- ### Configure
52
-
53
- All config is via environment variables in the shell that launches Pi (secrets
54
- stay out of any file):
55
-
56
- | Env var | Default | Purpose |
57
- | -------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------- |
58
- | `MEMINI_BASE_URL` | `http://localhost:8080` | memini REST base URL |
59
- | `MEMINI_NAMESPACE` | unset (server handshake decides) | machine-local namespace override; the offline escape hatch when the server is unreachable |
60
- | `MEMINI_HOME` | unset | caller's personal namespace, sent as `X-Memini-Home`; unset = no home leg |
61
- | `MEMINI_RECALL` | on | `0`/`false` disables recall-before-turn |
62
- | `MEMINI_CAPTURE` | on | `0`/`false` disables capture-after-turn |
63
- | `MEMINI_RECALL_LIMIT` | `3` | max memories injected per turn |
64
- | `MEMINI_INJECT_RECALL_MAX_TOK` | `0` | hard ceiling on recall-block tokens (`0` = unbounded); the tail is dropped with a footer |
65
- | `MEMINI_INJECT_RECALL_MIN_SCORE` | `0` | fused-score floor (>=) sent as `min_score` to `/v1/search` |
66
- | `MEMINI_INJECT_LABELS` | — | comma-separated bullet labels: `tier`, `confidence`, `age` |
67
- | `MEMINI_TIMEOUT_MS` | `30000` | per-request timeout |
68
- | `MEMINI_FALLBACK` | on | `0`/`false` surfaces errors instead of degrading silently |
69
- | `MEMINI_API_KEY` | — | bearer token, if memini needs auth (sent as `Authorization: Bearer …`) |
70
- | `MEMINI_REQUIRE_HTTPS` | — | `1` refuses to send the token over plaintext HTTP |
71
-
72
- The namespace itself is resolved by the memini **server**, not this extension:
73
- at the first turn the extension performs the config handshake
74
- (`POST /v1/handshake`), sending the project's facts (git remote, toplevel,
75
- cwd basename) and using whatever the server resolves a pin recorded for this
76
- project, `MEMINI_NAMESPACE` if exported, or derivation from the facts (repo
77
- name, then toplevel basename, then cwd basename). The result is memoized in
78
- memory for ten minutes. When the server is unreachable, the extension degrades
79
- to the same chain locally: `MEMINI_NAMESPACE`, else git/cwd derivation — which
80
- is why the env var is best thought of as the offline escape hatch, not the
81
- primary lever.
82
-
83
- ### Commands
84
-
85
- | Command | What it does |
86
- | ------------------ | ------------------------------------------------------------------------------- |
87
- | `memini:status` | Effective settings, the resolved namespace **and where it came from**, warnings |
88
- | `memini:namespace` | Show, set, or clear the server-side namespace pin for this project |
89
-
90
- `memini:status` exists because a list of values is not enough to debug a namespace
91
- problem. It shows provenance (`<- env` vs `<- server` vs `(default)`), so a
92
- `MEMINI_NAMESPACE` exported once from a shell profile — which pins _every_ repo on
93
- the machine to one namespace — shows up as a warning rather than as a mystery.
94
- Secrets are redacted.
95
-
96
- ### The namespace pin
39
+ Pi also auto-discovers source extensions placed in
40
+ `~/.pi/agent/extensions/` or `.pi/extensions/`; those locations can be
41
+ hot-reloaded with `/reload`.
42
+
43
+ ## What the extension does
44
+
45
+ ### Automatic memory and lifecycle
46
+
47
+ - **`session_start` / `session_tree`** reconstruct branch-local suppression and
48
+ capture state, then inject one bounded layered briefing when the active model
49
+ context does not already contain it. Startup, resume, fork, and reload do not
50
+ duplicate an intact briefing.
51
+ - **`before_agent_start`** searches for the submitted prompt and injects useful
52
+ matches as untrusted, read-only context. Blank text, command-shaped prompts,
53
+ and steering text shorter than 12 characters are skipped; search queries are
54
+ capped at 2,000 characters.
55
+ - **`agent_settled`** captures the final successful user/assistant turn only
56
+ after retries, overflow compaction, and queued continuations have settled.
57
+ Repeated settled events for the same assistant entry are idempotent.
58
+ - **`session_before_compact` / `session_compact`** optionally checkpoint bounded
59
+ state-changing activity, clear context-coupled suppression, and queue a fresh
60
+ briefing without starting an extra turn.
61
+ - **`session_shutdown`** optionally records a bounded activity digest for real
62
+ shutdown/switch/fork events. Reload is skipped because the session continues.
63
+ - **Branch-aware dedupe** persists in Pi custom entries only after the matching
64
+ recall/tool-result message is finalized. Automatic briefing and prompt recall,
65
+ plus explicit recall/briefing/list/get/history/answer results, share one cooldown
66
+ state; successful updates, deletes, and ID upserts make corrected content eligible immediately,
67
+ even when sibling tools complete concurrently.
68
+ Set `MEMINI_INJECT_DEDUPE=0` to disable exclusions, filtering, and recording
69
+ together.
70
+
71
+ The server-authoritative namespace from `POST /v1/handshake` scopes automatic
72
+ and explicit requests through `X-Memini-Namespace`. Automatic lifecycle work
73
+ no-ops while authority is unavailable; explicit tools retry the handshake once
74
+ and then return an actionable error rather than request a locally guessed partition.
75
+ A compatibility retry drops `exclude_ids` only after a server explicitly rejects
76
+ that field with HTTP 400; timeouts, throttling, and unrelated failures do not disable it.
77
+
78
+ ### Native tools
79
+
80
+ Pi registers these tools directly with `pi.registerTool`:
81
+
82
+ | Tool | Purpose |
83
+ | ----------------- | -------------------------------------------------------------------------------------------- |
84
+ | `memory_briefing` | Read pinned context, durable facts, procedures, recent work, and nested-project rollups. |
85
+ | `memory_recall` | Hybrid semantic/keyword recall with filters, time travel, scope, exclusions, and provenance. |
86
+ | `memory_list` | Browse and page newest-first memories without a search query. |
87
+ | `memory_remember` | Store or upsert an atomic memory, including validity, confidence, metadata, and visibility. |
88
+ | `memory_get` | Fetch one complete memory DTO. |
89
+ | `memory_history` | Read a memory's supersession history oldest-first. |
90
+ | `memory_update` | Partially correct content, summary, tier, level, tags, metadata, importance, or confidence. |
91
+ | `memory_forget` | Permanently delete a memory. |
92
+
93
+ `memory_answer` is added dynamically only when authenticated
94
+ `GET /healthz?verbose=1` returns the literal boolean
95
+ `deps.llm.configured: true`. Missing, malformed, false, unreachable, or
96
+ unrouted capability evidence leaves the tool unadvertised.
97
+
98
+ The REST-backed answer tool intentionally omits MCP's `reasoning_level` because
99
+ the current `/v1/answer` REST request does not accept it. REST briefing also
100
+ does not expose the service's truncated-child count, so Pi preserves every
101
+ returned child rollup but does not fabricate `children_note`.
102
+
103
+ Read results include namespace/provenance evidence. For inherited or personal
104
+ memories, copy the returned `namespace` verbatim into get/history/update/forget;
105
+ do not invent namespace paths. Writes choose semantic `visibility` instead.
106
+
107
+ ### Compact rendering
108
+
109
+ All explicit tools keep their complete structured JSON in the tool result sent
110
+ to the model and stored in the session. Their TUI renderer shows a concise
111
+ single line when collapsed and, with Pi's tool-expansion key (`Ctrl+O` by
112
+ default), a bounded human-readable view with at most eight memory/source items
113
+ plus kind-specific answer, acknowledgement, merge, and child-rollup details.
114
+
115
+ Automatic briefing and recall messages likewise remain persistent model
116
+ context while registered message renderers keep the normal transcript to one
117
+ line. Rendering never truncates or rewrites the model-facing payload.
118
+
119
+ ## Configure
120
+
121
+ The extension has two configuration layers:
122
+
123
+ 1. **Transport and identity** come from the environment of the Pi process.
124
+ 2. **Behavior settings** resolve as **environment override → handshake server
125
+ setting → built-in default**. This lets one server set team defaults while a
126
+ local environment can override a specific knob.
127
+
128
+ ### Transport and identity environment
129
+
130
+ | Environment variable | Default | Effect |
131
+ | ------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------- |
132
+ | `MEMINI_BASE_URL` | `http://localhost:8080` | memini REST base URL. |
133
+ | `MEMINI_API_KEY` | unset | Bearer token. |
134
+ | `MEMINI_REQUIRE_HTTPS` | off | Set to `1` to refuse a bearer token over non-loopback plaintext HTTP. |
135
+ | `MEMINI_HOME` | unset | Personal namespace sent as `X-Memini-Home`; unset means no personal read leg. |
136
+ | `MEMINI_NAMESPACE` | unset | Declared namespace fact and offline machine-local fallback; a server pin still wins. |
137
+ | `MEMINI_NAMESPACE_PREFIX` | unset | Prefix applied only to derived namespaces, online and in degraded fallback. |
138
+ | `MEMINI_AGENT` | unset | Optional per-agent namespace suffix fact. |
139
+ | `MEMINI_TIMEOUT_MS` | `30000` | Per-request timeout in milliseconds. |
140
+ | `MEMINI_FALLBACK` | on | `0`/`false` makes automatic lifecycle transport failures surface instead of degrading to warnings/no-op. |
141
+
142
+ The initial handshake sends git remote, repository root, cwd basename, and the
143
+ identity facts above. The server resolves pins and other server-side policy and
144
+ returns the authoritative namespace. The result is memoized for ten minutes;
145
+ setting or clearing a pin invalidates it immediately. If the handshake is
146
+ unreachable, Pi derives a namespace only for diagnostics and marks it degraded;
147
+ automatic memory traffic pauses and explicit tools refuse to route until server
148
+ authority is restored.
149
+
150
+ ### Behavior settings used by Pi
151
+
152
+ | Environment override | Built-in default | Effect |
153
+ | ------------------------------------ | ---------------- | --------------------------------------------------------------------------------------------- |
154
+ | `MEMINI_RECALL` | on | Automatic prompt recall. |
155
+ | `MEMINI_CAPTURE` | on | Settled-turn capture. |
156
+ | `MEMINI_RECALL_LIMIT` | `3` | Maximum automatic recall hits. |
157
+ | `MEMINI_INJECT_RECALL_MAX_TOK` | `0` | Recall injection token ceiling; `0` is unbounded. |
158
+ | `MEMINI_INJECT_RECALL_MIN_SCORE` | `0` | Minimum automatic recall score. |
159
+ | `MEMINI_INJECT_DEDUPE` | on | Shared cross-surface suppression state. |
160
+ | `MEMINI_INJECT_COOLDOWN_MS` | `1800000` | Time cooldown for repeated injection; `0` disables this dimension. |
161
+ | `MEMINI_INJECT_COOLDOWN_PROMPTS` | `3` | Prompt cooldown; `0` disables this dimension. Both cooldowns at `0` suppress for the session. |
162
+ | `MEMINI_INJECT_LABELS` | empty | Comma/pipe-separated automatic bullet labels: `tier`, `confidence`, `age`. |
163
+ | `MEMINI_INJECT_BRIEFING_PINNED` | `5` | Startup pinned-memory cap. |
164
+ | `MEMINI_INJECT_BRIEFING_FACTS` | `5` | Startup durable-fact cap. |
165
+ | `MEMINI_INJECT_BRIEFING_PROCEDURES` | `5` | Startup procedure cap. |
166
+ | `MEMINI_INJECT_BRIEFING_RECENT` | `3` | Startup recent-memory cap. |
167
+ | `MEMINI_INJECT_BRIEFING_MAX_TOK` | `0` | Whole briefing token ceiling; `0` is unbounded. |
168
+ | `MEMINI_SESSION_DIGEST` | on | Pre-compaction and shutdown activity checkpoints. |
169
+ | `MEMINI_MIN_CAPTURE_CHARS` | `0` | Minimum settled user-text length required for capture. |
170
+ | `MEMINI_CAPTURE_USER_MAX_CHARS` | `1000` | Per-turn captured user-text cap; `0` is unbounded. |
171
+ | `MEMINI_CAPTURE_ASSISTANT_MAX_CHARS` | `3000` | Per-turn captured assistant-text cap; `0` is unbounded. |
172
+
173
+ An already injected memory stays suppressed while **either** configured
174
+ cooldown still holds and may return only after both lapse. Explicit read tools
175
+ use conservative suppression until a correction evicts the stale entry.
176
+
177
+ ## Slash commands
178
+
179
+ Type commands with the leading `/` in Pi's editor:
180
+
181
+ | Command | Effect |
182
+ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
183
+ | `/memini:status` | Show effective settings and provenance, resolved namespace, connection status, read set, redacted auth, and warnings. |
184
+ | `/memini:namespace` | Show the current server-resolved namespace and pin provenance. |
185
+ | `/memini:namespace <namespace>` | Set this project's server-side namespace pin. |
186
+ | `/memini:namespace --clear` | Clear the server-side pin and return to automatic resolution. |
187
+
188
+ Command diagnostics are persisted as TUI-only custom entries and never enter
189
+ the model context, including server-authored pin notes and read-set labels.
190
+
191
+ Pins are stored by the memini server (`PUT`/`DELETE /v1/pins`) and keyed by the
192
+ project's git remote and/or repository root. They follow the project across
193
+ machines and beat `MEMINI_NAMESPACE` deliberately. Pin writes require a
194
+ reachable server; `MEMINI_NAMESPACE` remains the offline local override.
195
+
196
+ ## Build and verify
197
+
198
+ From the package directory:
97
199
 
98
- ```
99
- memini:namespace # show the namespace and where it came from
100
- memini:namespace acme/api # pin this project to acme/api
101
- memini:namespace --clear # back to automatic resolution
200
+ ```sh
201
+ cd integrations/pi/plugin
202
+ npm ci
203
+ npm test
102
204
  ```
103
205
 
104
- The pin lives on the **memini server** (`PUT`/`DELETE /v1/pins`), keyed by the
105
- project's git remote and/or toplevel path so it follows you across machines,
106
- and every client that handshakes for this project (Claude Code, this extension,
107
- `memini doctor`) resolves the same value.
206
+ `npm test` is the standard verification path. It type-checks source and tests
207
+ against Pi 0.80.6, builds `dist/index.js`, runs bundle and helper/lifecycle/tool
208
+ contract tests, packs the publication artifact, installs it into a clean
209
+ consumer, and imports the installed ESM with only Pi-provided peer modules.
108
210
 
109
- A pin beats `MEMINI_NAMESPACE` at handshake time, deliberately: a globally
110
- exported `MEMINI_NAMESPACE` is exactly the problem a pin exists to solve, so if
111
- the environment won, the command would silently do nothing on the machines that
112
- need it. Setting or clearing a pin takes effect on the next turn — the write
113
- drops the extension's in-memory handshake memo, so there is no restart or
114
- ten-minute wait.
211
+ Useful focused commands:
115
212
 
116
- Because pins are server-side, setting one needs the server reachable. For an
117
- offline, machine-local override, export `MEMINI_NAMESPACE` instead.
213
+ ```sh
214
+ npm run typecheck
215
+ npm run build
216
+ npm run test:unit
217
+ npm run test:package
218
+ npm pack --dry-run
219
+ ```
118
220
 
119
- ### Build & test
221
+ The package's `prepack` hook repeats typecheck and build so a direct `npm pack`
222
+ or publish cannot ship stale source. `@memini/client` is bundled into the
223
+ single extension file; `typebox`, `@earendil-works/pi-coding-agent`, and
224
+ `@earendil-works/pi-tui` remain host-provided peers as required for Pi packages.
225
+
226
+ Relevant cross-integration parity checks from the repository root are:
120
227
 
121
228
  ```sh
122
- cd integrations/pi/plugin
123
- npm install
124
- npm run build # esbuild bundle -> dist/index.js
125
- npm test # bundle test (node --test) + pure-helper unit tests (tsx --test)
229
+ pnpm --filter @memini/client test
230
+ node --test plugin/scripts/_test.mjs
126
231
  ```
127
232
 
128
233
  ## Alternative: MCP wire
129
234
 
130
- Pi can also reach memini's `memory_*` tools over MCP, but unlike Claude Code
131
- or Codex Pi has no native MCP client, so you first need an MCP extension for
132
- Pi (e.g. the one prewired in the [`my-pi`](https://github.com/spences10/my-pi)
133
- distribution), then point it at memini's server: `http://<host>:8080/mcp`
134
- (remote) or `memini mcp` (stdio). The native extension above is simpler and adds
135
- the automatic recall/capture loop on top of the tools, so prefer it.
235
+ Pi can reach memini's `memory_*` tools through a third-party MCP extension, then
236
+ connect to `http://<host>:8080/mcp` or `memini mcp` (stdio). The native package
237
+ above is preferred because it also provides automatic recall/capture, lifecycle
238
+ state, capability gating, and compact rendering.