@ghilteras/opencode-agent-memory 0.4.1 → 0.4.2

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,13 +1,13 @@
1
1
  # opencode-agent-memory
2
2
 
3
- > **Fork notice**: This is the maintained fork `@ghilteras/opencode-agent-memory` v0.4.0 of
3
+ > **Fork notice**: This is the maintained fork `@ghilteras/opencode-agent-memory` v0.4.2 of
4
4
  > [opencode-agent-memory](https://github.com/joshuadavidthomas/opencode-agent-memory)
5
- > (joshuadavidthomas, MIT). This release adopts the "proper timestamps without cache
5
+ > (joshuadavidthomas, MIT). v0.4.2 adds a title-anchor floor so title queries are not diluted by long bodies. This release adopts the "proper timestamps without cache
6
6
  > busting" design (per-block `modified_at` frontmatter, remove the volatile
7
7
  > `memory_metadata` block, append memory XML at the end of the system prompt) derived
8
8
  > from the Annakan/draxxris fork plan. Upstream PR #20 (freeze metadata timestamps) is
9
9
  > superseded by the stronger per-block design and remains open upstream. v0.4.0 swaps
10
- > the embedding model to a multilingual one and adds a versioned embedding format.
10
+ > the embedding model to a multilingual one and adds a versioned embedding format. v0.4.1 makes journal search fast (in-memory per-store index, embedding-model warmup at init) and fixes the js-yaml import for ESM runtimes.
11
11
 
