myagentmemory 0.4.12 → 0.4.13

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,10 +1,29 @@
1
1
  # agent-memory
2
2
 
3
- Persistent memory for coding agents [Claude Code](https://claude.ai/code), [OpenAI Codex](https://github.com/openai/codex), Cursor, and Agent (Cursor CLI). Semantic search powered by [qmd](https://github.com/tobi/qmd).
3
+ **Persistent memory for AI coding agents.** Give [Claude Code](https://claude.ai/code), [OpenAI Codex](https://github.com/openai/codex), [Cursor](https://cursor.com), and Agent (Cursor CLI) a memory that survives across sessions — long-term facts, daily logs, topic notes, and a scratchpad checklist, stored as plain markdown and searchable with [qmd](https://github.com/tobi/qmd)-powered semantic search.
4
4
 
5
- Thanks to https://github.com/skyfallsin/pi-mem for inspiration.
5
+ [![npm version](https://img.shields.io/npm/v/myagentmemory?color=cb3837&logo=npm)](https://www.npmjs.com/package/myagentmemory)
6
+ [![npm downloads](https://img.shields.io/npm/dm/myagentmemory?color=cb3837&logo=npm)](https://www.npmjs.com/package/myagentmemory)
7
+ [![license](https://img.shields.io/npm/l/myagentmemory)](LICENSE)
8
+ [![website](https://img.shields.io/badge/website-jayzeng.github.io%2Fagentmemory-0d9b7a)](https://jayzeng.github.io/agentmemory/)
6
9
 
7
- Long-term facts, daily logs, topic/event notes, and a scratchpad checklist stored as plain markdown files. Optional qmd integration adds keyword, semantic, and hybrid search across all memory files, plus automatic selective injection of relevant past memories into every turn.
10
+ [Website and quickstart](https://jayzeng.github.io/agentmemory/) · [Install](#installation) · [CLI commands](#cli-commands) · [How it works](#how-it-works)
11
+
12
+ ## Why agent-memory?
13
+
14
+ Coding agents forget everything between sessions. `agent-memory` gives them a durable, local-first memory so they stop re-learning your stack, your preferences, and past decisions on every run.
15
+
16
+ - **Persistent project memory** — decisions, preferences, and project context carry across sessions instead of starting cold.
17
+ - **Plain Markdown, local-first** — every memory is a readable, git-friendly file on disk. No database, cloud service, or lock-in.
18
+ - **Optional semantic search** — [qmd](https://github.com/tobi/qmd) adds keyword, semantic, and hybrid search across memory files.
19
+ - **Explicit retrieval** — skills load base context at session start and search for related memories when a task needs them.
20
+ - **Shared across agents** — Claude Code, Codex, Cursor, and Agent can use the same store.
21
+
22
+ > **Naming:** `agentmemory` is the GitHub repo (and Homebrew tap), `myagentmemory` is the npm package, and `agent-memory` is the installed CLI binary. Also known as *coding agent memory* or *AI coding memory*.
23
+
24
+ ### Product boundary
25
+
26
+ AgentMemory is a local Markdown store with a CLI, optional qmd search, and agent skills. It is not a Python SDK, vector database, or knowledge graph. The Markdown files remain the source of truth.
8
27
 
9
28
  ## Installation
10
29
 
@@ -13,7 +32,7 @@ Long-term facts, daily logs, topic/event notes, and a scratchpad checklist store
13
32
  brew tap jayzeng/agentmemory https://github.com/jayzeng/agentmemory
14
33
  brew install jayzeng/agentmemory/agent-memory
15
34
 
16
- # Install the CLI globally
35
+ # Install the portable CLI globally (Node.js 20+; macOS, Linux, or Windows)
17
36
  npm install -g myagentmemory
18
37
 
19
38
  # If you hit SSL errors due to corporate MITM/inspection, try:
@@ -33,11 +52,9 @@ agent-memory install-skills
33
52
  agent-memory uninstall-skills
34
53
  ```
35
54
 
36
- ### Pi users
37
-
38
- If you're on Pi and prefer a native extension, use `pi-memory` (https://github.com/jayzeng/pi-memory) instead of installing this skill. The CLI + skill workflow here is the cross-platform alternative, and works fine on Pi without any extension.
55
+ The npm package installs a platform-neutral Node.js executable. The optional Homebrew and `build:cli` paths use a native binary built for the current platform.
39
56
 
40
- This installs:
57
+ `install-skills` writes a SKILL.md into each agent's config directory:
41
58
  - `~/.claude/skills/agent-memory/SKILL.md` — Claude Code skill
42
59
  - `~/.codex/skills/agent-memory/SKILL.md` — Codex skill
43
60
  - `~/.cursor/skills/agent-memory/SKILL.md` — Cursor skill
@@ -47,6 +64,10 @@ This installs:
47
64
  - `%USERPROFILE%\.cursor\skills\agent-memory\SKILL.md` — Cursor skill (Windows)
48
65
  - `%USERPROFILE%\.agents\skills\agent-memory\SKILL.md` — Agent CLI skill (Windows)
49
66
 
67
+ ### Pi users
68
+
69
+ If you're on Pi and prefer a native extension, use `pi-memory` (https://github.com/jayzeng/pi-memory) instead of installing this skill. The CLI + skill workflow here is the cross-platform alternative, and works fine on Pi without any extension.
70
+
50
71
  ### Optional: Enable search with qmd
51
72
 
52
73
  When qmd is installed, the collection is automatically set up via `agent-memory init`.
@@ -65,22 +86,21 @@ Without qmd, all core tools (write/read/scratchpad) work normally. Only `memory_
65
86
  ## Architecture
66
87
 
67
88
  ```
68
- ┌──────────────┐
69
- │ src/core.ts │ ← all logic (paths, truncation, scratchpad,
70
- │ │ context builder, qmd, tool functions)
71
- └──────┬───────┘
72
-
73
- ┌────┴─────────────────┐
74
- ▼ ▼
75
- ┌──────────┐ ┌──────────────┐
76
- src/ │ │ skills/
77
- cli.ts │ │ ├─ claude-code/SKILL.md
78
- │ │ ├─ codex/SKILL.md
79
- │ │ ├─ cursor/SKILL.md
80
- │ │ │ └─ agent/SKILL.md
81
- └──────────┘ └──────────────┘
82
- CLI binary instruction files
83
- `agent-memory` that invoke CLI
89
+ ┌───────────────┐
90
+ │ src/core.ts │ ← all logic: paths, truncation, scratchpad,
91
+ └───────┬───────┘ context builder, qmd, tool functions
92
+
93
+ ┌────┴─────┐
94
+ ▼ ▼
95
+ ┌─────────┐ ┌─────────────────────────┐
96
+ │ src/ │ │ skills/ │
97
+ cli.ts │ │ ├─ claude-code/SKILL.md
98
+ │ │ ├─ codex/SKILL.md
99
+ │ │ ├─ cursor/SKILL.md
100
+ │ │ └─ agent/SKILL.md
101
+ └─────────┘ └─────────────────────────┘
102
+ CLI command instruction files
103
+ `agent-memory` that invoke the CLI
84
104
  ```
85
105
 
86
106
  The memory directory defaults to `~/.agent-memory/`. Override with `AGENT_MEMORY_DIR` env var or `--dir` flag.
@@ -89,8 +109,8 @@ The memory directory defaults to `~/.agent-memory/`. Override with `AGENT_MEMORY
89
109
 
90
110
  | Command | Purpose |
91
111
  |---------|---------|
92
- | `agent-memory context [--no-search]` | Build & print context injection string to stdout |
93
- | `agent-memory write --target <long_term\|daily\|topic> --content <text> [--mode append\|overwrite] [--topic <name>] [--date YYYY-MM-DD]` | Write to memory files |
112
+ | `agent-memory context [--query <text>] [--no-search]` | Build context and optionally include qmd matches for a query |
113
+ | `agent-memory write --target <long_term\|daily\|topic> --content <text> [--mode append\|overwrite] [--source-uri <uri>] [--topic <name>] [--date YYYY-MM-DD]` | Write to memory files with optional provenance |
94
114
  | `agent-memory read --target <long_term\|scratchpad\|daily\|list\|topic\|topics> [--date YYYY-MM-DD] [--topic <name>]` | Read memory files |
95
115
  | `agent-memory scratchpad <add\|done\|undo\|clear_done\|list> [--text <text>]` | Manage checklist |
96
116
  | `agent-memory search --query <text> [--mode keyword\|semantic\|deep] [--limit N]` | Search via qmd |
@@ -115,14 +135,14 @@ If the first search doesn't find what you need, try rephrasing or switching mode
115
135
 
116
136
  ```
117
137
  ~/.agent-memory/
118
- MEMORY.md # Curated long-term memory
119
- SCRATCHPAD.md # Checklist of things to fix/remember
138
+ MEMORY.md # Curated long-term memory
139
+ SCRATCHPAD.md # Checklist of things to fix/remember
120
140
  daily/
121
- 2026-02-15.md # Daily append-only log
141
+ 2026-02-15.md # Daily append-only log
122
142
  2026-02-14.md
123
143
  ...
124
144
  topics/
125
- auth.md # Topic/event log linked back to daily entries
145
+ auth.md # Topic/event log linked back to daily entries
126
146
  ```
127
147
 
128
148
  ## Topic notes
@@ -139,25 +159,31 @@ agent-memory read --target topics
139
159
 
140
160
  ### Context injection
141
161
 
142
- Before every agent turn, the following are injected into the system prompt (in priority order):
162
+ The context builder emits the following sections in priority order. Installed skills load base context at session start; callers can optionally supply `--query` to add relevant qmd results:
143
163
 
144
164
  1. **Open scratchpad items** (up to 2K chars)
145
165
  2. **Recent topic entries** (up to 2K chars) — most recent topic notes with backlinks
146
- 3. **Today's daily log** (up to 3K chars, tail)
166
+ 3. **Today's daily log** (up to 3K chars, head + tail)
147
167
  4. **Relevant memories via qmd search** (up to 2.5K chars) — searches using the user's current prompt to surface related past context
148
168
  5. **MEMORY.md** (up to 4K chars, middle-truncated)
149
169
  6. **Yesterday's daily log** (up to 3K chars, tail — lowest priority, trimmed first)
150
170
 
151
- Total injection is capped at 16K chars. When qmd is unavailable, step 3 is skipped and the rest works as before.
171
+ Total output, including headings and truncation notices, is hard-capped at 16,000 characters. Explicitly untrusted, expired, superseded, revoked, or retired blocks are excluded; legacy secret-like values are redacted before injection. When qmd is unavailable, the relevant-memory step is skipped and the rest still works.
152
172
 
153
- For Claude Code, context is injected via the `!`agent-memory context`` syntax in the SKILL.md. For Codex, Cursor, and Agent, the agent runs `agent-memory context` at session start.
173
+ Claude Code loads base context through the skill's shell injection. Codex, Cursor, and Agent run the same base command at session start. The bundled skills use explicit search when a task relates to prior work; they make no host-level guarantee of automatic retrieval.
154
174
 
155
175
  ### Selective injection
156
176
 
157
- When qmd is available, the system automatically searches memory using the user's prompt on demand (CLI). The top 3 keyword results are injected alongside the standard context.
177
+ When qmd is available and `context --query` is supplied, the CLI sanitizes the query, limits it to 200 characters, and includes the top three keyword results with the standard context. Programmatic integrations should spawn the CLI with an argument array so query text is not evaluated by a shell.
158
178
 
159
179
  The search has a 3-second timeout and fails silently. If qmd is down or the query returns nothing, injection falls back to the standard behavior.
160
180
 
181
+ ### Provenance, temporal state, and secret screening
182
+
183
+ `write --source-uri <uri>` stores an addressable `Source:` line with the entry. Plain-Markdown compatibility is retained: complete write entries containing standalone header metadata lines such as `Trust: untrusted`, `Status: expired`, `Status: superseded`, `Status: revoked`, or `Status: retired` are kept on disk but omitted from direct, distilled, and auto-retrieved agent context. A standalone past `Valid until: YYYY-MM-DD` line is also honored. These phrases inside ordinary prose are not treated as metadata.
184
+
185
+ Writes screen a bounded set of high-confidence credential shapes and replace matching values with `[REDACTED_SECRET]` before persistence. Context rendering applies the same screening to legacy files. This is defense in depth, not a secrets vault; avoid passing real credentials in command arguments or memory content.
186
+
161
187
  ### Tags and links
162
188
 
163
189
  Use `#tags` and `[[wiki-links]]` in memory content to improve searchability:
@@ -192,6 +218,14 @@ These are content conventions, not enforced metadata. qmd's full-text indexing m
192
218
  # Unit tests (no LLM, no qmd — fast, deterministic)
193
219
  bun test test/unit.test.ts
194
220
  bun test test/cli.test.ts
221
+
222
+ # External-feedback dataset and deterministic capability probes
223
+ bun run build:eval
224
+ bun run test:eval
225
+ bun run eval:feedback
226
+
227
+ # Optional: add isolated live qmd multilingual retrieval probes
228
+ bun run eval:feedback --live-qmd
195
229
  ```
196
230
 
197
231
  ### Test levels
@@ -200,6 +234,7 @@ bun test test/cli.test.ts
200
234
  |-------|------|-------------|---------------|
201
235
  | Unit | `test/unit.test.ts` | None | Utilities, scratchpad parsing, context builder, qmd helpers, tool functions |
202
236
  | CLI | `test/cli.test.ts` | None | CLI commands, subprocess integration |
237
+ | Feedback eval | `test/eval.test.ts`, `eval/` | qmd optional | External feedback, capability gaps, multilingual retrieval, and qualitative boundaries |
203
238
 
204
239
  ## Development
205
240
 
@@ -218,7 +253,7 @@ agent-memory install-skills
218
253
 
219
254
  ```bash
220
255
  # Confirm package name is available
221
- npm view agent-memory
256
+ npm view myagentmemory
222
257
 
223
258
  # Bump version (choose patch/minor/major)
224
259
  npm version patch
@@ -227,13 +262,23 @@ npm version patch
227
262
  npm publish --access public
228
263
  ```
229
264
 
265
+ ### Repository assets (maintainers)
266
+
267
+ - **Social preview image:** `.github/assets/social-preview.png` (1280×640)
268
+ - **Release notes template:** `.github/release.yml` (used by GitHub auto-generated release notes)
269
+ - **Landing page source:** `docs/index.html` (deployed by `.github/workflows/deploy-pages.yml`)
270
+
271
+ ## Acknowledgments
272
+
273
+ Inspired by [skyfallsin/pi-mem](https://github.com/skyfallsin/pi-mem). Semantic search is powered by [qmd](https://github.com/tobi/qmd).
274
+
230
275
  ## Changelog
231
276
 
232
- ### 0.5.0
277
+ ### 0.4.12
233
278
 
234
279
  - **Removed pi extension**: Removed `index.ts` and all pi-specific code (`@mariozechner/pi-ai`, `@mariozechner/pi-coding-agent`, `@sinclair/typebox` peer dependencies).
235
280
  - **Standalone tool functions**: Extracted `memoryWrite()`, `memoryRead()`, `scratchpadAction()`, `memorySearch()` into `src/core.ts` as standalone functions usable without any framework.
236
- - **Renamed package**: `pi-memory` → `agent-memory`.
281
+ - **Renamed package**: `pi-memory` → `myagentmemory` (npm); the CLI binary is `agent-memory`.
237
282
  - **Renamed env var**: `PI_MEMORY_QMD_UPDATE` → `AGENT_MEMORY_QMD_UPDATE` (old name still works as fallback).
238
283
  - **Default memory directory**: Now always `~/.agent-memory/`.
239
284
  - **Removed pi-specific tests**: Deleted `test/e2e.ts`, `test/eval-recall.ts`, `test/unit.ts`.
package/dist/cli.js CHANGED
@@ -18,8 +18,17 @@
18
18
  * --json Machine-readable JSON output
19
19
  */
20
20
  import * as fs from "node:fs";
21
- import { _setBaseDir, buildMemoryContext, checkCollection, dailyPath, detectQmd, distilMemories, ensureDirs, ensureQmdAvailableForSync, ensureQmdAvailableForUpdate, getCollectionName, getDailyDir, getMemoryDir, getMemoryFile, getQmdEmbedMode, getQmdHealth, getQmdResultPath, getQmdResultText, getScratchpadFile, getTopicsDir, installSkills, nowTimestamp, parseScratchpad, readFileSafe, runQmdEmbedDetached, runQmdSearch, runQmdSync, runQmdUpdateNow, scheduleQmdUpdate, searchRelevantMemories, serializeScratchpad, setupQmdCollection, slugifyTopic, todayStr, topicPath, uninstallSkills, } from "./core.js";
22
- const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "dev";
21
+ import { _setBaseDir, buildMemoryContext, checkCollection, dailyPath, detectQmd, distilMemories, ensureDirs, ensureQmdAvailableForSync, ensureQmdAvailableForUpdate, getCollectionName, getDailyDir, getMemoryDir, getMemoryFile, getQmdEmbedMode, getQmdHealth, getQmdResultPath, getQmdResultText, getScratchpadFile, getTopicsDir, installSkills, memoryWrite, nowTimestamp, parseScratchpad, probeEmbeddings, readFileSafe, redactSecrets, runQmdEmbedDetached, runQmdSearch, runQmdSync, runQmdUpdateNow, scheduleQmdUpdate, searchRelevantMemories, serializeScratchpad, setupQmdCollection, slugifyTopic, todayStr, topicPath, uninstallSkills, } from "./core.js";
22
+ function readPackageVersion() {
23
+ try {
24
+ const packageJson = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
25
+ return typeof packageJson.version === "string" ? packageJson.version : "dev";
26
+ }
27
+ catch {
28
+ return "dev";
29
+ }
30
+ }
31
+ const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : readPackageVersion();
23
32
  function parseArgs(argv) {
24
33
  const flags = {};
25
34
  const positional = [];
@@ -83,8 +92,11 @@ function exitError(message, json) {
83
92
  async function cmdContext(flags) {
84
93
  const json = hasFlag(flags, "json");
85
94
  const noSearch = hasFlag(flags, "no-search");
95
+ const query = getFlag(flags, "query") ?? "";
86
96
  ensureDirs();
87
- const searchResults = noSearch ? "" : await searchRelevantMemories("");
97
+ if (!noSearch && query)
98
+ await ensureQmdAvailableForSync();
99
+ const searchResults = noSearch ? "" : await searchRelevantMemories(query);
88
100
  const context = buildMemoryContext(searchResults);
89
101
  if (json) {
90
102
  output({ context, directory: getMemoryDir() }, true);
@@ -102,68 +114,28 @@ async function cmdWrite(flags) {
102
114
  const mode = getFlag(flags, "mode") ?? "append";
103
115
  const topic = getFlag(flags, "topic");
104
116
  const date = getFlag(flags, "date");
117
+ const sourceUri = getFlag(flags, "source-uri");
105
118
  if (!["long_term", "daily", "topic"].includes(target)) {
106
119
  exitError("--target must be 'long_term', 'daily', or 'topic' (default: daily)", json);
107
120
  }
121
+ if (!["append", "overwrite"].includes(mode)) {
122
+ exitError("--mode must be 'append' or 'overwrite'", json);
123
+ }
108
124
  if (!content) {
109
125
  exitError("--content is required", json);
110
126
  }
111
- ensureDirs();
112
- const ts = nowTimestamp();
113
- const sid = "cli";
114
- if (target === "daily") {
115
- const filePath = dailyPath(todayStr());
116
- const existing = readFileSafe(filePath) ?? "";
117
- const separator = existing.trim() ? "\n\n" : "";
118
- const stamped = `<!-- ${ts} [${sid}] -->\n${content}`;
119
- fs.writeFileSync(filePath, existing + separator + stamped, "utf-8");
120
- await ensureQmdAvailableForUpdate();
121
- scheduleQmdUpdate();
122
- output(json
123
- ? { ok: true, path: filePath, target, mode: "append", timestamp: ts }
124
- : `Appended to daily log: ${filePath}`, json);
125
- return;
126
- }
127
- if (target === "topic") {
128
- if (!topic) {
129
- exitError("--topic is required when --target is 'topic'", json);
130
- }
131
- const slug = slugifyTopic(topic);
132
- if (!slug) {
133
- exitError("--topic must include at least one letter or number", json);
134
- }
135
- const filePath = topicPath(slug);
136
- const existing = readFileSafe(filePath) ?? "";
137
- const linkDate = date?.trim() || todayStr();
138
- const header = `# Topic: ${topic}\n\n<!-- created: ${ts} [${sid}] -->\n`;
139
- const separator = existing.trim() ? "\n\n" : "";
140
- const base = existing.trim() ? existing : header.trimEnd();
141
- const stamped = `<!-- ${ts} [${sid}] -->\n${content.trim()}\nDaily: [[${linkDate}]]`;
142
- fs.writeFileSync(filePath, `${base}${separator}${stamped}`, "utf-8");
143
- await ensureQmdAvailableForUpdate();
144
- scheduleQmdUpdate();
145
- output(json
146
- ? { ok: true, path: filePath, target, mode: "append", timestamp: ts, topic, slug, date: linkDate }
147
- : `Appended to topic: ${filePath}`, json);
148
- return;
149
- }
150
- // long_term
151
- const memFile = getMemoryFile();
152
- const existing = readFileSafe(memFile) ?? "";
153
- if (mode === "overwrite") {
154
- const stamped = `<!-- last updated: ${ts} [${sid}] -->\n${content}`;
155
- fs.writeFileSync(memFile, stamped, "utf-8");
156
- }
157
- else {
158
- const separator = existing.trim() ? "\n\n" : "";
159
- const stamped = `<!-- ${ts} [${sid}] -->\n${content}`;
160
- fs.writeFileSync(memFile, existing + separator + stamped, "utf-8");
161
- }
162
- await ensureQmdAvailableForUpdate();
163
- scheduleQmdUpdate();
164
- output(json
165
- ? { ok: true, path: memFile, target, mode, timestamp: ts }
166
- : `${mode === "overwrite" ? "Overwrote" : "Appended to"} MEMORY.md`, json);
127
+ const result = await memoryWrite({
128
+ target: target,
129
+ content,
130
+ mode: mode,
131
+ sessionId: "cli",
132
+ topic,
133
+ date,
134
+ sourceUri,
135
+ });
136
+ if (result.isError)
137
+ exitError(result.text.replace(/^Error:\s*/, ""), json);
138
+ output(json ? { ok: true, ...result.details } : result.text.split("\n\n", 1)[0], json);
167
139
  }
168
140
  async function cmdRead(flags) {
169
141
  const json = hasFlag(flags, "json");
@@ -270,7 +242,11 @@ async function cmdScratchpad(flags, positional) {
270
242
  ensureDirs();
271
243
  const spFile = getScratchpadFile();
272
244
  const existing = readFileSafe(spFile) ?? "";
273
- let items = parseScratchpad(existing);
245
+ let items = parseScratchpad(existing).map((item) => ({
246
+ ...item,
247
+ text: redactSecrets(item.text).content,
248
+ meta: redactSecrets(item.meta).content,
249
+ }));
274
250
  if (action === "list") {
275
251
  if (items.length === 0) {
276
252
  output(json ? { items: [], count: 0, open: 0 } : "Scratchpad is empty.", json);
@@ -292,11 +268,12 @@ async function cmdScratchpad(flags, positional) {
292
268
  if (!text)
293
269
  exitError("--text is required for add", json);
294
270
  const ts = nowTimestamp();
295
- items.push({ done: false, text: text, meta: `<!-- ${ts} [cli] -->` });
271
+ const safeText = redactSecrets(text).content;
272
+ items.push({ done: false, text: safeText, meta: `<!-- ${ts} [cli] -->` });
296
273
  fs.writeFileSync(spFile, serializeScratchpad(items), "utf-8");
297
274
  await ensureQmdAvailableForUpdate();
298
275
  scheduleQmdUpdate();
299
- output(json ? { ok: true, action, text } : `Added: - [ ] ${text}`, json);
276
+ output(json ? { ok: true, action, text: safeText } : `Added: - [ ] ${safeText}`, json);
300
277
  return;
301
278
  }
302
279
  if (action === "done" || action === "undo") {
@@ -561,11 +538,18 @@ async function cmdStatus(flags) {
561
538
  const qmdFound = await detectQmd();
562
539
  let hasCollection = false;
563
540
  let health = null;
541
+ let embeddings = "n/a";
564
542
  if (qmdFound) {
565
543
  hasCollection = await checkCollection();
566
544
  if (hasCollection) {
567
545
  await ensureQmdAvailableForSync();
568
546
  health = await getQmdHealth();
547
+ // A live semantic probe confirms embeddings are actually usable, but
548
+ // it costs a real qmd query (and a possible model load), so it's
549
+ // opt-in — the cheap pending-embed count below covers the common case.
550
+ if (hasFlag(flags, "probe")) {
551
+ embeddings = await probeEmbeddings();
552
+ }
569
553
  }
570
554
  }
571
555
  const embedMode = getQmdEmbedMode();
@@ -588,6 +572,7 @@ async function cmdStatus(flags) {
588
572
  available: qmdFound,
589
573
  collection: hasCollection ? getCollectionName() : null,
590
574
  health,
575
+ embeddings,
591
576
  },
592
577
  embedMode,
593
578
  }, true);
@@ -617,6 +602,14 @@ async function cmdStatus(flags) {
617
602
  console.log(`qmd: available`);
618
603
  console.log(`Collection '${getCollectionName()}': ${hasCollection ? "configured" : "not configured — run: agent-memory init"}`);
619
604
  console.log(`Embed mode: ${embedMode}`);
605
+ if (hasCollection && embeddings !== "n/a") {
606
+ const embLabel = embeddings === "ready"
607
+ ? "ready"
608
+ : embeddings === "missing"
609
+ ? "missing — run: agent-memory sync"
610
+ : "unknown (could not verify within probe timeout)";
611
+ console.log(`Embeddings (semantic/deep search): ${embLabel}`);
612
+ }
620
613
  if (health) {
621
614
  if (health.totalFiles !== null)
622
615
  console.log(`Files indexed: ${health.totalFiles}`);
@@ -671,15 +664,15 @@ Commands:
671
664
  version Show binary version
672
665
  install-skills Install (or --uninstall) bundled skills
673
666
  uninstall-skills Uninstall bundled skills
674
- context Build & print context injection string
675
- write Write to memory files (default: daily)
667
+ context Build context; optionally retrieve memories with --query
668
+ write Write to memory files (default: daily; optional --source-uri)
676
669
  read Read memory files
677
670
  scratchpad Manage checklist items
678
671
  search Search across memory files (requires qmd)
679
672
  distil Generate compact MEMORY.md index from daily logs + topics
680
673
  sync Re-index and embed all files (requires qmd)
681
674
  init Initialize memory directory and qmd collection
682
- status Show configuration and status
675
+ status Show configuration and status (--probe for a live embeddings check)
683
676
 
684
677
  Global flags:
685
678
  --dir <path> Override memory directory
@@ -688,7 +681,7 @@ Global flags:
688
681
  Examples:
689
682
  agent-memory init
690
683
  agent-memory write --content "Fixed auth bug in login flow"
691
- agent-memory write --target long_term --content "User prefers dark mode"
684
+ agent-memory write --target long_term --content "User prefers dark mode" --source-uri "session://agent/turn/12"
692
685
  agent-memory write --target topic --topic "auth" --content "Rolled JWT refresh to edge"
693
686
  agent-memory read --target long_term
694
687
  agent-memory read --target daily --date 2026-02-15
@@ -700,7 +693,7 @@ Examples:
700
693
  agent-memory scratchpad done --text "PR #42"
701
694
  agent-memory search --query "database choice" --mode keyword
702
695
  agent-memory distil --dry-run
703
- agent-memory context --no-search
696
+ agent-memory context --query "database choice"
704
697
  agent-memory sync
705
698
  agent-memory status --json`);
706
699
  }
package/dist/core.d.ts CHANGED
@@ -40,6 +40,13 @@ export interface PreviewResult {
40
40
  previewLines: number;
41
41
  previewChars: number;
42
42
  }
43
+ /** Redact common credential shapes before content reaches disk or agent context. */
44
+ export declare function redactSecrets(content: string): {
45
+ content: string;
46
+ redacted: boolean;
47
+ };
48
+ /** Apply trust, lifecycle, and secret policy to complete logical write entries. */
49
+ export declare function filterMemoryForContext(content: string, now?: Date): string;
43
50
  export declare function truncateLines(lines: string[], maxLines: number, mode: TruncateMode): {
44
51
  lines: string[];
45
52
  truncated: boolean;
@@ -166,6 +173,7 @@ export declare function searchRelevantMemories(prompt: string): Promise<string>;
166
173
  export interface QmdSearchResult {
167
174
  path?: string;
168
175
  file?: string;
176
+ context?: string;
169
177
  score?: number;
170
178
  content?: string;
171
179
  chunk?: string;
@@ -175,10 +183,21 @@ export interface QmdSearchResult {
175
183
  }
176
184
  export declare function getQmdResultPath(r: QmdSearchResult): string | undefined;
177
185
  export declare function getQmdResultText(r: QmdSearchResult): string;
178
- export declare function runQmdSearch(mode: "keyword" | "semantic" | "deep", query: string, limit: number): Promise<{
186
+ export declare function runQmdSearch(mode: "keyword" | "semantic" | "deep", query: string, limit: number, options?: {
187
+ signal?: AbortSignal;
188
+ }): Promise<{
179
189
  results: QmdSearchResult[];
180
190
  stderr: string;
181
191
  }>;
192
+ /**
193
+ * Best-effort check of whether vector embeddings are actually usable for
194
+ * semantic/deep search right now. Runs a tiny semantic probe and looks for
195
+ * qmd's "need embeddings" warning. Bounded by a short timeout because the very
196
+ * first semantic query can trigger a model download — returns "unknown" rather
197
+ * than blocking on it. "ready" means the probe ran without the warning; it does
198
+ * not prove the index has content.
199
+ */
200
+ export declare function probeEmbeddings(): Promise<"ready" | "missing" | "unknown">;
182
201
  export interface ToolResult {
183
202
  text: string;
184
203
  details: Record<string, unknown>;
@@ -191,6 +210,7 @@ export declare function memoryWrite(params: {
191
210
  sessionId?: string;
192
211
  topic?: string;
193
212
  date?: string;
213
+ sourceUri?: string;
194
214
  }): Promise<ToolResult>;
195
215
  export declare function scratchpadAction(params: {
196
216
  action: "add" | "done" | "undo" | "clear_done" | "list";