plugmem 0.8.0 → 0.9.0
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 +104 -46
- package/index.d.ts +121 -22
- package/index.js +2 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# plugmem
|
|
2
2
|
|
|
3
|
-
> ⚠️ Experimental. plugmem is mostly an AI-built experiment
|
|
3
|
+
> ⚠️ Experimental. plugmem is mostly an AI-built experiment, written with
|
|
4
4
|
> the help of a small local model (Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf) and various
|
|
5
5
|
> Claude models, in roughly equal measure. Expect non-professional design
|
|
6
6
|
> choices, rough edges, broken behavior, or mistakes. Use it at your own risk.
|
|
@@ -9,11 +9,11 @@ An embeddable bitemporal memory database for local-first applications and
|
|
|
9
9
|
agents, embedded in your Node process. It stores short facts and answers a
|
|
10
10
|
query with ranked facts and edges plus an optional bounded rendered block.
|
|
11
11
|
|
|
12
|
-
File-backed on disk, no server, no daemon.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
WebAssembly copy of the file in RAM and no 4 GiB
|
|
16
|
-
Bun.
|
|
12
|
+
File-backed on disk, no server, no daemon. The
|
|
13
|
+
[`plugmem-host`](https://docs.rs/plugmem-host/latest) engine is compiled to a
|
|
14
|
+
native addon through [napi-rs](https://napi.rs) and linked directly into the
|
|
15
|
+
process, so there is no WebAssembly copy of the file in RAM and no 4 GiB
|
|
16
|
+
ceiling. Runs on Node, Deno and Bun.
|
|
17
17
|
|
|
18
18
|
**Contents:** [Install](#install) · [Quick start](#quick-start) ·
|
|
19
19
|
[What it stores](#what-it-stores) · [Two clocks](#two-clocks) ·
|
|
@@ -47,11 +47,15 @@ const res = await db.recall({ query: "tokio", k: 5 });
|
|
|
47
47
|
console.log(res.rendered); // paste this into the prompt
|
|
48
48
|
// - [f0] user: the user prefers tokio (2026-08; active) #pref
|
|
49
49
|
|
|
50
|
+
const tags = await db.listTags({ prefix: "pre", limit: 64 });
|
|
51
|
+
console.log(tags.items); // [{ name: "pref", count: 1 }]
|
|
52
|
+
// await db.removeTag("pref"); // global: revises every current fact carrying it
|
|
53
|
+
|
|
50
54
|
db.close();
|
|
51
55
|
```
|
|
52
56
|
|
|
53
|
-
The query is `"tokio"`
|
|
54
|
-
|
|
57
|
+
The query is `"tokio"` rather than `"which runtime?"` because recall matches on
|
|
58
|
+
**words** when no embedder is configured, and "runtime"
|
|
55
59
|
appears nowhere in that fact, so the more natural question returns nothing.
|
|
56
60
|
Reach it through the graph instead with `entities: ["user"]`, or configure an
|
|
57
61
|
[embedder](#configuration-and-embeddings) and the meaning matches too. Only one
|
|
@@ -116,16 +120,16 @@ the second query has something to answer with.
|
|
|
116
120
|
|
|
117
121
|
`asOf` moves **both** clocks: a fact answers only if it was valid at that
|
|
118
122
|
instant *and* had already been recorded by then. The second half is the one
|
|
119
|
-
people trip over
|
|
120
|
-
because the memory
|
|
123
|
+
people trip over: an `asOf` earlier than a fact's `recordedAt` sees nothing,
|
|
124
|
+
because the memory had not recorded the fact yet. Answering with today's knowledge
|
|
121
125
|
would be the wrong answer to "what did I hold".
|
|
122
126
|
|
|
123
127
|
`validFrom` is the other half: a statement that became true before you heard of
|
|
124
128
|
it. Recording today that someone moved a week ago closes the previous interval a
|
|
125
|
-
week ago rather than now, so a query as of three days back finds **neither
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
week ago rather than now, so a query as of three days back finds **neither**.
|
|
130
|
+
The old fact had stopped being true, and the new one was not yet known. That
|
|
131
|
+
result follows directly from the two clocks; a single timestamp cannot express
|
|
132
|
+
it.
|
|
129
133
|
|
|
130
134
|
Two more queries over the same axes:
|
|
131
135
|
|
|
@@ -176,18 +180,30 @@ res.edges; // { src, rel, dst, provenance }[] — what the graph walked
|
|
|
176
180
|
res.truncated; // true if selection stopped at k or the budget with more left
|
|
177
181
|
```
|
|
178
182
|
|
|
179
|
-
`remember`
|
|
180
|
-
|
|
181
|
-
|
|
183
|
+
`remember` stores the new fact and returns its id **plus any live facts it may
|
|
184
|
+
duplicate or contradict**. If a preflight must not write, use
|
|
185
|
+
`rememberGuarded`: the database holds one write scope across its similarity
|
|
186
|
+
check and conditional insertion, so concurrent preflights cannot both pass.
|
|
182
187
|
|
|
183
188
|
```typescript
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
const decision = await db.rememberGuarded({
|
|
190
|
+
text: "the user prefers async-std",
|
|
191
|
+
entity: "user",
|
|
192
|
+
});
|
|
193
|
+
if (decision.status === "blocked") {
|
|
194
|
+
for (const s of decision.similar) {
|
|
195
|
+
// revise/forget an old fact, or use ordinary remember to keep both
|
|
196
|
+
console.log(s.id, s.score, s.reason);
|
|
197
|
+
}
|
|
188
198
|
}
|
|
189
199
|
```
|
|
190
200
|
|
|
201
|
+
`blocked` has no `outcome`: it allocated no id and changed neither indexes nor
|
|
202
|
+
journal. Ordinary `remember` is also a safe complete write; it simply never rejects
|
|
203
|
+
one. Do not use `recall` for this check. Recall returns ranked context and can
|
|
204
|
+
return a weak nearest vector; its fused score is not cosine similarity or a
|
|
205
|
+
conflict threshold.
|
|
206
|
+
|
|
191
207
|
## API
|
|
192
208
|
|
|
193
209
|
Every method wraps the identically-named verb of the Rust `Database`; this layer
|
|
@@ -198,9 +214,11 @@ only moves arguments and results across the boundary.
|
|
|
198
214
|
| Method | Does |
|
|
199
215
|
|---|---|
|
|
200
216
|
| `remember(args)` | store a fact; resolves with its id and similar facts |
|
|
217
|
+
| `rememberGuarded(args)` | check similarity and store only if clear, without a check/write race |
|
|
201
218
|
| `rememberMany(args[])` | store a batch: one embedding round-trip, one journal sync |
|
|
202
219
|
| `revise(id, args)` | close a fact and record its successor |
|
|
203
220
|
| `forget(id)` | tombstone a fact; resolves with whether it was live |
|
|
221
|
+
| `removeTag(tag)` | remove a tag from every current fact while preserving facts/history |
|
|
204
222
|
| `link(args)` | upsert a typed edge, optionally with `provenance` |
|
|
205
223
|
| `unlink(args)` | close the current edge; resolves with whether one was open |
|
|
206
224
|
|
|
@@ -211,6 +229,7 @@ only moves arguments and results across the boundary.
|
|
|
211
229
|
| `recall(args?)` | ranked, fused, token-budgeted result (async) |
|
|
212
230
|
| `get(id)` | one fact's full card, or `null` (sync) |
|
|
213
231
|
| `tagsOf(id)` | that fact's tags (sync) |
|
|
232
|
+
| `listTags(options?)` | bounded lexical page of current tags and counts (async) |
|
|
214
233
|
| `stats()` | engine size counters (sync) |
|
|
215
234
|
| `path()` | the file this handle resolved to (sync) |
|
|
216
235
|
| `export()` | every open fact as one array (async, unbounded — see below) |
|
|
@@ -223,11 +242,18 @@ only moves arguments and results across the boundary.
|
|
|
223
242
|
| Method | Does |
|
|
224
243
|
|---|---|
|
|
225
244
|
| `maintain(mode?)` | `"auto"` (default), `"compact"`, `"reindex-text"`, `"optimize-vectors"`, `"full"`. No mode ever drops a revision or an edge version |
|
|
245
|
+
| `reembed(batchSize?)` | explicitly recompute every retained vector with the configured model and publish atomically; never invoked by `maintain('auto')` |
|
|
226
246
|
| `checkpoint()` | flush the journal into a fresh snapshot |
|
|
227
247
|
| `verify()` | full content-integrity sweep; rejects on the first inconsistency |
|
|
228
248
|
| `scrub(options?)` | start a resumable byte-level check of the snapshot |
|
|
229
249
|
| `recover(src, dst, options?)` | module function: salvage a damaged file into a clean copy |
|
|
230
250
|
|
|
251
|
+
The snapshot stores the model's readable vector-space identity, not only its
|
|
252
|
+
dimension. A changed model makes ordinary automatic embedding reject instead
|
|
253
|
+
of mixing incompatible vectors. `reembed` is the deliberate transition; it
|
|
254
|
+
runs on a libuv worker, leaves the JavaScript event loop responsive, keeps reads
|
|
255
|
+
live and makes concurrent writes reject with `PLUGMEM_BUSY`.
|
|
256
|
+
|
|
231
257
|
**Read-only handles** (`{ readOnly: true }`) observe another process's writer
|
|
232
258
|
over a published snapshot. The read verbs answer, the write verbs throw, and two
|
|
233
259
|
more appear: `generation()` (the pinned snapshot number) and `refresh()` (adopt
|
|
@@ -408,6 +434,7 @@ half_life_days = 30 # and treat anything older than a month as stale
|
|
|
408
434
|
enabled = true # false keeps settings but makes no embedder calls
|
|
409
435
|
url = "http://localhost:11434/v1/embeddings"
|
|
410
436
|
model = "nomic-embed-text"
|
|
437
|
+
space_id = "nomic-embed-text@v1" # optional; defaults to model
|
|
411
438
|
api_key_env = "OPENAI_API_KEY" # env var holding the bearer token
|
|
412
439
|
|
|
413
440
|
[maintenance]
|
|
@@ -440,7 +467,9 @@ embedder, its dimension governs and `dim` must agree.
|
|
|
440
467
|
The host uses one `OpenAiCompatEmbedder` implementation for OpenAI, Ollama,
|
|
441
468
|
LM Studio, vLLM and other OpenAI-compatible servers. `url` is the complete
|
|
442
469
|
embeddings endpoint exactly as provided (nothing is appended), and `model` is
|
|
443
|
-
the model name understood by that server.
|
|
470
|
+
the model name understood by that server. `space_id` optionally identifies the
|
|
471
|
+
exact semantic space and defaults to `model`; it is never discovered over the
|
|
472
|
+
network. Set `enabled = false` to keep the
|
|
444
473
|
settings without creating or calling the embedder; `$PLUGMEM_EMBEDDER_ENABLED`
|
|
445
474
|
overrides it with `true` or `false`.
|
|
446
475
|
|
|
@@ -459,14 +488,15 @@ embedder's HTTP round trip or an fsync would freeze every timer, socket and
|
|
|
459
488
|
callback in the process. Anything that can do that runs on a libuv worker and
|
|
460
489
|
returns a promise instead.
|
|
461
490
|
|
|
462
|
-
Promises: `Plugmem.open`, `remember`, `rememberMany`, `revise`, `recall`,
|
|
463
|
-
`forget`, `link`, `unlink`, `export`, `exportPage`, `verify`, `maintain`,
|
|
464
|
-
`checkpoint`,
|
|
465
|
-
`
|
|
491
|
+
Promises: `Plugmem.open`, `remember`, `rememberGuarded`, `rememberMany`, `revise`, `recall`,
|
|
492
|
+
`forget`, `removeTag`, `listTags`, `link`, `unlink`, `export`, `exportPage`, `verify`, `maintain`,
|
|
493
|
+
`checkpoint`, every database verb on `WorkspaceMemory`, and every registry
|
|
494
|
+
verb on `Workspace`.
|
|
466
495
|
|
|
467
|
-
Synchronous: `path`, `get`, `stats`, `tagsOf`, `generation`,
|
|
468
|
-
|
|
469
|
-
|
|
496
|
+
Synchronous: the direct handle's `path`, `get`, `stats`, `tagsOf`, `generation`,
|
|
497
|
+
`refresh`, `close`; and `Workspace.memory`, `release`, `closeIdle`, `openCount`,
|
|
498
|
+
`close`. Creating a logical reference touches no file. Its own database verbs
|
|
499
|
+
are promises because acquiring a cold lease may open and replay a file.
|
|
470
500
|
|
|
471
501
|
Arguments are still checked on your thread: a refused one **throws** at the call
|
|
472
502
|
site rather than rejecting later, so a mistake in your code and a failure in the
|
|
@@ -542,13 +572,13 @@ import { Workspace, type DbEntry } from "plugmem";
|
|
|
542
572
|
|
|
543
573
|
const ws = new Workspace("/srv/memories");
|
|
544
574
|
|
|
545
|
-
//
|
|
546
|
-
// a
|
|
547
|
-
const chat =
|
|
575
|
+
// This is only a name plus a weak reference to `ws`: no file is opened and no
|
|
576
|
+
// writer lock is held until a verb runs. A first write creates the memory.
|
|
577
|
+
const chat = ws.memory("chat-42");
|
|
548
578
|
await chat.remember({ text: "prefers tokio" });
|
|
549
579
|
|
|
550
580
|
// Another name is another memory. They cannot see each other.
|
|
551
|
-
const other =
|
|
581
|
+
const other = ws.memory("chat-99");
|
|
552
582
|
(await other.recall({ query: "tokio" })).facts.length; // 0
|
|
553
583
|
```
|
|
554
584
|
|
|
@@ -568,10 +598,10 @@ const byOwner: DbEntry[] = await ws.find("ann"); // → the same memo
|
|
|
568
598
|
```
|
|
569
599
|
|
|
570
600
|
A name is `[a-z0-9][a-z0-9_-]*` and **cannot express a path**, so it resolves to
|
|
571
|
-
exactly one named database inside the directory — traversal is not filtered out,
|
|
572
|
-
unconstructible. `
|
|
573
|
-
|
|
574
|
-
an empty result.
|
|
601
|
+
exactly one named database inside the directory — traversal is not filtered out,
|
|
602
|
+
it is unconstructible. `memory(name)` itself creates nothing. A write verb creates
|
|
603
|
+
an unused name; a read verb refuses it, so a typo is diagnosed rather than
|
|
604
|
+
answered with an empty result.
|
|
575
605
|
|
|
576
606
|
**Who may reach which memory is not this package's job.** The name comes from
|
|
577
607
|
your code, so the policy belongs there.
|
|
@@ -580,7 +610,8 @@ your code, so the policy belongs there.
|
|
|
580
610
|
|
|
581
611
|
| Method | Does |
|
|
582
612
|
|---|---|
|
|
583
|
-
| `
|
|
613
|
+
| `memory(name)` | return a lock-free logical `WorkspaceMemory` reference |
|
|
614
|
+
| `release(name)` | evict one inactive pooled handle; references remain valid |
|
|
584
615
|
| `list()` | every memory in the directory, from the filesystem — including undescribed ones |
|
|
585
616
|
| `entries()` | every described memory, from the registry |
|
|
586
617
|
| `find(query, k?)` | memories whose description or owner best matches |
|
|
@@ -592,20 +623,47 @@ your code, so the policy belongs there.
|
|
|
592
623
|
| `openCount()` | how many are open right now (sync) |
|
|
593
624
|
| `close()` | close every pooled memory and the registry |
|
|
594
625
|
|
|
595
|
-
`closeIdle()` matters more than it looks.
|
|
626
|
+
`closeIdle()` matters more than it looks. A pooled database holds its file's
|
|
596
627
|
exclusive lock, so a long-running process that never lets go makes its memories
|
|
597
628
|
unreachable from anything else on the machine. Call it on a timer — that is what
|
|
598
|
-
the idle timeout is for, liveness rather than memory. The pool
|
|
599
|
-
|
|
600
|
-
room
|
|
629
|
+
the idle timeout is for, liveness rather than memory. The pool is a hard bound on
|
|
630
|
+
open databases (`maxOpen`, default 16): an inactive least-recently-used entry is
|
|
631
|
+
closed to make room. If every slot belongs to an active verb, a different memory
|
|
632
|
+
gets `PLUGMEM_BUSY` immediately instead of waiting or opening a hidden extra
|
|
633
|
+
handle:
|
|
601
634
|
|
|
602
635
|
```ts
|
|
603
636
|
const ws = new Workspace("/srv/memories", { maxOpen: 16, idleTimeoutMs: 60_000 });
|
|
604
637
|
setInterval(() => ws.closeIdle(), 30_000);
|
|
605
638
|
```
|
|
606
639
|
|
|
607
|
-
|
|
608
|
-
|
|
640
|
+
Each `WorkspaceMemory` verb takes a scoped lease. While it runs, `release`,
|
|
641
|
+
`closeIdle` and LRU eviction cannot take that entry; after it returns, the entry
|
|
642
|
+
is eligible immediately. `ws.close()` invalidates every logical reference.
|
|
643
|
+
Garbage collection of a `WorkspaceMemory` neither opens nor closes anything.
|
|
644
|
+
|
|
645
|
+
This lifecycle applies only to workspaces. A direct `Plugmem.open(path)` still
|
|
646
|
+
returns an explicitly owned native handle, and `close()` remains how its writer
|
|
647
|
+
lock is released.
|
|
648
|
+
|
|
649
|
+
### Migration from the handle-returning workspace API
|
|
650
|
+
|
|
651
|
+
This is a breaking ownership change:
|
|
652
|
+
|
|
653
|
+
```ts
|
|
654
|
+
// before: a second native owner whose lifetime depended on JavaScript GC
|
|
655
|
+
const memory = await ws.open("chat-42");
|
|
656
|
+
memory.close();
|
|
657
|
+
|
|
658
|
+
// now: a stable logical reference; each verb owns one scoped lease
|
|
659
|
+
const memory = ws.memory("chat-42");
|
|
660
|
+
ws.release("chat-42"); // optional: release an inactive pooled lock now
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
There is no `WorkspaceMemory.close()`: it owns nothing to close. Database reads
|
|
664
|
+
such as `get`, `stats` and `tagsOf` are promises on this class because a cold
|
|
665
|
+
call may have to reopen and replay the file. The same methods on a direct
|
|
666
|
+
`Plugmem` remain synchronous.
|
|
609
667
|
|
|
610
668
|
`verify()` reports and never repairs, because a workspace is a directory a
|
|
611
669
|
person can edit, and guessing at their intent is how a consistency check loses
|
|
@@ -627,7 +685,7 @@ search. For those, use a dedicated system — [Qdrant](https://qdrant.tech),
|
|
|
627
685
|
|
|
628
686
|
## Other ways in
|
|
629
687
|
|
|
630
|
-
|
|
688
|
+
plugmem also ships interfaces for Rust, Python, agents and the terminal.
|
|
631
689
|
|
|
632
690
|
| You are | Use |
|
|
633
691
|
|---|---|
|
|
@@ -638,7 +696,7 @@ The same engine ships five ways. This package is the Node one.
|
|
|
638
696
|
| a person at a terminal | [`plugmem-cli`](https://docs.rs/plugmem-cli/latest) |
|
|
639
697
|
|
|
640
698
|
**Working with an LLM agent?** There is a companion
|
|
641
|
-
[skill](https://github.com/m62624/plugmem/blob/main/
|
|
699
|
+
[skill](https://github.com/m62624/plugmem/blob/main/skills/plugmem/SKILL.md) describing
|
|
642
700
|
the remember/recall loop, the contradiction workflow and the verbs. This package
|
|
643
701
|
ships it: `skill()` returns the text and `skillVersion()` the version it was
|
|
644
702
|
written against.
|
package/index.d.ts
CHANGED
|
@@ -133,6 +133,15 @@ export interface LinkArgs {
|
|
|
133
133
|
*/
|
|
134
134
|
provenance?: number
|
|
135
135
|
}
|
|
136
|
+
/** Bounded tag-catalog options. Omitted fields select the first 64 entries. */
|
|
137
|
+
export interface TagListOptions {
|
|
138
|
+
/** Exact, case-sensitive prefix. */
|
|
139
|
+
prefix?: string
|
|
140
|
+
/** Opaque cursor returned by the previous page. */
|
|
141
|
+
cursor?: string
|
|
142
|
+
/** Page size (maximum 256). */
|
|
143
|
+
limit?: number
|
|
144
|
+
}
|
|
136
145
|
/** Options for [`recover`]. */
|
|
137
146
|
export interface RecoverOptions {
|
|
138
147
|
/**
|
|
@@ -201,6 +210,15 @@ export interface RememberOutcome {
|
|
|
201
210
|
*/
|
|
202
211
|
similar: Array<Similar>
|
|
203
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Result of `rememberGuarded`. Blocked results have no `outcome` because no
|
|
215
|
+
* fact id was allocated.
|
|
216
|
+
*/
|
|
217
|
+
export interface GuardedRememberOutcome {
|
|
218
|
+
status: 'stored' | 'blocked'
|
|
219
|
+
outcome?: RememberOutcome
|
|
220
|
+
similar: Array<Similar>
|
|
221
|
+
}
|
|
204
222
|
/** One recalled fact. */
|
|
205
223
|
export interface RecalledFact {
|
|
206
224
|
/** The fact id. */
|
|
@@ -373,6 +391,31 @@ export interface ExportPage {
|
|
|
373
391
|
/** Pass this opaque cursor to the next call; absent when the scan is done. */
|
|
374
392
|
nextCursor?: number
|
|
375
393
|
}
|
|
394
|
+
/** One active tag and the number of current facts carrying it. */
|
|
395
|
+
export interface TagSummary {
|
|
396
|
+
name: string
|
|
397
|
+
count: number
|
|
398
|
+
}
|
|
399
|
+
/** One bounded, stable page of current tags. */
|
|
400
|
+
export interface TagPage {
|
|
401
|
+
items: Array<TagSummary>
|
|
402
|
+
nextCursor?: string
|
|
403
|
+
}
|
|
404
|
+
/** Result of removing one tag from all current facts. */
|
|
405
|
+
export interface RemoveTagReport {
|
|
406
|
+
affected: number
|
|
407
|
+
}
|
|
408
|
+
/** The report of an explicit complete vector-axis replacement. */
|
|
409
|
+
export interface ReembedReport {
|
|
410
|
+
previousSpace?: string
|
|
411
|
+
newSpace: string
|
|
412
|
+
previousDim: number
|
|
413
|
+
newDim: number
|
|
414
|
+
embedded: number
|
|
415
|
+
tombstonesSkipped: number
|
|
416
|
+
vectorBytes: number
|
|
417
|
+
hnswIndexed: number
|
|
418
|
+
}
|
|
376
419
|
/** The report of a `maintain` pass. */
|
|
377
420
|
export interface MaintainReport {
|
|
378
421
|
/** Tombstoned facts physically removed by this pass. */
|
|
@@ -448,7 +491,7 @@ export const enum MaintainMode {
|
|
|
448
491
|
* round-trip it would be the wrong direction of dependency.
|
|
449
492
|
*/
|
|
450
493
|
export interface DbEntry {
|
|
451
|
-
/** The memory's name — its identity, and what `Workspace.
|
|
494
|
+
/** The memory's name — its identity, and what `Workspace.memory` takes. */
|
|
452
495
|
db: string
|
|
453
496
|
/** What it is for. */
|
|
454
497
|
description: string
|
|
@@ -636,6 +679,14 @@ export declare class Plugmem {
|
|
|
636
679
|
* @throws synchronously in read-only mode.
|
|
637
680
|
*/
|
|
638
681
|
remember(args: RememberArgs): Promise<RememberOutcome>
|
|
682
|
+
/**
|
|
683
|
+
* Stores only when the same bounded Jaccard/cosine detector used by
|
|
684
|
+
* `remember().similar` finds no candidate. No other write can slip between
|
|
685
|
+
* the check and possible write; a blocked result performs no mutation.
|
|
686
|
+
* Runs on a libuv worker because
|
|
687
|
+
* automatic embedding and durable writes are blocking work.
|
|
688
|
+
*/
|
|
689
|
+
rememberGuarded(args: RememberArgs): Promise<GuardedRememberOutcome>
|
|
639
690
|
/**
|
|
640
691
|
* Stores a batch of facts and resolves with one outcome per input.
|
|
641
692
|
*
|
|
@@ -755,6 +806,17 @@ export declare class Plugmem {
|
|
|
755
806
|
scrub(options?: ScrubOptions | undefined | null): Promise<Scrub>
|
|
756
807
|
/** One fact's tags, or an empty array for an unknown or tombstoned id. */
|
|
757
808
|
tagsOf(id: number): Array<string>
|
|
809
|
+
/**
|
|
810
|
+
* One bounded page of current tags. Runs on a libuv worker so neither a
|
|
811
|
+
* mapped-page fault nor host locking can pause the JavaScript event loop.
|
|
812
|
+
*/
|
|
813
|
+
listTags(options?: TagListOptions | undefined | null): Promise<TagPage>
|
|
814
|
+
/**
|
|
815
|
+
* Removes a tag from every current fact by creating successor revisions.
|
|
816
|
+
* The bulk write and journal sync run on a libuv worker.
|
|
817
|
+
* @throws synchronously in read-only mode.
|
|
818
|
+
*/
|
|
819
|
+
removeTag(tag: string): Promise<RemoveTagReport>
|
|
758
820
|
/**
|
|
759
821
|
* Content-integrity check; rejects on the first inconsistency found.
|
|
760
822
|
*
|
|
@@ -774,6 +836,16 @@ export declare class Plugmem {
|
|
|
774
836
|
* the only mode that reclaims edge-history page slack.
|
|
775
837
|
*/
|
|
776
838
|
maintain(mode?: 'auto' | 'compact' | 'reindex-text' | 'optimize-vectors' | 'full'): Promise<MaintainReport>
|
|
839
|
+
/**
|
|
840
|
+
* Explicitly recomputes every retained fact with the configured embedder
|
|
841
|
+
* and atomically replaces the complete vector axis. This is never invoked
|
|
842
|
+
* by `maintain('auto')`.
|
|
843
|
+
*
|
|
844
|
+
* **Async**: provider and snapshot work runs on a libuv worker; the event
|
|
845
|
+
* loop remains responsive. Reads continue while it runs and writes reject
|
|
846
|
+
* with `PLUGMEM_BUSY` instead of waiting.
|
|
847
|
+
*/
|
|
848
|
+
reembed(batchSize?: number | undefined | null): Promise<ReembedReport>
|
|
777
849
|
/**
|
|
778
850
|
* Flushes the journal into a fresh snapshot. **Async** (returns a `Promise`):
|
|
779
851
|
* it writes and fsyncs a snapshot file, so it runs on a libuv worker thread.
|
|
@@ -838,9 +910,38 @@ export declare class Scrub {
|
|
|
838
910
|
active(): boolean
|
|
839
911
|
}
|
|
840
912
|
/**
|
|
841
|
-
* A
|
|
842
|
-
*
|
|
913
|
+
* A logical reference to one named memory.
|
|
914
|
+
*
|
|
915
|
+
* It owns no open database and no file lock. Each verb obtains a scoped
|
|
916
|
+
* workspace lease on a libuv worker, and dropping this JavaScript object has
|
|
917
|
+
* no resource-management meaning. The workspace is the owner; closing it
|
|
918
|
+
* invalidates every reference.
|
|
843
919
|
*/
|
|
920
|
+
export declare class WorkspaceMemory {
|
|
921
|
+
/** The stable workspace name this reference addresses. */
|
|
922
|
+
name(): string
|
|
923
|
+
remember(args: RememberArgs): Promise<RememberOutcome>
|
|
924
|
+
rememberGuarded(args: RememberArgs): Promise<GuardedRememberOutcome>
|
|
925
|
+
rememberMany(args: Array<RememberArgs>): Promise<RememberOutcome[]>
|
|
926
|
+
revise(id: number, args: RememberArgs): Promise<RememberOutcome>
|
|
927
|
+
recall(args?: RecallArgs | undefined | null): Promise<RecallResult>
|
|
928
|
+
forget(id: number): Promise<boolean>
|
|
929
|
+
link(args: LinkArgs): Promise<void>
|
|
930
|
+
unlink(args: LinkArgs): Promise<boolean>
|
|
931
|
+
get(id: number): Promise<FactSnapshot | null>
|
|
932
|
+
stats(): Promise<Stats>
|
|
933
|
+
export(): Promise<ExportedFact[]>
|
|
934
|
+
exportPage(cursor?: number | undefined | null): Promise<ExportPage>
|
|
935
|
+
exportEdges(onBatch: (edges: ExportedEdge[]) => void): Promise<number>
|
|
936
|
+
scrub(options?: ScrubOptions | undefined | null): Promise<Scrub>
|
|
937
|
+
tagsOf(id: number): Promise<string[]>
|
|
938
|
+
listTags(options?: TagListOptions | undefined | null): Promise<TagPage>
|
|
939
|
+
removeTag(tag: string): Promise<RemoveTagReport>
|
|
940
|
+
verify(): Promise<void>
|
|
941
|
+
maintain(mode?: 'auto' | 'compact' | 'reindex-text' | 'optimize-vectors' | 'full'): Promise<MaintainReport>
|
|
942
|
+
reembed(batchSize?: number | undefined | null): Promise<ReembedReport>
|
|
943
|
+
checkpoint(): Promise<void>
|
|
944
|
+
}
|
|
844
945
|
export declare class Workspace {
|
|
845
946
|
/**
|
|
846
947
|
* Opens the workspace rooted at `root`. Creates nothing: the directories
|
|
@@ -850,22 +951,22 @@ export declare class Workspace {
|
|
|
850
951
|
*/
|
|
851
952
|
constructor(root: string, options?: WorkspaceOptions | undefined | null)
|
|
852
953
|
/**
|
|
853
|
-
*
|
|
854
|
-
* class, and the same verbs, as a memory opened by path.
|
|
954
|
+
* Returns a logical reference to the memory named `db`.
|
|
855
955
|
*
|
|
856
|
-
*
|
|
857
|
-
*
|
|
858
|
-
*
|
|
859
|
-
*
|
|
860
|
-
|
|
956
|
+
* This does not open a file, acquire a lock or create the memory. Read
|
|
957
|
+
* verbs require it to exist; write verbs create it on first use. The
|
|
958
|
+
* returned object stays valid across pool eviction and `release()` and
|
|
959
|
+
* transparently reopens the memory on its next verb.
|
|
960
|
+
*/
|
|
961
|
+
memory(db: string): WorkspaceMemory
|
|
962
|
+
/**
|
|
963
|
+
* Evicts one inactive memory from the pool and releases its file lock.
|
|
861
964
|
*
|
|
862
|
-
*
|
|
863
|
-
*
|
|
864
|
-
*
|
|
865
|
-
* snapshot, and making room in the pool closes another memory — file work
|
|
866
|
-
* that the one thread running JavaScript must not be holding.
|
|
965
|
+
* Logical references remain valid. A later verb reopens the memory. If a
|
|
966
|
+
* verb is currently using it, this throws a typed `BUSY` error instead of
|
|
967
|
+
* waiting.
|
|
867
968
|
*/
|
|
868
|
-
|
|
969
|
+
release(db: string): boolean
|
|
869
970
|
/**
|
|
870
971
|
* Every memory in the directory, sorted by name.
|
|
871
972
|
*
|
|
@@ -934,12 +1035,10 @@ export declare class Workspace {
|
|
|
934
1035
|
/** How many memories are open right now. */
|
|
935
1036
|
openCount(): number
|
|
936
1037
|
/**
|
|
937
|
-
*
|
|
938
|
-
* and closes the workspace.
|
|
939
|
-
*
|
|
940
|
-
*
|
|
941
|
-
* own handle and holds its own lock until it is closed or garbage
|
|
942
|
-
* collected.
|
|
1038
|
+
* Invalidates every [`WorkspaceMemory`] reference, closes inactive pooled
|
|
1039
|
+
* memories and the registry, and closes the workspace. A verb already in
|
|
1040
|
+
* flight finishes with its scoped handle; its lock is released when that
|
|
1041
|
+
* verb returns. Every later call throws `CLOSED`.
|
|
943
1042
|
*/
|
|
944
1043
|
close(): void
|
|
945
1044
|
}
|
package/index.js
CHANGED
|
@@ -310,12 +310,13 @@ if (!nativeBinding) {
|
|
|
310
310
|
throw new Error(`Failed to load native binding`)
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
const { Plugmem, recover, Scrub, MaintainMode, Workspace, version, about, settingsHelp, skill, skillFull, skillVersion } = nativeBinding
|
|
313
|
+
const { Plugmem, recover, Scrub, MaintainMode, WorkspaceMemory, Workspace, version, about, settingsHelp, skill, skillFull, skillVersion } = nativeBinding
|
|
314
314
|
|
|
315
315
|
module.exports.Plugmem = Plugmem
|
|
316
316
|
module.exports.recover = recover
|
|
317
317
|
module.exports.Scrub = Scrub
|
|
318
318
|
module.exports.MaintainMode = MaintainMode
|
|
319
|
+
module.exports.WorkspaceMemory = WorkspaceMemory
|
|
319
320
|
module.exports.Workspace = Workspace
|
|
320
321
|
module.exports.version = version
|
|
321
322
|
module.exports.about = about
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plugmem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Native Node.js addon for plugmem: an embedded bitemporal memory and retrieval engine for local-first applications and agents (remember / recall / revise / forget over one local database).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,11 +43,11 @@
|
|
|
43
43
|
"typecheck": "tsc -p tsconfig.json"
|
|
44
44
|
},
|
|
45
45
|
"optionalDependencies": {
|
|
46
|
-
"plugmem-linux-x64-gnu": "0.
|
|
47
|
-
"plugmem-linux-arm64-gnu": "0.
|
|
48
|
-
"plugmem-darwin-x64": "0.
|
|
49
|
-
"plugmem-darwin-arm64": "0.
|
|
50
|
-
"plugmem-win32-x64-msvc": "0.
|
|
51
|
-
"plugmem-win32-arm64-msvc": "0.
|
|
46
|
+
"plugmem-linux-x64-gnu": "0.9.0",
|
|
47
|
+
"plugmem-linux-arm64-gnu": "0.9.0",
|
|
48
|
+
"plugmem-darwin-x64": "0.9.0",
|
|
49
|
+
"plugmem-darwin-arm64": "0.9.0",
|
|
50
|
+
"plugmem-win32-x64-msvc": "0.9.0",
|
|
51
|
+
"plugmem-win32-arm64-msvc": "0.9.0"
|
|
52
52
|
}
|
|
53
53
|
}
|