12
12
  [Letta](https://letta.com)-style editable [memory blocks](https://docs.letta.com/guides/agents/memory-blocks/) for [OpenCode](https://opencode.ai).
13
13
 
@@ -37,7 +37,7 @@ Add to your OpenCode config (`~/.config/opencode/opencode.json`):
37
37
 
38
38
  ```json
39
39
  {
40
- "plugin": ["opencode-agent-memory"]
40
+ "plugin": ["@ghilteras/opencode-agent-memory"]
41
41
  }
42
42
  ```
43
43
 
@@ -47,7 +47,7 @@ Optionally, pin to a specific version for stability:
47
47
 
48
48
  ```json
49
49
  {
50
- "plugin": ["opencode-agent-memory@0.2.0"]
50
+ "plugin": ["@ghilteras/opencode-agent-memory@0.4.1"]
51
51
  }
52
52
  ```
53
53
 
@@ -58,7 +58,7 @@ OpenCode fetches unpinned plugins from npm on each startup; pinned versions are
58
58
  If you want to customize or contribute:
59
59
 
60
60
  ```bash
61
- git clone https://github.com/joshuadavidthomas/opencode-agent-memory ~/.config/opencode/opencode-agent-memory
61
+ git clone https://github.com/Ghilteras/opencode-agent-memory ~/.config/opencode/opencode-agent-memory
62
62
  mkdir -p ~/.config/opencode/plugin
63
63
  ln -sf ~/.config/opencode/opencode-agent-memory/src/plugin.ts ~/.config/opencode/plugin/memory.ts
64
64
  ```
@@ -91,6 +91,8 @@ Journal entries are append-only markdown files with YAML frontmatter, stored in
91
91
 
92
92
  Embedding files (`.embedding`, written alongside each entry) use a versioned format (`{ v: 2, model, dimension, vector }`) so that stale or mismatched embeddings are detected: if the stored dimension doesn't match the current model, the entry falls back to text matching instead of failing the search. Legacy bare-array embeddings from v0.3.x remain readable.
93
93
 
94
+ **Search performance (v0.4.1–v0.4.2)**: `journal_search` keeps an in-memory index per store instance. Entries are re-read only when their file changed — fingerprinted on both the entry `.md` and its `.embedding` sidecar (mtime + size), so regenerating or deleting a sidecar is picked up on the next search without a restart. The embedding model is warmed up in the background at plugin init when the journal is enabled (respecting `cacheDir`), so the first search after a restart doesn't pay the cold model-load cost. Since v0.4.2, a query whose text matches an entry's title (in either direction, case-insensitive) is guaranteed a high score floor (0.75), so title-based pointers remain retrievable even for entries with long bodies.
95
+
94
96
  ### cacheDir Configuration
95
97
 
96
98
  By default the transformers.js model cache lives in the default location (`~/.cache/huggingface`). You can relocate it by setting `cacheDir` at the top level of `~/.config/opencode/agent-memory.json`:
@@ -173,7 +175,7 @@ Also worth exploring: [private-journal-mcp](https://github.com/obra/private-jour
173
175
  Contributions are welcome! Here's how to set up for development:
174
176
 
175
177
  ```bash
176
- git clone https://github.com/joshuadavidthomas/opencode-agent-memory
178
+ git clone https://github.com/Ghilteras/opencode-agent-memory
177
179
  cd opencode-agent-memory
178
180
  bun install
179
181
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghilteras/opencode-agent-memory",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Letta-style editable memory blocks + journal for OpenCode (maintained fork of opencode-agent-memory).",
5
5
  "author": "Angelo Pantano <ghilteras@protonmail.com>",
6
6
  "license": "MIT",
@@ -392,6 +392,27 @@ describe("journal store", () => {
392
392
  expect(result.total).toBeGreaterThan(0);
393
393
  });
394
394
 
395
+ test("title query surfaces long-body entry despite diluted embedding", async () => {
396
+ tmpDir = await mkTmpDir();
397
+ const store = createJournalStore(tmpDir);
398
+ const entry = await store.write({
399
+ title: "Agent-memory release delivery channel",
400
+ body: "Unrelated topics include coastal erosion, orbital mechanics, ceramic glazing, " +
401
+ "database indexing, ancient trade routes, orchard irrigation, keyboard switches, " +
402
+ "bird migration, public transit planning, and archival paper chemistry. These notes " +
403
+ "continue across varied subjects so the long body dilutes its mean-pooled embedding. " +
404
+ "Further unrelated details cover weather instruments, typography, fermentation, " +
405
+ "geology, woodworking, astronomy, logistics, and museum conservation. The entry is " +
406
+ "deliberately verbose and semantically unrelated to a fresh exact-title query, while " +
407
+ "its title remains the searchable anchor for this regression case.",
408
+ });
409
+
410
+ // The deterministic mock embeddings produce an unrelated query vector here;
411
+ // the title-anchor floor guarantees presence regardless of cosine score.
412
+ const result = await store.search({ text: "Agent-memory release delivery channel" });
413
+ expect(result.entries.some((candidate) => candidate.id === entry.id)).toBe(true);
414
+ });
415
+
395
416
  test("search skips stale-dimension embedding and falls back to text match", async () => {
396
417
  tmpDir = await mkTmpDir();
397
418
  const store = createJournalStore(tmpDir);
package/src/journal.ts CHANGED
@@ -86,6 +86,13 @@ function embeddingPath(entryPath: string): string {
86
86
  return entryPath.replace(/\.md$/, ".embedding");
87
87
  }
88
88
 
89
+ function titleMatches(queryText: string, title: string): boolean {
90
+ const q = queryText.trim().toLowerCase();
91
+ if (!q) return false;
92
+ const t = title.trim().toLowerCase();
93
+ return t.includes(q) || q.includes(t);
94
+ }
95
+
89
96
  async function readEntryFile(filePath: string): Promise<JournalEntry> {
90
97
  const raw = await fs.readFile(filePath, "utf-8");
91
98
  const { frontmatterText, body } = splitFrontmatter(raw);
@@ -412,6 +419,13 @@ export function createJournalStore(configDir?: string, cacheDir?: string): Journ
412
419
  score = haystack.includes(query.text.toLowerCase()) ? 1 : 0;
413
420
  }
414
421
 
422
+ // Title-anchor floor: a query that matches the entry title (either
423
+ // direction, case-insensitive) must not be diluted below the top
424
+ // ranks by long-body mean pooling (see 20260905-171239-533).
425
+ if (titleMatches(query.text, entry.title)) {
426
+ score = Math.max(score, 0.75);
427
+ }
428
+
415
429
  if (score <= 0) continue;
416
430
  } else {
417
431
  // No text query - chronological order (score by recency)