pi-automem-bridge 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  <div align="center">
2
2
 
3
- ![Pi Automem Bridge](assets/banner.png)
3
+ ![pi-automem-bridge](assets/banner.png)
4
4
 
5
5
  # pi-automem-bridge
6
6
 
7
- The missing link between [pi](https://github.com/earendil-works/pi) and [AutoMem](https://github.com/verygoodplugins/automem). If you already have both set up, this package connects them — giving pi automatic long-term memory: startup recall, turn-level recall, policy-gated writes, and relationship tools.
7
+ > **AutoMem is the memory. This bridge ensures pi actually uses it.**
8
8
 
9
9
  ```bash
10
10
  pi install npm:pi-automem-bridge
11
11
  ```
12
12
 
13
- [![npm version](https://badge.fury.io/js/pi-automem-bridge.svg)](https://badge.fury.io/js/pi-automem-bridge)
13
+ [![npm version](https://img.shields.io/npm/v/pi-automem-bridge)](https://www.npmjs.com/package/pi-automem-bridge)
14
14
  [![npm downloads](https://img.shields.io/npm/dw/pi-automem-bridge)](https://www.npmjs.com/package/pi-automem-bridge)
15
15
 
16
16
  [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/L2J320X82M)
@@ -19,6 +19,30 @@ pi install npm:pi-automem-bridge
19
19
 
20
20
  ---
21
21
 
22
+ ## Why pi-automem-bridge
23
+
24
+ Plenty of agents can store a memory. Far fewer reach for it when it counts — or check what they're scribbling down. pi-automem-bridge makes pi do both, automatically: startup + per-turn recall injected straight into the prompt, and a secret-scanning, policy-gated write pipeline guarding the door to AutoMem.
25
+
26
+ - **Per-project scoping** — recall limits and filters tuned to the repo or folder you're working in.
27
+ - **Bring your own AutoMem** — talks to your existing instance over MCP. No duplicate credentials or storage to manage.
28
+
29
+ > The storage and the recall/similarity intelligence are [AutoMem](https://github.com/verygoodplugins/automem)'s. This package is the guardrail-and-automation layer that makes them automatic inside pi.
30
+
31
+ ---
32
+
33
+ ## How it works
34
+
35
+ Once installed, the bridge hooks into pi's session lifecycle:
36
+
37
+ - **At session start** it runs your startup recall queries against AutoMem and injects the results — your preferences, working style, and environment — into the system prompt.
38
+ - **Before each turn** it recalls memories relevant to the current task and the detected project, again injected silently.
39
+ - **When the agent writes a memory** the candidate passes through the write pipeline — normalize → secret-scan → policy check → dedupe → confirm or auto-store — so nothing unvetted reaches AutoMem.
40
+ - **Relationship tools** let the agent link memories or record corrections with provenance, building a connected graph over time.
41
+
42
+ Recall display, write policy, and per-project scoping are all configurable — see the [Configuration reference](#configuration-reference).
43
+
44
+ ---
45
+
22
46
  ## Before you begin
23
47
 
24
48
  This package does not include pi or AutoMem — it connects them. You need both running independently first:
@@ -27,24 +51,25 @@ This package does not include pi or AutoMem — it connects them. You need both
27
51
  2. **[AutoMem](https://github.com/verygoodplugins/automem)** — the graph-vector memory service (self-hosted or Railway)
28
52
  3. **[mcp-automem](https://github.com/verygoodplugins/mcp-automem)** — the MCP bridge that exposes AutoMem's tools over the MCP protocol
29
53
 
30
- Once those three are in place, add `mcp-automem` to pi's MCP config and install this package. That's all the wiring this package needs.
54
+ Once those three are in place, follow the setup below.
31
55
 
32
56
  ---
33
57
 
34
- ## What it does
58
+ ## Setup
35
59
 
36
- - **Startup recall**at session start, queries AutoMem for your preferences, working style, and environment
37
- - **Turn-level recall** — before each agent prompt, retrieves memories relevant to the current task and detected project
38
- - **Silent injection** — memory context is injected into the system prompt, not the chat window
39
- - **Policy-gated writes** — every memory write is validated, secret-scanned, deduplicated, and confirmed before storage
40
- - **Relationship tools** — link memories to each other or record corrections with provenance history
41
- - **Per-project tuning** — configure different recall limits and filters per detected project
60
+ Three steps to a working install, then optional tuning. The package install is automatic the **only** thing you must configure by hand is the connection to your own AutoMem server, because that carries your private server URL and token, and no package can (or should) write those for you.
42
61
 
43
- ---
62
+ ### 1. Install the package
63
+
64
+ ```bash
65
+ pi install npm:pi-automem-bridge
66
+ ```
44
67
 
45
- ## Getting started
68
+ This registers the extension's tools, commands, and recall hooks with pi automatically. Nothing runs yet — it has no server to talk to.
46
69
 
47
- **1. Add your AutoMem server to `~/.pi/agent/mcp.json`:**
70
+ ### 2. Connect it to your AutoMem server *required*
71
+
72
+ Add an MCP server entry named `automem` to `~/.pi/agent/mcp.json`, pointing at the AutoMem instance from [Before you begin](#before-you-begin):
48
73
 
49
74
  ```json
50
75
  {
@@ -59,9 +84,17 @@ Once those three are in place, add `mcp-automem` to pi's MCP config and install
59
84
  }
60
85
  ```
61
86
 
62
- Use `${ENV_VAR}` interpolation for secrets. Never hardcode credentials.
87
+ This is the one step that can't be automated: the package has no way to know your server's address or auth token, and writing credentials on your behalf would be unsafe. Use `${ENV_VAR}` interpolation for the token — never hardcode secrets. The entry must be named `automem` (the name the extension looks for by default), or set a different name via `mcpServerName` in step 4.
88
+
89
+ **Don't want to hand-edit JSON?** pi is a coding agent — tell it to do it: *"add an `automem` MCP server to my `mcp.json` at `https://my-server.example.com/mcp`, using `${AUTOMEM_TOKEN}` for auth."* Keep the real token in your environment so it never touches the file or the chat.
90
+
91
+ ### 3. Reload pi
92
+
93
+ Start a new session or run `/reload`. **That's it — recall is now automatic and the bridge runs on sensible defaults** (`safe-auto` writes, `summary` recall display). From here, just work: pi recalls on its own and saves routine decisions automatically — tell it *"remember this"* anytime you want something kept. Confirm everything's live with `/automem-status`.
94
+
95
+ ### 4. Tune behavior — *optional*
63
96
 
64
- **2. Create `~/.pi/agent/automem.json`:**
97
+ The bridge works fully without this file. To customize recall queries, write policy, per-project scoping, or display mode, create `~/.pi/agent/automem.json` — any value you leave out falls back to its default. (Or just tell pi what you want — *"only auto-save bug fixes and technical decisions, and hide the recall block"* — and have it write the file for you.)
65
98
 
66
99
  ```json
67
100
  {
@@ -79,7 +112,7 @@ Use `${ENV_VAR}` interpolation for secrets. Never hardcode credentials.
79
112
  }
80
113
  ```
81
114
 
82
- **3. Start or reload pi.** Recall is now automatic.
115
+ Every option with real values you can copy — is in the [Configuration reference](#configuration-reference) below.
83
116
 
84
117
  ---
85
118
 
@@ -92,6 +125,8 @@ Use `${ENV_VAR}` interpolation for secrets. Never hardcode credentials.
92
125
 
93
126
  ## Tools
94
127
 
128
+ You don't type these — pi does, in plain conversation. Tell it *"remember that I prefer Vitest over Jest"* and it runs the thought through the write pipeline before storing; say *"actually, we moved off Railway"* and it records a correction with provenance. In `safe-auto` mode it also captures routine decisions on its own, no prompting needed.
129
+
95
130
  | Tool | What it does |
96
131
  |---|---|
97
132
  | `automem_propose_memory` | Preview a memory candidate — validates, scans for secrets, checks for duplicates. Does not write. |
@@ -100,6 +135,8 @@ Use `${ENV_VAR}` interpolation for secrets. Never hardcode credentials.
100
135
  | `automem_link_memories` | Create a typed relationship between two existing memories. |
101
136
  | `automem_correct_memory` | Store a correction and link old → new with a provenance relationship (EVOLVED_INTO or CONTRADICTS). |
102
137
 
138
+ Policy blocks, missing approval in non-interactive contexts, and invalid update requests surface as pi tool errors. User-cancelled confirmations and duplicate detection are normal control-flow results, so the agent can stop or choose the next write path deliberately.
139
+
103
140
  ---
104
141
 
105
142
  ## Write policy
@@ -135,16 +172,6 @@ When a commit finds a close match, `automem_commit_memory` returns `DUPLICATE_DE
135
172
 
136
173
  ---
137
174
 
138
- ## Recall display modes
139
-
140
- | Mode | Behavior |
141
- |---|---|
142
- | `hidden` | Inject into system prompt only. Nothing shown in chat. |
143
- | `summary` | Inject into system prompt + show a compact notification. |
144
- | `full` | Show the full recall block. Useful for debugging. |
145
-
146
- ---
147
-
148
175
  ## Configuration reference
149
176
 
150
177
  Config file: `~/.pi/agent/automem.json` (or `AUTOMEM_CONFIG_PATH`)
@@ -152,14 +179,31 @@ Config file: `~/.pi/agent/automem.json` (or `AUTOMEM_CONFIG_PATH`)
152
179
  | Section | Purpose |
153
180
  |---|---|
154
181
  | `mcpServerName` | Which server in `mcp.json` to use |
155
- | `startupRecall` | Queries, tags, limits, byte budget for session-start recall |
156
- | `turnRecall` | Per-prompt recall: limits, memory types, relation/entity expansion |
182
+ | `startupRecall` | Queries, tags, limits, byte budget, and timeout for session-start recall |
183
+ | `turnRecall` | Per-prompt recall: limits, memory types, relation/entity expansion, and timeout |
157
184
  | `projectDetection` | Map git repos and folder names to project tags for scoped recall |
158
185
  | `projectOverrides` | Per-project overrides for turn recall limits and filters |
159
186
  | `writePolicy` | Write mode, categories, importance threshold, dedupe settings |
160
187
  | `behavior` | Display mode and content-length preferences |
161
188
 
162
- See [`examples/config.minimal.json`](examples/config.minimal.json) and [`examples/config.advanced.json`](examples/config.advanced.json).
189
+ Set only the keys you want to change — everything else uses its default. Two ready-to-edit starting points ship with the package:
190
+
191
+ - **[`config.minimal.json`](https://github.com/vaniteav/pi-automem-bridge/blob/main/examples/config.minimal.json)** — the smallest useful config.
192
+ - **[`config.advanced.json`](https://github.com/vaniteav/pi-automem-bridge/blob/main/examples/config.advanced.json)** — every option above, filled in with real values and sensible defaults. Copy it, trim what you don't need.
193
+
194
+ ### Recall display (`behavior.displayRecall`)
195
+
196
+ Controls how much of the recalled context shows in chat. Injection into the system prompt happens regardless.
197
+
198
+ | Mode | Behavior |
199
+ |---|---|
200
+ | `hidden` | Inject into system prompt only. Nothing shown in chat. |
201
+ | `summary` | Inject into system prompt + show a compact notification. |
202
+ | `full` | Show the full recall block. Useful for debugging. |
203
+
204
+ ### Recall timeouts
205
+
206
+ Recall is best-effort context enrichment, so it runs on a short, bounded timeout instead of the full MCP request timeout — a slow or unreachable AutoMem server degrades gracefully to no injection rather than blocking your prompt. Tune with `turnRecall.timeoutMs` (default `8000`) and `startupRecall.timeoutMs` (default `15000`).
163
207
 
164
208
  ---
165
209
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-automem-bridge",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Automatic long-term memory recall and policy-gated writes for pi agents via AutoMem MCP",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -27,7 +27,9 @@
27
27
  "scripts": {
28
28
  "test": "tsx tests/unit.ts && tsx tests/phase2-policy.ts && tsx tests/review-fixes.ts",
29
29
  "test:smoke": "tsx tests/phase1-smoke.ts",
30
- "test:live": "tsx tests/phase2-live-write.ts"
30
+ "test:live": "tsx tests/phase2-live-write.ts",
31
+ "preflight": "node scripts/preflight.mjs",
32
+ "prepublishOnly": "node scripts/preflight.mjs"
31
33
  },
32
34
  "devDependencies": {
33
35
  "tsx": "^4.20.6"
@@ -68,11 +68,10 @@ export function registerMemoryTools(pi: ExtensionAPI) {
68
68
  config,
69
69
  ).catch(() => ({ text: "Similar recall failed.", matches: [] }));
70
70
 
71
- return {
72
- content: [{ type: "text" as const, text: formatProposal(decision.action, decision.reasons, decision.normalized, similarText, similarMatches) }],
73
- details: { action: decision.action, reasons: decision.reasons, findings: decision.findings, candidate: decision.normalized, similarMatches },
74
- isError: decision.action === "block",
75
- };
71
+ return {
72
+ content: [{ type: "text" as const, text: formatProposal(decision.action, decision.reasons, decision.normalized, similarText, similarMatches) }],
73
+ details: { action: decision.action, reasons: decision.reasons, findings: decision.findings, candidate: decision.normalized, similarMatches },
74
+ };
76
75
  },
77
76
  });
78
77
 
@@ -87,15 +86,11 @@ export function registerMemoryTools(pi: ExtensionAPI) {
87
86
  const config = loadConfig();
88
87
  setAutoMemMcpServerName(config.mcpServerName);
89
88
  const candidate = toCandidate(params);
90
- const decision = evaluateWritePolicy(candidate, config);
91
-
92
- if (decision.action === "block") {
93
- return {
94
- content: [{ type: "text" as const, text: "Blocked by AutoMem write policy.\n" + decision.reasons.map((r: string) => "- " + r).join("\n") }],
95
- details: { action: decision.action, reasons: decision.reasons, findings: decision.findings },
96
- isError: true,
97
- };
98
- }
89
+ const decision = evaluateWritePolicy(candidate, config);
90
+
91
+ if (decision.action === "block") {
92
+ throw new Error("Blocked by AutoMem write policy.\n" + decision.reasons.map((r: string) => "- " + r).join("\n"));
93
+ }
99
94
 
100
95
  const needsConfirmation = decision.action !== "auto";
101
96
  if (needsConfirmation && !params.approvedByUser) {
@@ -104,14 +99,10 @@ export function registerMemoryTools(pi: ExtensionAPI) {
104
99
  if (!ok) {
105
100
  return { content: [{ type: "text" as const, text: "AutoMem memory write cancelled." }], details: { cancelled: true } };
106
101
  }
107
- } else {
108
- return {
109
- content: [{ type: "text" as const, text: "Confirmation required before storing this memory. Re-run with approvedByUser=true only after explicit user approval." }],
110
- details: { action: decision.action, reasons: decision.reasons, candidate: decision.normalized },
111
- isError: true,
112
- };
113
- }
114
- }
102
+ } else {
103
+ throw new Error("Confirmation required before storing this memory. Re-run with approvedByUser=true only after explicit user approval.");
104
+ }
105
+ }
115
106
 
116
107
  // ── UPDATE path ──────────────────────────────────────────────────────
117
108
  if (params.updateMemoryId) {
@@ -158,11 +149,10 @@ export function registerMemoryTools(pi: ExtensionAPI) {
158
149
  " 2. Store anyway (new memory): call automem_commit_memory with dedupeQuery=\"\" to skip dedupe",
159
150
  " 3. Cancel: do nothing if this is not worth storing separately",
160
151
  ].join("\n"),
161
- }],
162
- details: { duplicateDetected: true, existingMemoryId: top.id, existingContent: top.content, candidate: decision.normalized, allSimilar: similarMatches },
163
- isError: false,
164
- };
165
- }
152
+ }],
153
+ details: { duplicateDetected: true, existingMemoryId: top.id, existingContent: top.content, candidate: decision.normalized, allSimilar: similarMatches },
154
+ };
155
+ }
166
156
 
167
157
  // ── STORE path ───────────────────────────────────────────────────────
168
158
  const result = await automemStore(
@@ -193,12 +183,9 @@ export function registerMemoryTools(pi: ExtensionAPI) {
193
183
  promptSnippet: "Use after automem_commit_memory returns DUPLICATE_DETECTED, or when correcting a known memory. Requires the existing memory ID.",
194
184
  parameters: UpdateParams,
195
185
  async execute(_toolCallId, params, _signal, _onUpdate, ctx: any) {
196
- if (!params.memoryId) {
197
- return {
198
- content: [{ type: "text" as const, text: "memoryId is required for automem_update_memory." }],
199
- isError: true,
200
- };
201
- }
186
+ if (!params.memoryId) {
187
+ throw new Error("memoryId is required for automem_update_memory.");
188
+ }
202
189
 
203
190
  if (!params.approvedByUser) {
204
191
  if (ctx && ctx.ui && typeof ctx.ui.confirm === "function") {
@@ -212,13 +199,10 @@ export function registerMemoryTools(pi: ExtensionAPI) {
212
199
  if (!ok) {
213
200
  return { content: [{ type: "text" as const, text: "AutoMem memory update cancelled." }], details: { cancelled: true } };
214
201
  }
215
- } else {
216
- return {
217
- content: [{ type: "text" as const, text: "Confirmation required before updating this memory. Re-run with approvedByUser=true only after explicit user approval." }],
218
- isError: true,
219
- };
220
- }
221
- }
202
+ } else {
203
+ throw new Error("Confirmation required before updating this memory. Re-run with approvedByUser=true only after explicit user approval.");
204
+ }
205
+ }
222
206
 
223
207
  const result = await automemUpdate(params.memoryId, {
224
208
  content: params.content,
@@ -31,14 +31,11 @@ export function registerRelationshipTools(pi: ExtensionAPI) {
31
31
  parameters: LinkParams,
32
32
  async execute(_toolCallId: string, params: any) {
33
33
  const config = loadConfig();
34
- setAutoMemMcpServerName(config.mcpServerName);
35
-
36
- if (!params.approvedByUser) {
37
- return {
38
- content: [{ type: "text" as const, text: "Confirmation required before linking memories. Re-run with approvedByUser=true only after explicit user approval.\n\nWould link:\n " + params.memoryId1 + " → " + params.relationship + " → " + params.memoryId2 }],
39
- isError: true,
40
- };
41
- }
34
+ setAutoMemMcpServerName(config.mcpServerName);
35
+
36
+ if (!params.approvedByUser) {
37
+ throw new Error("Confirmation required before linking memories. Re-run with approvedByUser=true only after explicit user approval.\n\nWould link:\n " + params.memoryId1 + " -> " + params.relationship + " -> " + params.memoryId2);
38
+ }
42
39
 
43
40
  const strength = typeof params.strength === "number" ? params.strength : 0.5;
44
41
  const result = await automemAssociate(params.memoryId1, params.memoryId2, params.relationship, strength);
@@ -66,21 +63,14 @@ export function registerRelationshipTools(pi: ExtensionAPI) {
66
63
  tags: Array.isArray(params.tags) ? params.tags : [],
67
64
  importance: params.importance,
68
65
  };
69
- const decision = evaluateWritePolicy(candidate, config);
70
- if (decision.action === "block") {
71
- return {
72
- content: [{ type: "text" as const, text: "Blocked by AutoMem write policy.\n" + decision.reasons.map((r: string) => "- " + r).join("\n") }],
73
- details: { action: decision.action, reasons: decision.reasons, findings: decision.findings },
74
- isError: true,
75
- };
76
- }
77
-
78
- if (!params.approvedByUser) {
79
- return {
80
- content: [{ type: "text" as const, text: "Confirmation required before correcting memory. Re-run with approvedByUser=true only after explicit user approval.\n\nWould correct memory " + params.memoryId + " with:\n " + params.correction }],
81
- isError: true,
82
- };
83
- }
66
+ const decision = evaluateWritePolicy(candidate, config);
67
+ if (decision.action === "block") {
68
+ throw new Error("Blocked by AutoMem write policy.\n" + decision.reasons.map((r: string) => "- " + r).join("\n"));
69
+ }
70
+
71
+ if (!params.approvedByUser) {
72
+ throw new Error("Confirmation required before correcting memory. Re-run with approvedByUser=true only after explicit user approval.\n\nWould correct memory " + params.memoryId + " with:\n " + params.correction);
73
+ }
84
74
 
85
75
  const rel = params.relationship === "CONTRADICTS" ? "CONTRADICTS" : "EVOLVED_INTO";
86
76
  // Store the normalized candidate so corrections get the same alwaysTag,