@intentface/latch-memory 0.9.1

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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -0
  3. package/dist/extraction-key.d.ts +20 -0
  4. package/dist/extraction-key.d.ts.map +1 -0
  5. package/dist/extraction-key.js +43 -0
  6. package/dist/extraction-key.js.map +1 -0
  7. package/dist/index-file.d.ts +77 -0
  8. package/dist/index-file.d.ts.map +1 -0
  9. package/dist/index-file.js +169 -0
  10. package/dist/index-file.js.map +1 -0
  11. package/dist/index.d.ts +23 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +23 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/jobs/types.d.ts +133 -0
  16. package/dist/jobs/types.d.ts.map +1 -0
  17. package/dist/jobs/types.js +12 -0
  18. package/dist/jobs/types.js.map +1 -0
  19. package/dist/prompts/consolidator.d.ts +18 -0
  20. package/dist/prompts/consolidator.d.ts.map +1 -0
  21. package/dist/prompts/consolidator.js +76 -0
  22. package/dist/prompts/consolidator.js.map +1 -0
  23. package/dist/prompts/extractor.d.ts +34 -0
  24. package/dist/prompts/extractor.d.ts.map +1 -0
  25. package/dist/prompts/extractor.js +60 -0
  26. package/dist/prompts/extractor.js.map +1 -0
  27. package/dist/prompts/inject.d.ts +20 -0
  28. package/dist/prompts/inject.d.ts.map +1 -0
  29. package/dist/prompts/inject.js +58 -0
  30. package/dist/prompts/inject.js.map +1 -0
  31. package/dist/provider.d.ts +47 -0
  32. package/dist/provider.d.ts.map +1 -0
  33. package/dist/provider.js +101 -0
  34. package/dist/provider.js.map +1 -0
  35. package/dist/sanitize.d.ts +29 -0
  36. package/dist/sanitize.d.ts.map +1 -0
  37. package/dist/sanitize.js +88 -0
  38. package/dist/sanitize.js.map +1 -0
  39. package/dist/schema.d.ts +60 -0
  40. package/dist/schema.d.ts.map +1 -0
  41. package/dist/schema.js +144 -0
  42. package/dist/schema.js.map +1 -0
  43. package/dist/scope.d.ts +62 -0
  44. package/dist/scope.d.ts.map +1 -0
  45. package/dist/scope.js +136 -0
  46. package/dist/scope.js.map +1 -0
  47. package/dist/search-evidence.d.ts +34 -0
  48. package/dist/search-evidence.d.ts.map +1 -0
  49. package/dist/search-evidence.js +40 -0
  50. package/dist/search-evidence.js.map +1 -0
  51. package/dist/store/loredex.d.ts +61 -0
  52. package/dist/store/loredex.d.ts.map +1 -0
  53. package/dist/store/loredex.js +558 -0
  54. package/dist/store/loredex.js.map +1 -0
  55. package/dist/store/memory.d.ts +31 -0
  56. package/dist/store/memory.d.ts.map +1 -0
  57. package/dist/store/memory.js +156 -0
  58. package/dist/store/memory.js.map +1 -0
  59. package/dist/tools.d.ts +91 -0
  60. package/dist/tools.d.ts.map +1 -0
  61. package/dist/tools.js +389 -0
  62. package/dist/tools.js.map +1 -0
  63. package/dist/types.d.ts +130 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/types.js +13 -0
  66. package/dist/types.js.map +1 -0
  67. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Intentface
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # @intentface/latch-memory
2
+
3
+ Pluggable, platform-managed memory for Latch agents. Declare `memory` on an
4
+ agent and the platform does all upkeep:
5
+
6
+ 1. **Always-loaded index** — a compiled `MEMORY.md` (≤100 lines, one fact per
7
+ line with date, provenance, and source links) is injected into the system
8
+ prompt every turn, plus standing instructions for the tools.
9
+ 2. **Episodic store + recall** — immutable, timestamped episode notes live in
10
+ the agent's own connection workspace: per-user scopes under the caller's
11
+ private root (`__private_root/_memory/<agent|_user>/u-<id>/`), `agent-org`
12
+ under the shared `_memory/<agent>/org/`;
13
+ the agent searches them on demand with `memory_search` (every recall is
14
+ usage-tracked — the retention signal) and saves explicit "remember this"
15
+ notes with `memory_save`.
16
+ 3. **Background consolidation** — a cron sweep extracts durable facts from
17
+ idle chats (`memory-extractor`, cheap model) and folds pending episodes
18
+ into the index (`memory-consolidator`), superseding and expiring entries.
19
+
20
+ ## Design invariants
21
+
22
+ - **Scopes** (`agent-user` | `agent-org` | `user`) are hard boundaries: tools
23
+ are path-locked at construction, and consolidation never crosses a scope.
24
+ Per-user scopes live under the Loredex **private root** (owner-only ACL):
25
+ `__private_root/_memory/<agent>/u-<id>` for `agent-user`,
26
+ `__private_root/_memory/_user/u-<id>` for `user`. The `u-<id>` leaf is kept
27
+ even though the private root is already per-user: it keeps roots globally
28
+ unique (scope keys stay `${connection}:${root}`) and keeps users' stores
29
+ apart on a workspace-shared connection, where every caller resolves the
30
+ same private root.
31
+ - **Non-destructive consolidation**: episodes are immutable; only the index is
32
+ rewritten, every rewrite snapshots the previous version first, and the
33
+ validator rejects any rewrite that silently drops a previously ACTIVE entry
34
+ (supersede/expire = move to `## Archive`; archived entries may be dropped
35
+ later — snapshots preserve history, so the size cap stays satisfiable).
36
+ - **Graceful degradation**: an unreachable/unauthorized store means a turn
37
+ without memory, never a failed turn.
38
+ - **Anti-hot-loop**: every job completion advances its eligibility timestamp —
39
+ success, failure, or "not actually memory-enabled".
40
+ - **Extraction idempotency**: every completed extraction records an idempotency
41
+ key (`extractionRunKey` — content hash of the message window + the extraction
42
+ prompt version). An unchanged window never re-buys a run, and a run that
43
+ found NOTHING tombstones its content via the same key. Failed runs never
44
+ record a key, so they stay retryable. Bump `EXTRACTION_PROMPT_VERSION` to
45
+ invalidate old keys after a material prompt change.
46
+ - **Provenance**: every fact is tagged `user-said` / `tool-derived` /
47
+ `user-requested`; the extractor is instructed to scrub secrets/PII.
48
+ - **Trust marks**: extractor-written episodes carry `status: unverified`
49
+ frontmatter; index entries folded from them carry an `unverified` marker
50
+ (labeled — in the injected block and in search results — never hidden). The
51
+ consolidator promotes an entry only on later, independent corroboration.
52
+ Explicit `memory_save` notes are never marked unverified.
53
+ - **Aliases + create-safety**: entries can carry `aka:` aliases (episodes:
54
+ `aliases:` frontmatter) for subjects with several names/spellings.
55
+ `memory_search` matches them exactly (NFKC/case/whitespace-normalized),
56
+ tags every hit with `matchedOn` evidence (`alias-exact` | `description` |
57
+ `body`), and derives a response-level `create_safety`
58
+ (`exists` | `probable` | `unknown`) that `memory_save` instructs the agent
59
+ to consult before creating anything new.
60
+ - **Injection hardening**: memory text is LLM-authored from user conversation —
61
+ a prompt-injection persistence vector. At injection time (the `<memory>`
62
+ block, search snippets) it passes through `sanitizeMemoryText`, which
63
+ de-fangs envelope/role tags and strips a named set of known injection
64
+ phrasings. Volume reduction, not a guarantee — the standing instructions
65
+ additionally declare the envelope's content to be data, never instructions.
66
+
67
+ ## Pieces
68
+
69
+ | Module | What it is |
70
+ | --- | --- |
71
+ | `types` / `scope` | `MemoryStore` seam, `ScopeRef` derivation (`scopeRefOf`) |
72
+ | `store/loredex` | `MemoryStore` over a Loredex MCP connection (opens per op via the `ConnectionRegistry`; tolerant result parsing; no delete — snapshot + rewrite) |
73
+ | `index-file` | MEMORY.md grammar: parse, `validateIndexRewrite`, id minting |
74
+ | `provider` | `createMemoryProvider` → the runtime's `MemoryProvider` (TTL-cached index block + bound tools) |
75
+ | `tools` | agent-facing `memory_search`/`memory_save` + scope-locked job tools |
76
+ | `prompts/*` | injection block, extractor/consolidator instructions + prompt builders |
77
+ | `jobs/types` | `MemoryJobStore` (implemented in `@intentface/latch-drizzle`: `createMemoryJobStore`) |
78
+
79
+ The sweep itself is platform glue (`apps/latch-platform/src/lib/memory/sweep.ts`),
80
+ following the Codex pattern: idle-gated parallel extraction, per-scope
81
+ lease-serialized consolidation with cooldown + watermark, usage-based expiry
82
+ (default 30 days unrecalled).
83
+
84
+ ## Wiring it into a host
85
+
86
+ This package ships the ingredients, not the jobs — a host app (see
87
+ `apps/latch-platform` for the reference implementation of every step) wires:
88
+
89
+ 1. **Tables + job store** — `@intentface/latch-drizzle`'s migrations create
90
+ `latch_memory_chat_state` / `latch_memory_scope_state` / `latch_memory_usage`;
91
+ build the store with `createMemoryJobStore(db)`.
92
+ 2. **Store + provider → runtime** — in the composition root:
93
+
94
+ ```ts
95
+ const memoryStore = createLoredexStore({ connections });
96
+ const memoryProvider = createMemoryProvider({
97
+ store: memoryStore,
98
+ scopeOf: ({ principal, agent, memory }) =>
99
+ scopeRefOf({ agent, memory, userId: principal.userId }),
100
+ recordUsage: (scopeKey, ids) => memoryJobs.recordUsage(scopeKey, ids, Date.now()),
101
+ onEpisodeWritten: (a) => memoryJobs.touchScope(/* mark scope dirty */),
102
+ });
103
+ const runtime = createRuntime({ ...rest, memory: memoryProvider });
104
+ ```
105
+
106
+ That alone gives every agent declaring `memory` the injected index block and
107
+ the `memory_search`/`memory_save` tools.
108
+ 3. **Two job agents** — register `memory-extractor` and `memory-consolidator`
109
+ as code agents (cheap model, no connections) built from the exported parts:
110
+ `EXTRACTOR_INSTRUCTIONS`/`CONSOLIDATOR_INSTRUCTIONS` + the scope-locked tool
111
+ factories (`memoryReadIndexTool`, `memoryWriteEpisodeTool`,
112
+ `memoryReadEpisodesTool`, `memoryWriteIndexTool`), bound from the sweep's
113
+ `turnContext`. Hide them from user-facing pickers (the platform reserves the
114
+ `memory-*` name prefix).
115
+ 4. **The sweep** — a cron-driven loop over `MemoryJobStore`: claim idle chats →
116
+ `runtime.runAgent("memory-extractor", …)` under the chat's own reconstructed
117
+ principal → advance the seq watermark; claim dirty scopes past cooldown →
118
+ `runtime.runAgent("memory-consolidator", …)` → clear consumed episodes.
119
+ Copy `apps/latch-platform/src/lib/memory/sweep.ts` — the always-advance
120
+ eligibility discipline (no hot loops) and the lease fencing live there.
121
+ 5. **Declare memory on agents** — `memory: { scope, connection }` in
122
+ `defineAgent` config or the UI agent record; the connection must be one the
123
+ agent itself declares.
124
+
125
+ ## Offline evals
126
+
127
+ `evals/` holds the deterministic (zero-LLM, zero-network) evaluation
128
+ foundation for this package:
129
+
130
+ - **Synthetic corpus** — a seeded, byte-deterministic week of running-coach
131
+ conversations (`evals/corpus/`, seed 42, mulberry32 PRNG) with planted
132
+ contradictions, temporal supersessions, stale facts, paraphrased
133
+ prompt-injection attempts, and implicit preferences. Gold labels live in
134
+ `evals/fixtures/corpus/gold/`, referenced by `fixture_id`
135
+ (`<session>:<messageIndex>`).
136
+ - **Retrieval benchmark** — a reference memory-file set
137
+ (`evals/fixtures/memory-files/`), sealed qrels (`evals/fixtures/qrels.json`;
138
+ the search path only ever sees a `PublicQuery {id, text}` projection), and a
139
+ pure scorer (P@k, R@k, MRR, graded nDCG@k, top-1).
140
+
141
+ Commands (from `packages/memory/`):
142
+
143
+ - `pnpm eval:memory-search` — run every qrels query through the real
144
+ `memory_search` tool over the in-memory store; prints a per-tier report and
145
+ rewrites the committed baseline
146
+ (`evals/results/memory-search-baseline.json`).
147
+ - `pnpm eval:regen-fixtures` — regenerate all committed fixtures after a
148
+ deliberate template change (`determinism.test.ts` fails on any drift).
149
+
150
+ The adversarial qrels tier (paraphrases sharing <30% content words with their
151
+ target) is EXPECTED to score ~0 on keyword search — it exists to measure the
152
+ gap, not to be tuned away.
153
+
154
+ ## Caveat
155
+
156
+ Per-user scopes (`agent-user`, `user`) are stored under the caller's Loredex
157
+ private root (owner-only ACL), which is real privacy **only when the
158
+ connection authenticates as the end user**: on a workspace-shared connection
159
+ every call resolves the connection identity's private root, not the end
160
+ user's — all users' memory then lands in one private root, separated only by
161
+ the `u-<id>` folder convention (as before, but now hidden from the rest of
162
+ the workspace). Use a personal connection where per-user privacy matters.
163
+ `agent-org` memory lives in the shared `_memory/**` tree by design.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The idempotency key for one extraction window. Hashes role + text only
3
+ * (order-preserving, unambiguously framed), deliberately NOT `seq`: a
4
+ * re-sequenced chat (legacy heal, replay) still hashes the same, which is
5
+ * exactly the duplicate-run case the key exists to suppress. Bumping
6
+ * `EXTRACTION_PROMPT_VERSION` changes every key, so already-tombstoned
7
+ * windows become extractable again under the new prompt.
8
+ *
9
+ * `opts.schemaFingerprint` (see `schemasFingerprint`) covers the run's level
10
+ * set and per-level schemas the same way: a schema edit — or a level added or
11
+ * removed — changes the key, so the same content becomes re-extractable under
12
+ * the new configuration.
13
+ */
14
+ export declare function extractionRunKey(messages: ReadonlyArray<{
15
+ role: string;
16
+ text: string;
17
+ }>, opts?: {
18
+ schemaFingerprint?: string;
19
+ }): string;
20
+ //# sourceMappingURL=extraction-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extraction-key.d.ts","sourceRoot":"","sources":["../src/extraction-key.ts"],"names":[],"mappings":"AA2BA;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACvD,IAAI,CAAC,EAAE;IAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,GACpC,MAAM,CAIR"}
@@ -0,0 +1,43 @@
1
+ import { EXTRACTION_PROMPT_VERSION } from "./prompts/extractor.js";
2
+ /**
3
+ * Extraction-run idempotency keys.
4
+ *
5
+ * A key fingerprints WHAT an extraction run processed: the content of the
6
+ * message window plus the extraction prompt version. The sweep persists the
7
+ * key of the last COMPLETED run per chat (`last_extracted_key`) and skips a
8
+ * new paid run whenever the next window hashes to the same key (watermark
9
+ * reset, replayed rows) — including runs that yielded zero memories, where
10
+ * the recorded key doubles as a tombstone so the same content is never
11
+ * re-extracted. FAILED runs (LLM error/timeout) never record a key, so they
12
+ * stay eligible for retry.
13
+ */
14
+ /** FNV-1a 64-bit, hex — deterministic, dependency-free (Node/edge safe). */
15
+ function fnv1a64(input) {
16
+ let h = 0xcbf29ce484222325n;
17
+ const prime = 0x100000001b3n;
18
+ const mask = 0xffffffffffffffffn;
19
+ for (let i = 0; i < input.length; i++) {
20
+ h ^= BigInt(input.charCodeAt(i));
21
+ h = (h * prime) & mask;
22
+ }
23
+ return h.toString(16).padStart(16, "0");
24
+ }
25
+ /**
26
+ * The idempotency key for one extraction window. Hashes role + text only
27
+ * (order-preserving, unambiguously framed), deliberately NOT `seq`: a
28
+ * re-sequenced chat (legacy heal, replay) still hashes the same, which is
29
+ * exactly the duplicate-run case the key exists to suppress. Bumping
30
+ * `EXTRACTION_PROMPT_VERSION` changes every key, so already-tombstoned
31
+ * windows become extractable again under the new prompt.
32
+ *
33
+ * `opts.schemaFingerprint` (see `schemasFingerprint`) covers the run's level
34
+ * set and per-level schemas the same way: a schema edit — or a level added or
35
+ * removed — changes the key, so the same content becomes re-extractable under
36
+ * the new configuration.
37
+ */
38
+ export function extractionRunKey(messages, opts) {
39
+ const payload = messages.map((m) => `${m.role}\u0000${m.text}`).join("\u0001");
40
+ const suffix = opts?.schemaFingerprint ? `:${opts.schemaFingerprint}` : "";
41
+ return `x${EXTRACTION_PROMPT_VERSION}:${fnv1a64(payload)}${suffix}`;
42
+ }
43
+ //# sourceMappingURL=extraction-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extraction-key.js","sourceRoot":"","sources":["../src/extraction-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;;;;;;;;GAWG;AAEH,4EAA4E;AAC5E,SAAS,OAAO,CAAC,KAAa;IAC5B,IAAI,CAAC,GAAG,mBAAmB,CAAC;IAC5B,MAAM,KAAK,GAAG,cAAc,CAAC;IAC7B,MAAM,IAAI,GAAG,mBAAmB,CAAC;IACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QACjC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;IACzB,CAAC;IACD,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAuD,EACvD,IAAqC;IAErC,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/E,MAAM,MAAM,GAAG,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,OAAO,IAAI,yBAAyB,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,MAAM,EAAE,CAAC;AACtE,CAAC"}
@@ -0,0 +1,77 @@
1
+ import type { Provenance } from "./types.js";
2
+ /**
3
+ * The compiled index (MEMORY.md): parsing, id minting, and the rewrite
4
+ * validator that makes consolidation non-destructive.
5
+ *
6
+ * Entry grammar (one line per fact):
7
+ *
8
+ * - [m-7f3a] (2026-07-12, user-said) Prefers concise answers. ← episodes/2026-07-12-09-30-onboarding.md
9
+ * - [m-9911] (2026-06-02, user-said; superseded 2026-07-20 by m-a1c2) Old fact. ← episodes/…
10
+ *
11
+ * Optional `;`-separated parenthetical tail markers (any order):
12
+ * - `aka: Robert Smith, Bobby` — alternate names/spellings of the entry's
13
+ * subject, matched exactly by memory_search;
14
+ * - `unverified` — auto-extracted and not yet confirmed (trust mark; the
15
+ * consolidator removes it on corroboration).
16
+ *
17
+ * Entries under a `## Archive` heading are archived (superseded/expired).
18
+ * The validator enforces the two invariants the research said matter most:
19
+ * a hard size cap, and "no entry ever silently disappears" — a rewrite may
20
+ * move an entry to Archive but never drop its id.
21
+ */
22
+ export declare const MEMORY_INDEX_FILENAME = "MEMORY.md";
23
+ /** Soft cap the consolidator is instructed to stay under. */
24
+ export declare const INDEX_LINE_CAP = 100;
25
+ /** Hard cap: rewrites longer than this are rejected outright. */
26
+ export declare const INDEX_LINE_HARD_CAP = 120;
27
+ export interface IndexEntry {
28
+ id: string;
29
+ /** YYYY-MM-DD the fact was recorded/updated. */
30
+ date?: string;
31
+ provenance?: Provenance;
32
+ /** Set when the parenthetical carries a `superseded <date> by <id>` marker. */
33
+ superseded?: {
34
+ date?: string;
35
+ by?: string;
36
+ };
37
+ /** Alternate names/spellings of the entry's subject (the `aka:` marker). */
38
+ aliases: string[];
39
+ /** True when the parenthetical carries the `unverified` trust mark. */
40
+ unverified: boolean;
41
+ text: string;
42
+ /** Source episode paths (the `← …` tail). */
43
+ sources: string[];
44
+ /** True when the entry sits under the `## Archive` heading. */
45
+ archived: boolean;
46
+ /** The entry line verbatim (for surfacing the entry as a search snippet). */
47
+ raw: string;
48
+ }
49
+ export interface ParsedIndex {
50
+ entries: IndexEntry[];
51
+ lineCount: number;
52
+ /** Lines that look like entries but don't parse — surfaced to the model. */
53
+ malformed: string[];
54
+ }
55
+ export declare function parseIndex(text: string): ParsedIndex;
56
+ export declare class IndexValidationError extends Error {
57
+ readonly problems: string[];
58
+ constructor(problems: string[]);
59
+ }
60
+ /**
61
+ * Validate a proposed index rewrite against the previous version. Returns the
62
+ * problem list (empty = valid). Rules:
63
+ * - hard line cap (`INDEX_LINE_HARD_CAP`);
64
+ * - no duplicate entry ids;
65
+ * - every previously ACTIVE id must still be present (active or archived) —
66
+ * supersede/expire is a MOVE to `## Archive`, never a silent delete;
67
+ * - previously ARCHIVED ids MAY be dropped: every rewrite snapshots the full
68
+ * prior index first, so archived history stays recoverable while the cap
69
+ * stays satisfiable (otherwise Archive would grow past the cap forever);
70
+ * - entry-shaped lines must parse.
71
+ */
72
+ export declare function validateIndexRewrite(prev: string | undefined, next: string): string[];
73
+ /** Mint a new entry id not already taken (6 hex chars, `m-` prefixed). */
74
+ export declare function mintMemoryId(taken: ReadonlySet<string>): string;
75
+ /** All `[m-…]` ids referenced in a blob of text (for usage tracking). */
76
+ export declare function memoryIdsIn(text: string): string[];
77
+ //# sourceMappingURL=index-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-file.d.ts","sourceRoot":"","sources":["../src/index-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,eAAO,MAAM,qBAAqB,cAAc,CAAC;AACjD,6DAA6D;AAC7D,eAAO,MAAM,cAAc,MAAM,CAAC;AAClC,iEAAiE;AACjE,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,+EAA+E;IAC/E,UAAU,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,4EAA4E;IAC5E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,uEAAuE;IACvE,UAAU,EAAE,OAAO,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAC;IAClB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AASD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAwEpD;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;gBAChB,QAAQ,EAAE,MAAM,EAAE;CAK/B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAiCrF;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,CAO/D;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAElD"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * The compiled index (MEMORY.md): parsing, id minting, and the rewrite
3
+ * validator that makes consolidation non-destructive.
4
+ *
5
+ * Entry grammar (one line per fact):
6
+ *
7
+ * - [m-7f3a] (2026-07-12, user-said) Prefers concise answers. ← episodes/2026-07-12-09-30-onboarding.md
8
+ * - [m-9911] (2026-06-02, user-said; superseded 2026-07-20 by m-a1c2) Old fact. ← episodes/…
9
+ *
10
+ * Optional `;`-separated parenthetical tail markers (any order):
11
+ * - `aka: Robert Smith, Bobby` — alternate names/spellings of the entry's
12
+ * subject, matched exactly by memory_search;
13
+ * - `unverified` — auto-extracted and not yet confirmed (trust mark; the
14
+ * consolidator removes it on corroboration).
15
+ *
16
+ * Entries under a `## Archive` heading are archived (superseded/expired).
17
+ * The validator enforces the two invariants the research said matter most:
18
+ * a hard size cap, and "no entry ever silently disappears" — a rewrite may
19
+ * move an entry to Archive but never drop its id.
20
+ */
21
+ export const MEMORY_INDEX_FILENAME = "MEMORY.md";
22
+ /** Soft cap the consolidator is instructed to stay under. */
23
+ export const INDEX_LINE_CAP = 100;
24
+ /** Hard cap: rewrites longer than this are rejected outright. */
25
+ export const INDEX_LINE_HARD_CAP = 120;
26
+ // Id charset is deliberately wider than what `mintMemoryId` produces (hex):
27
+ // the consolidator model mints ids itself and "hex-looking" ids like m-g8d7e9
28
+ // are common. Ids are opaque keys — uniqueness matters, the alphabet doesn't,
29
+ // and rejecting them sends the model into pointless rewrite loops.
30
+ const ENTRY_RE = /^\s*-\s*\[(m-[0-9a-z]{4,8})\]\s*\(([^)]*)\)\s*(.*)$/;
31
+ const PROVENANCES = new Set(["user-said", "tool-derived", "user-requested", "mixed"]);
32
+ export function parseIndex(text) {
33
+ const lines = text.split("\n");
34
+ const entries = [];
35
+ const malformed = [];
36
+ let archived = false;
37
+ for (const line of lines) {
38
+ if (/^##\s+archive\b/i.test(line.trim())) {
39
+ archived = true;
40
+ continue;
41
+ }
42
+ if (/^##\s+/.test(line.trim()) && !/^##\s+archive\b/i.test(line.trim())) {
43
+ // A new section after Archive would be unusual, but honor it.
44
+ if (archived && !/archive/i.test(line))
45
+ archived = false;
46
+ continue;
47
+ }
48
+ const looksLikeEntry = /^\s*-\s*\[m-/.test(line);
49
+ const m = ENTRY_RE.exec(line);
50
+ if (!m) {
51
+ if (looksLikeEntry)
52
+ malformed.push(line.trim());
53
+ continue;
54
+ }
55
+ const [, id, paren, rest] = m;
56
+ // Parenthetical: `<date>, <provenance>[; superseded <date> by <id>]` — parse
57
+ // leniently; a hand-edited index must not break recall.
58
+ const [head, ...tailParts] = paren.split(";").map((s) => s.trim());
59
+ const headBits = head.split(",").map((s) => s.trim());
60
+ const date = /^\d{4}-\d{2}-\d{2}$/.test(headBits[0] ?? "") ? headBits[0] : undefined;
61
+ const prov = headBits.find((b) => PROVENANCES.has(b));
62
+ let superseded;
63
+ const supersededPart = tailParts.find((p) => /^superseded\b/i.test(p));
64
+ if (supersededPart) {
65
+ const sd = /(\d{4}-\d{2}-\d{2})/.exec(supersededPart)?.[1];
66
+ // Bounded lookahead: an overlong id (m-abcdefghi) must not silently
67
+ // truncate into a valid-looking — but wrong — supersession pointer.
68
+ const by = /\bby\s+(m-[0-9a-z]{4,8})(?![0-9A-Za-z])/.exec(supersededPart)?.[1];
69
+ superseded = { date: sd, by };
70
+ }
71
+ const akaPart = tailParts.find((p) => /^aka\s*:/i.test(p));
72
+ const aliases = akaPart
73
+ ? akaPart
74
+ .replace(/^aka\s*:/i, "")
75
+ .split(",")
76
+ .map((a) => a.trim())
77
+ .filter(Boolean)
78
+ : [];
79
+ const unverified = tailParts.some((p) => /^unverified$/i.test(p));
80
+ // The `← sources` tail (allow plain "<-" as a fallback for hand edits).
81
+ const arrowSplit = rest.split(/\s*(?:←|<-)\s*/);
82
+ const text_ = (arrowSplit[0] ?? "").trim();
83
+ const sources = arrowSplit.length > 1
84
+ ? arrowSplit
85
+ .slice(1)
86
+ .join(",")
87
+ .split(",")
88
+ .map((s) => s.trim())
89
+ .filter(Boolean)
90
+ : [];
91
+ entries.push({
92
+ id,
93
+ date,
94
+ provenance: prov,
95
+ superseded,
96
+ aliases,
97
+ unverified,
98
+ text: text_,
99
+ sources,
100
+ archived,
101
+ raw: line.trim(),
102
+ });
103
+ }
104
+ return { entries, lineCount: lines.length, malformed };
105
+ }
106
+ export class IndexValidationError extends Error {
107
+ problems;
108
+ constructor(problems) {
109
+ super(`Index rewrite rejected:\n- ${problems.join("\n- ")}`);
110
+ this.name = "IndexValidationError";
111
+ this.problems = problems;
112
+ }
113
+ }
114
+ /**
115
+ * Validate a proposed index rewrite against the previous version. Returns the
116
+ * problem list (empty = valid). Rules:
117
+ * - hard line cap (`INDEX_LINE_HARD_CAP`);
118
+ * - no duplicate entry ids;
119
+ * - every previously ACTIVE id must still be present (active or archived) —
120
+ * supersede/expire is a MOVE to `## Archive`, never a silent delete;
121
+ * - previously ARCHIVED ids MAY be dropped: every rewrite snapshots the full
122
+ * prior index first, so archived history stays recoverable while the cap
123
+ * stays satisfiable (otherwise Archive would grow past the cap forever);
124
+ * - entry-shaped lines must parse.
125
+ */
126
+ export function validateIndexRewrite(prev, next) {
127
+ const problems = [];
128
+ const parsed = parseIndex(next);
129
+ if (parsed.lineCount > INDEX_LINE_HARD_CAP) {
130
+ problems.push(`index is ${parsed.lineCount} lines — the hard cap is ${INDEX_LINE_HARD_CAP} ` +
131
+ `(target ≤ ${INDEX_LINE_CAP}). Merge or archive entries instead of growing the index.`);
132
+ }
133
+ for (const line of parsed.malformed) {
134
+ problems.push(`malformed entry line (expected \`- [m-<4-8 lowercase letters/digits>] ` +
135
+ `(<YYYY-MM-DD>, <provenance>) <fact> ← <sources>\`): "${line}"`);
136
+ }
137
+ const seen = new Set();
138
+ for (const e of parsed.entries) {
139
+ if (seen.has(e.id))
140
+ problems.push(`duplicate entry id: ${e.id}`);
141
+ seen.add(e.id);
142
+ }
143
+ if (prev) {
144
+ const prevActive = parseIndex(prev)
145
+ .entries.filter((e) => !e.archived)
146
+ .map((e) => e.id);
147
+ const missing = prevActive.filter((id) => !seen.has(id));
148
+ if (missing.length > 0) {
149
+ problems.push(`active entries silently deleted: ${missing.join(", ")} — move superseded/expired ` +
150
+ `entries under "## Archive" instead of removing them.`);
151
+ }
152
+ }
153
+ return problems;
154
+ }
155
+ /** Mint a new entry id not already taken (6 hex chars, `m-` prefixed). */
156
+ export function mintMemoryId(taken) {
157
+ for (;;) {
158
+ const id = `m-${Math.floor(Math.random() * 0xffffff)
159
+ .toString(16)
160
+ .padStart(6, "0")}`;
161
+ if (!taken.has(id))
162
+ return id;
163
+ }
164
+ }
165
+ /** All `[m-…]` ids referenced in a blob of text (for usage tracking). */
166
+ export function memoryIdsIn(text) {
167
+ return Array.from(new Set(Array.from(text.matchAll(/\[(m-[0-9a-z]{4,8})\]/g), (m) => m[1])));
168
+ }
169
+ //# sourceMappingURL=index-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-file.js","sourceRoot":"","sources":["../src/index-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @intentface/latch-memory
3
+ *
4
+ * Pluggable agent memory for Latch: an always-loaded compiled index injected
5
+ * into the system prompt, an episodic store searched on demand, and background
6
+ * extraction/consolidation over a `MemoryStore` seam (Loredex-backed first).
7
+ */
8
+ export * from "./types.js";
9
+ export * from "./scope.js";
10
+ export * from "./schema.js";
11
+ export * from "./index-file.js";
12
+ export * from "./extraction-key.js";
13
+ export * from "./search-evidence.js";
14
+ export * from "./sanitize.js";
15
+ export * from "./provider.js";
16
+ export * from "./tools.js";
17
+ export * from "./store/loredex.js";
18
+ export * from "./store/memory.js";
19
+ export * from "./prompts/inject.js";
20
+ export * from "./prompts/extractor.js";
21
+ export * from "./prompts/consolidator.js";
22
+ export * from "./jobs/types.js";
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @intentface/latch-memory
3
+ *
4
+ * Pluggable agent memory for Latch: an always-loaded compiled index injected
5
+ * into the system prompt, an episodic store searched on demand, and background
6
+ * extraction/consolidation over a `MemoryStore` seam (Loredex-backed first).
7
+ */
8
+ export * from "./types.js";
9
+ export * from "./scope.js";
10
+ export * from "./schema.js";
11
+ export * from "./index-file.js";
12
+ export * from "./extraction-key.js";
13
+ export * from "./search-evidence.js";
14
+ export * from "./sanitize.js";
15
+ export * from "./provider.js";
16
+ export * from "./tools.js";
17
+ export * from "./store/loredex.js";
18
+ export * from "./store/memory.js";
19
+ export * from "./prompts/inject.js";
20
+ export * from "./prompts/extractor.js";
21
+ export * from "./prompts/consolidator.js";
22
+ export * from "./jobs/types.js";
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC"}