dsh-loci 0.0.0-stage → 2.0.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/CHANGELOG.md +303 -0
- package/LICENSE +21 -0
- package/NOTICE +31 -0
- package/README.md +143 -2
- package/README.zh.md +111 -0
- package/client.js +1369 -0
- package/cordis.patch.yml +10 -0
- package/docs/ACCEPTANCE.md +279 -0
- package/docs/AUDIT-1.1.md +173 -0
- package/docs/COMPATIBILITY.md +81 -0
- package/docs/DESIGN.md +311 -0
- package/docs/OPERATIONS.md +199 -0
- package/docs/PRIVACY.md +93 -0
- package/docs/RESEARCH.md +374 -0
- package/docs/SECURITY.md +76 -0
- package/icon.svg +6 -0
- package/index.js +13 -0
- package/locale/en.json +12 -0
- package/locale/zh.json +12 -0
- package/package.json +88 -4
- package/scripts/cli.mjs +331 -0
- package/src/host/api/routes.js +353 -0
- package/src/host/capture/capture.js +417 -0
- package/src/host/commands.js +115 -0
- package/src/host/config.js +221 -0
- package/src/host/doctor.js +164 -0
- package/src/host/extract/extractor.js +258 -0
- package/src/host/index.js +290 -0
- package/src/host/inject/index-section.js +252 -0
- package/src/host/inject/sanitize.js +304 -0
- package/src/host/paths.js +130 -0
- package/src/host/scope.js +147 -0
- package/src/host/store/activity.js +290 -0
- package/src/host/store/audit.js +208 -0
- package/src/host/store/db.js +185 -0
- package/src/host/store/duplicates.js +140 -0
- package/src/host/store/embedding.js +180 -0
- package/src/host/store/export.js +246 -0
- package/src/host/store/ids.js +58 -0
- package/src/host/store/importers.js +259 -0
- package/src/host/store/migrate.js +156 -0
- package/src/host/store/schema.js +209 -0
- package/src/host/store/search.js +519 -0
- package/src/host/store/store.js +1548 -0
- package/src/host/store/tokenize.js +209 -0
- package/src/host/tools/tools.js +548 -0
- package/src/host/update.js +163 -0
- package/src/mcp/server.js +261 -0
- package/src/mcp/store-facade.js +43 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the project follows
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html) from 1.0.0.
|
|
6
|
+
|
|
7
|
+
## [2.0.0] — 2026-11-14
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`loci doctor` now looks for near-duplicates.** Two entries that say the same
|
|
12
|
+
thing split the ranking, each take an index line, and force a correction to be
|
|
13
|
+
applied twice. The check reports groups with their ids and a similarity score, and
|
|
14
|
+
leaves the merge to a person (the store can already do it: `loci_write op:'merge'`).
|
|
15
|
+
|
|
16
|
+
The metric is `max(Jaccard, containment)` over character bigrams. Jaccard alone
|
|
17
|
+
scores a restated Chinese title at 0.55 — under any useful threshold — because the
|
|
18
|
+
two titles share most of the *shorter* one rather than most of the union. The scan
|
|
19
|
+
is bounded (verified heads only, ≤2000 entries, candidate pairs from shared tags
|
|
20
|
+
and title windows, report capped at 5 groups).
|
|
21
|
+
|
|
22
|
+
- `test/unit/duplicates.test.mjs`: the metric, the verified-only rule, the clean
|
|
23
|
+
store, the caps, and the doctor row.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- `publishConfig.access: "public"` is set in the manifest, so a direct `npm publish`
|
|
28
|
+
cannot accidentally create a private package.
|
|
29
|
+
|
|
30
|
+
### Note
|
|
31
|
+
|
|
32
|
+
This is the last item from the audit's "ten improvements" list: rows 1–3 and 5–10
|
|
33
|
+
are done, and row 2's remaining gap (duplicate detection) closed here. The version
|
|
34
|
+
jumps to 2.0.0 because the feature set that list described is complete, not because
|
|
35
|
+
anything in the on-disk format broke — schemas and the tool surface are unchanged.
|
|
36
|
+
|
|
37
|
+
## [1.9.0] — 2026-11-14
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- **Candidate-only background extraction** (`extractMode: 'on'`, **off by default**).
|
|
42
|
+
The deterministic capture layer only records statements that announce themselves
|
|
43
|
+
("always", "never", "we agreed"). A preference stated in passing — "honestly I'd
|
|
44
|
+
rather see the diff first" — carries no such marker and was lost. With the feature
|
|
45
|
+
on, the recent conversation is sent to the configured model at turn end and the
|
|
46
|
+
answers land as **candidates** for review.
|
|
47
|
+
|
|
48
|
+
Four rules keep it from becoming the feature a user turns off:
|
|
49
|
+
- off unless `extractMode: 'on'`, and inert when the profile has no `llm` route
|
|
50
|
+
(reports `no-model`, writes nothing);
|
|
51
|
+
- candidates only — an extracted preference can never reach the prompt by itself;
|
|
52
|
+
- budgeted: `extractMaxCallsPerHour` (6), `extractMaxCandidates` (3),
|
|
53
|
+
`extractTimeoutMs` (20 s), and a 12 KB input cap. Exhausting the budget is
|
|
54
|
+
silent, not an error;
|
|
55
|
+
- it can always fail: timeout, provider error, unparseable answer or an invented
|
|
56
|
+
quote all return `{written: 0, reason}` on its own timer, never touching the
|
|
57
|
+
turn. A quote that is not in the transcript is dropped before storage.
|
|
58
|
+
|
|
59
|
+
- `test/unit/extractor.test.mjs` covers the switch, the missing route, the budget,
|
|
60
|
+
the timeout, invented quotes and the unusable-answer paths.
|
|
61
|
+
|
|
62
|
+
### Unverified
|
|
63
|
+
|
|
64
|
+
- The `ctx.llm` adapter is written against the Host's `llm.stream(request)` call and
|
|
65
|
+
collects text from whatever frame shape the provider emits. **It has not been run
|
|
66
|
+
against a live provider**; the unit tests use a fake completer and a fake stream.
|
|
67
|
+
If a provider's chunk shape is not one of the recognised forms, the extractor
|
|
68
|
+
records `model-error` and stays out of the way — it does not write junk.
|
|
69
|
+
|
|
70
|
+
## [1.8.0] — 2026-11-14
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
|
|
74
|
+
- **Import bridges** for the memory plugins a user is likely leaving
|
|
75
|
+
(`dsh-loci import --from auto-memory|claude`, `POST /api/loci/import-peer`,
|
|
76
|
+
`--dry-run` to preview):
|
|
77
|
+
- **dsh-auto-memory**: reads one Markdown file per memory with its front matter
|
|
78
|
+
(`name/title/type/disabled/evidence`). `_user/` becomes global scope, each
|
|
79
|
+
`--<project>--/` directory keeps its own scope key, `type: feedback` maps to
|
|
80
|
+
`procedural`, and disabled or already-superseded entries are skipped.
|
|
81
|
+
- **Claude Code memory**: reads `MEMORY.md`'s links plus every topic file that
|
|
82
|
+
is not in the index, so a hand-added file is not silently lost.
|
|
83
|
+
- everything lands as a **candidate**: another plugin's trust level is not
|
|
84
|
+
evidence here, and the source file is recorded as `sourceRef`.
|
|
85
|
+
- format details were taken from the producer's own serializer, not guessed;
|
|
86
|
+
`@tr1v3r/dsh-ltm` is deliberately not read (private SQLite schema) and the
|
|
87
|
+
operations guide says why.
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- A **byte-order mark** in front matter silently disabled parsing: the whole file
|
|
92
|
+
became body text and the entry imported with a filename-derived title. A BOM is
|
|
93
|
+
now stripped before the fence is matched, with a regression test.
|
|
94
|
+
|
|
95
|
+
## [1.7.0] — 2026-11-14
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- **`sourceRef`**: a fact can cite where it lives (`src/host/store.js:120`).
|
|
100
|
+
- accepted by `loci_write`, stored with the evidence (the quote and the citation
|
|
101
|
+
are both provenance), returned by `loci_get` as a `source:` line, shown under
|
|
102
|
+
**Source** in the panel detail, and written to the export front matter so a
|
|
103
|
+
round trip keeps it;
|
|
104
|
+
- flattened and capped at 160 characters like every other free-text field;
|
|
105
|
+
- a citation that trips the secret scanner is dropped rather than stored;
|
|
106
|
+
- it never raises trust: a model-written entry with a citation is still a
|
|
107
|
+
candidate, because knowing where something is written down does not make it
|
|
108
|
+
true.
|
|
109
|
+
|
|
110
|
+
## [1.6.0] — 2026-11-14
|
|
111
|
+
|
|
112
|
+
### Added
|
|
113
|
+
|
|
114
|
+
- **`merge`** (`loci_write` with `op:'merge'`, `store.merge()`, `POST /api/loci/merge`).
|
|
115
|
+
Two entries that say the same thing in different words split the ranking, each
|
|
116
|
+
take an index line, and force a correction to be applied twice. Write-time dedupe
|
|
117
|
+
only catches byte-identical or same-title writes, so the realistic case — written
|
|
118
|
+
months apart, different wording — now has an explicit operation:
|
|
119
|
+
- the sources are **superseded, never deleted**: their history stays readable and
|
|
120
|
+
`loci_get` keeps working;
|
|
121
|
+
- tags and entities are unioned, so the merged entry keeps both entries'
|
|
122
|
+
searchability;
|
|
123
|
+
- an optional `expectedRevisions` list aborts the whole merge on a stale
|
|
124
|
+
expectation, before anything is written;
|
|
125
|
+
- a merge that changes the text re-opens verification (the target lands as a
|
|
126
|
+
candidate), because trust is bound to the words that were confirmed;
|
|
127
|
+
- one `merge` audit line names both sides.
|
|
128
|
+
|
|
129
|
+
## [1.5.0] — 2026-11-14
|
|
130
|
+
|
|
131
|
+
### Fixed
|
|
132
|
+
|
|
133
|
+
- **`--json doctor` printed the help text and exited 2.** Flags are now
|
|
134
|
+
position-independent: `--json doctor` and `doctor --json` are the same thing.
|
|
135
|
+
- **`snapshots` was unreachable** — its block sat after the switch, so it never ran.
|
|
136
|
+
It is a real command now, alongside a new `snapshot [--label <name>]` that writes
|
|
137
|
+
one consistent `VACUUM INTO` file under `backups/`.
|
|
138
|
+
- **`stats` reported the current directory's scope only.** Running it from another
|
|
139
|
+
directory showed "0 entries" for a store with entries in it. Diagnostics default
|
|
140
|
+
to the whole store; `--session` narrows to the current scope set.
|
|
141
|
+
- **The audit log accepted arbitrary prose** in a delete `reason`. It is now capped
|
|
142
|
+
and scanned by the same secret detector as memory bodies.
|
|
143
|
+
- **`assertInside` was lexical**: a symlink inside the data directory passed the
|
|
144
|
+
prefix check and then wrote wherever the link pointed. It now also resolves the
|
|
145
|
+
deepest existing part of the path and requires that to stay inside, with a test
|
|
146
|
+
that skips on a host that cannot create symlinks.
|
|
147
|
+
|
|
148
|
+
### Changed
|
|
149
|
+
|
|
150
|
+
- CI and the release workflow install with **`npm ci`** from a committed
|
|
151
|
+
`package-lock.json`.
|
|
152
|
+
- The release workflow refuses a tag whose commit is not an ancestor of `main`,
|
|
153
|
+
publishes prereleases to the **`next`** dist-tag, and runs the coverage gate
|
|
154
|
+
before publishing.
|
|
155
|
+
- The coverage gate now fails on **lines (<80), branches (<55) and functions
|
|
156
|
+
(<70)**, and prints all three; before, only lines were checked while branches sat
|
|
157
|
+
at 62%.
|
|
158
|
+
- CI installs the packed tarball and runs its `bin`, so a broken `files` list or
|
|
159
|
+
shebang fails the build instead of a user's terminal.
|
|
160
|
+
- `docs/OPERATIONS.md` shows the exact way to invoke the CLI from an installed
|
|
161
|
+
profile (`node <profile>/node_modules/dsh-loci/scripts/cli.mjs`), documents the
|
|
162
|
+
`/loci` slash commands, and replaces the "no snapshot command" note.
|
|
163
|
+
|
|
164
|
+
## [1.4.0] — 2026-11-14
|
|
165
|
+
|
|
166
|
+
### Fixed
|
|
167
|
+
|
|
168
|
+
- **`export` then `import` imported nothing.** The exporter wrote entries under
|
|
169
|
+
`<root>/markdown/<scope>/<kind>/` and a one-line summary under
|
|
170
|
+
`<root>/exports/`; the importer read `exports`. The default is now the tree that
|
|
171
|
+
holds the documents, and the original scope and scope key are restored from the
|
|
172
|
+
front matter instead of forcing every import into `workspace:imported`.
|
|
173
|
+
- The CLI granted itself `allowHardDelete`; it now honours the config, with
|
|
174
|
+
`--allow-hard-delete` as the explicit opt-in for one invocation.
|
|
175
|
+
- `cli.mjs` read the version from four hardcoded strings; it now reads
|
|
176
|
+
`package.json`.
|
|
177
|
+
|
|
178
|
+
### Added
|
|
179
|
+
|
|
180
|
+
- `store.forgetAll()`: soft (or hard, with `allowHardDelete`) deletion of one scope
|
|
181
|
+
or the whole store, gated by an echoed confirmation phrase, one audit line per
|
|
182
|
+
entry. `POST /api/loci/forget-all` exposes it, and **Settings → Delete
|
|
183
|
+
everything** asks for the phrase before running.
|
|
184
|
+
- `test/unit/export.test.mjs` (round trip, scope restore, path guard) and
|
|
185
|
+
`test/unit/forget-all.test.mjs` (confirmation, hard-delete gate, scope limit).
|
|
186
|
+
|
|
187
|
+
## [1.3.0] — 2026-11-14
|
|
188
|
+
|
|
189
|
+
### Changed
|
|
190
|
+
|
|
191
|
+
- **A captured statement verifies itself.** With capture on by default, every
|
|
192
|
+
durable-sounding sentence the user said landed in the review queue and was never
|
|
193
|
+
injected — while the same words, written by the model with a quote, went straight
|
|
194
|
+
through. A captured statement now passes the same quote gate as a model-supplied
|
|
195
|
+
quote: if its sentence is still in the session buffer it is verified, otherwise it
|
|
196
|
+
stays a candidate. Imports and session summaries never verify themselves.
|
|
197
|
+
- `autoVerifyCaptured` (default `true`) keeps the old behaviour for a cautious user.
|
|
198
|
+
- The review queue lists only the kinds that could be injected, so past-session
|
|
199
|
+
summaries stop pretending to be pending decisions.
|
|
200
|
+
|
|
201
|
+
### Added
|
|
202
|
+
|
|
203
|
+
- Reject, verify-all, and a visible error line in the review queue; a failed click
|
|
204
|
+
now says so instead of disappearing into `console.warn`.
|
|
205
|
+
|
|
206
|
+
## [1.2.0] — 2026-11-14
|
|
207
|
+
|
|
208
|
+
### Changed
|
|
209
|
+
|
|
210
|
+
- **The sidebar card is a page of notes, not a dashboard.** Counters ("4 today",
|
|
211
|
+
"4 this week") are gone. The card now shows `更新于 <time>` over one short
|
|
212
|
+
paragraph made of the few things the agent currently understands in this
|
|
213
|
+
project — a consensus, a lesson, a fact — so reading it answers "did it
|
|
214
|
+
actually take that in?" instead of "did the number go up?".
|
|
215
|
+
Ordering is by when an entry last *became* trustworthy (`verified_at`, falling
|
|
216
|
+
back to the last edit), so confirming an old entry today puts it at the top.
|
|
217
|
+
- The card is scoped to the current project's read set, not to every scope in the
|
|
218
|
+
store: it answers "what does this conversation's agent know".
|
|
219
|
+
- Pending entries are mentioned as one line of text ("still need your review"),
|
|
220
|
+
never as a badge count.
|
|
221
|
+
|
|
222
|
+
### Added
|
|
223
|
+
|
|
224
|
+
- `buildKnowledge` + `GET /api/loci/knowledge`: verified entries as notes, with a
|
|
225
|
+
first-sentence `gist` that is withheld when it merely restates the title.
|
|
226
|
+
- `test/unit/knowledge.test.mjs`: notes exclude candidates, respect scope, carry
|
|
227
|
+
the moment they became true, and re-order on a fresh confirmation.
|
|
228
|
+
|
|
229
|
+
## [1.1.0] — 2026-11-13
|
|
230
|
+
|
|
231
|
+
### Added
|
|
232
|
+
|
|
233
|
+
- **Sidebar mirror** (`sidebar.footer.action`): a flat card beside Settings that
|
|
234
|
+
shows what memory changed today and in the last seven days, the last three
|
|
235
|
+
changes as one-line entries, pending-review count, the installed version in small
|
|
236
|
+
text, and an explicit "check for updates" that is the only path that touches the
|
|
237
|
+
network.
|
|
238
|
+
- **Feed tab** in the panel: every add, revision, verification, pin, deletion,
|
|
239
|
+
restore, import and maintenance pass, newest first, with the entry it touched.
|
|
240
|
+
- **`/api/loci/activity`**, **`/api/loci/update`**, **`/api/loci/version`** — the
|
|
241
|
+
owner-only endpoints behind those two surfaces; the activity feed carries ids,
|
|
242
|
+
titles and counts, never bodies.
|
|
243
|
+
- **Update checker** (`src/host/update.js`): six-hour cache, 4-second timeout, and
|
|
244
|
+
"unavailable" on any failure instead of an error state. It never installs
|
|
245
|
+
anything; upgrading stays a decision made in the plugin manager.
|
|
246
|
+
|
|
247
|
+
### Changed
|
|
248
|
+
|
|
249
|
+
- The panel opens on the **Feed** tab (was Index), because "what changed" is the
|
|
250
|
+
question a user actually arrives with.
|
|
251
|
+
- Version text follows the ecosystem convention: `v1.1.0` at 10px in the card,
|
|
252
|
+
and a quiet version line in the Stats tab.
|
|
253
|
+
- Removed the `build:client` script: the client half is hand-authored
|
|
254
|
+
module-loader JavaScript, so there was nothing to build and the entry was broken.
|
|
255
|
+
|
|
256
|
+
### Fixed
|
|
257
|
+
|
|
258
|
+
- (pending) findings from the four independent production-readiness audits.
|
|
259
|
+
|
|
260
|
+
## [1.0.0] — 2026-11-13
|
|
261
|
+
|
|
262
|
+
First release.
|
|
263
|
+
|
|
264
|
+
### Added
|
|
265
|
+
|
|
266
|
+
- **Layered memory** with five kinds (`rule`, `semantic`, `procedural`, `episodic`,
|
|
267
|
+
`working`), where `working` is never injected.
|
|
268
|
+
- **Bi-temporal facts**: `valid_from` / `valid_to` for event time and `recorded_at`
|
|
269
|
+
for transaction time; edits append a revision and close the previous one instead
|
|
270
|
+
of overwriting it. `loci_search(asOf)` answers "what was true then".
|
|
271
|
+
- **One injected section** (`loci:index`, order 4000, `interpolate:false`),
|
|
272
|
+
byte-budgeted (default 3,584 B), stable across read-only turns, empty on an empty
|
|
273
|
+
store.
|
|
274
|
+
- **Five tools**: `loci_write`, `loci_search`, `loci_get`, `loci_forget`,
|
|
275
|
+
`loci_stats`. Search returns `total`, `hasMore` and a cursor; pagination reaches
|
|
276
|
+
everything it counts.
|
|
277
|
+
- **Hybrid retrieval**: CJK bigram full text + entity expansion + optional vectors,
|
|
278
|
+
ranked in process with a coverage admission rule (no `bm25()` scan, no prefix
|
|
279
|
+
query). Measured p95 12.5 ms at 10,000 entries.
|
|
280
|
+
- **Trust gate**: only `verified` entries are injected; status is derived from a
|
|
281
|
+
verbatim quote found in the user's live messages, never declared by the model.
|
|
282
|
+
- **Scope isolation**: `session` / `workspace` / `project` / `global`, explicit keys,
|
|
283
|
+
`workspace` default, `global` only on request.
|
|
284
|
+
- **Deterministic capture**: durable statements become candidates after a completed
|
|
285
|
+
turn; a session summary is written when a root agent is disposed. No background
|
|
286
|
+
model call.
|
|
287
|
+
- **Safety**: `{{}}` neutralization, markup escaping, instruction-shape quarantine,
|
|
288
|
+
credential redaction, fenced injection, audit without content.
|
|
289
|
+
- **Native UI**: a settings page and a four-tab sidebar panel (Index, Search,
|
|
290
|
+
Review, Stats) built on the Host's slots and theme tokens, with `zh`/`en` strings.
|
|
291
|
+
- **Owner-only JSON API** for the panel, over the Host's exact-fetch routes.
|
|
292
|
+
- **Optional MCP facade** (`src/mcp/server.js`), read-only by default.
|
|
293
|
+
- SQLite store with WAL, busy timeout, snapshot migration and rollback, Markdown
|
|
294
|
+
export/import, `dsh-loci` CLI, `/loci` command.
|
|
295
|
+
|
|
296
|
+
### Known limits
|
|
297
|
+
|
|
298
|
+
- No LLM consolidation (v1.1 candidate).
|
|
299
|
+
- Deterministic capture only records explicitly phrased durable statements.
|
|
300
|
+
- Vector search is brute-force cosine over stored embeddings.
|
|
301
|
+
- Plaintext at rest; no encryption beyond the OS.
|
|
302
|
+
|
|
303
|
+
[1.0.0]: https://github.com/Awenforever/dsh-loci/releases/tag/v1.0.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Awenforever
|
|
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/NOTICE
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
dsh-loci
|
|
2
|
+
Copyright (c) 2026 Awenforever
|
|
3
|
+
SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
Third-party material
|
|
6
|
+
--------------------
|
|
7
|
+
|
|
8
|
+
No third-party source code is vendored in this package. The design cites the
|
|
9
|
+
following work for specific mechanisms; see docs/RESEARCH.md for the exact
|
|
10
|
+
section and URL behind each claim.
|
|
11
|
+
|
|
12
|
+
- Graphiti / Zep — bi-temporal edge model and edge invalidation
|
|
13
|
+
(arXiv 2501.13956). Mechanism reimplemented from the published description.
|
|
14
|
+
- Mem0 — multi-signal retrieval with BM25 as a boost signal, and write-time
|
|
15
|
+
temporal metadata (arXiv 2504.19413; docs.mem0.ai). Mechanism reimplemented.
|
|
16
|
+
- Cognee — current / superseded / conflicting fact marks and an all-local
|
|
17
|
+
default storage stack (docs.cognee.ai). Mechanism reimplemented.
|
|
18
|
+
- MemOS — layered memory vocabulary and the local RRF/MMR plugin as an
|
|
19
|
+
existence proof (arXiv 2505.22101, arXiv 2507.03724). Vocabulary only.
|
|
20
|
+
- OpenViking — L0/L1 context layer sizes and session-start budget discipline
|
|
21
|
+
(docs.openviking.ai). Budgets reimplemented.
|
|
22
|
+
- ReMe — guidance-only injection into DSH and read-only on-demand retrieval
|
|
23
|
+
(agentscope-ai/ReMe, reme.agentscope.io). Product-level pattern, no code.
|
|
24
|
+
- memsearch — Markdown-as-truth with a rebuildable shadow index, and the
|
|
25
|
+
low-risk-only project configuration rule (zilliztech/memsearch). Pattern only.
|
|
26
|
+
- Letta / MemGPT — core vs archival memory vocabulary (docs.letta.com).
|
|
27
|
+
- Claude Code, Cursor, ChatGPT — product documentation reviewed for what to
|
|
28
|
+
adopt and what not to copy.
|
|
29
|
+
|
|
30
|
+
Runtime dependencies: `@deepseek-ai/schemastery` (MIT), used for the
|
|
31
|
+
plugin configuration schema. It is not modified.
|
package/README.md
CHANGED
|
@@ -1,3 +1,144 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-loci · Loci 记忆宫殿
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**A long-term memory for DeepSeek Harness where the prompt only ever sees the palace's index — every room is one lookup away.**
|
|
4
|
+
|
|
5
|
+
[中文](README.zh.md) · [Design](docs/DESIGN.md) · [Research](docs/RESEARCH.md) · [Operations](docs/OPERATIONS.md) · [Security](docs/SECURITY.md) · [Acceptance](docs/ACCEPTANCE.md)
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
system prompt: ~1.3k tokens, stable, one line per fact
|
|
9
|
+
user asks → loci_search "发布顺序" → total=3, one call
|
|
10
|
+
needs detail → loci_get m_01J… → full text + history + evidence
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Why it exists
|
|
14
|
+
|
|
15
|
+
Every memory plugin tested in this workspace failed one of two ways: it cost a
|
|
16
|
+
fixed toll on every request (mnemon: +4.8 KB of system prompt, 16 tools), or it
|
|
17
|
+
grew without bound (ledger-memory: 7 KB → 27 KB per turn). The ones that stayed
|
|
18
|
+
cheap truncated recall silently (mnemon returned 4 of 20 requested rows;
|
|
19
|
+
auto-memory's index holds about 27 Chinese entries).
|
|
20
|
+
|
|
21
|
+
Loci is built around one rule: **the prompt holds an index, not a memory.**
|
|
22
|
+
|
|
23
|
+
- Empty store → **0 bytes injected**. No header, no advice, nothing.
|
|
24
|
+
- 100 entries → still one stable section, capped at a byte budget with an honest
|
|
25
|
+
"… +N more" footer.
|
|
26
|
+
- A read-only turn → the section is byte-identical, so the prefix cache survives.
|
|
27
|
+
- A broad question → `loci_search` returns `total` and a cursor, so "list them
|
|
28
|
+
all" is answerable.
|
|
29
|
+
|
|
30
|
+
## What it remembers
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
| Layer | Kind | Injected? | Example |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| rules | `rule` | yes, first | "删除前必须做路径断言" |
|
|
37
|
+
| semantic | `semantic` | yes, index line | "生产机是 prod-01" |
|
|
38
|
+
| procedural | `procedural` | yes, index line | "发布顺序 staging → canary → 全量" |
|
|
39
|
+
| episodic | `episodic` | yes, index line | "2026-11-13 会话摘要" |
|
|
40
|
+
| working | `working` | no | scratch facts for this session only |
|
|
41
|
+
|
|
42
|
+
Only `verified` entries are injected. Anything written without a verbatim quote
|
|
43
|
+
from the conversation is stored as a **candidate** and waits in the panel for
|
|
44
|
+
one click.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
Inside the Harness profile that should have memory:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
Settings → Plugins → install from a directory or the market listing, or
|
|
52
|
+
dsh plugin --profile <name> add dsh-loci
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The bundle adds one Host row (`dsh-loci`) and one Client half. Restart the
|
|
56
|
+
Harness, then open **Settings → Loci** or the **Memory** panel in the sidebar.
|
|
57
|
+
|
|
58
|
+
Requirements: DSH `>=0.2.2-rc.1 <0.3.0-0`, Node `>=24.9.0` (the Harness ships it).
|
|
59
|
+
No service and no native module.
|
|
60
|
+
|
|
61
|
+
The package also ships a small CLI (`snapshot`, `export`, `import`, `doctor`,
|
|
62
|
+
`stats`, `review`, `purge`, `migrate`, `rollback`). It is not on your `PATH` after a
|
|
63
|
+
plugin install — call it through the profile:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
node "$HOME/.dsh/profiles/<profile>/node_modules/dsh-loci/scripts/cli.mjs" doctor --json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Inside a chat there are zero-token slash commands too: `/loci`, `/loci review`,
|
|
70
|
+
`/loci doctor`, `/loci export`.
|
|
71
|
+
|
|
72
|
+
## The five tools
|
|
73
|
+
|
|
74
|
+
| Tool | Use |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `loci_write` | Save, revise, or merge facts. `evidenceQuote` must be verbatim; without it the entry is a candidate. `op:'merge'` folds duplicate ids into one — the sources are superseded, never deleted. |
|
|
77
|
+
| `loci_search` | Hybrid search. Returns `total`, `hasMore`, `cursor`; `detail` is snippet/overview/full. Omit `query` to list the newest entries. |
|
|
78
|
+
| `loci_get` | Full text plus revision history and the evidence quote. |
|
|
79
|
+
| `loci_forget` | Soft-delete (history kept) or restore. |
|
|
80
|
+
| `loci_stats` | Counts, budget use, channel health, database location. |
|
|
81
|
+
|
|
82
|
+
Tool names are prefixed `loci_` on purpose: `ctx.tools.register` throws on a
|
|
83
|
+
duplicate name, so two memory plugins both claiming `memory_search` would break
|
|
84
|
+
one of them.
|
|
85
|
+
|
|
86
|
+
## Where you see it
|
|
87
|
+
|
|
88
|
+
- **Sidebar note** — a small card beside Settings, like the quota and advisor
|
|
89
|
+
cards, that reads like a page of notes rather than a dashboard: `更新于 <time>`
|
|
90
|
+
over one short paragraph of what the agent currently understands in this
|
|
91
|
+
project (a consensus, a lesson, a fact), plus one quiet line if something is
|
|
92
|
+
waiting for your review. It answers "did it take that in?" — no counters.
|
|
93
|
+
The version sits in small text with an explicit "check for updates", the only
|
|
94
|
+
path that touches the network.
|
|
95
|
+
- **Memory panel** — the full view, opening on a **Feed** of every add, revision,
|
|
96
|
+
verification, pin, deletion and import, newest first.
|
|
97
|
+
- **Settings → Loci** — the budget, capture mode, default scope, secret policy
|
|
98
|
+
and embedding channel.
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
Everything lives in the plugin's Loader row; Loci never reads a project file.
|
|
103
|
+
The settings page writes the fields marked *live* without a restart.
|
|
104
|
+
|
|
105
|
+
| Key | Default | Meaning |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `sectionMaxBytes` | `3584` | hard byte budget for the injected section (max 16384) |
|
|
108
|
+
| `rulesMaxBytes` | `1024` | ceiling for the rules block inside that budget |
|
|
109
|
+
| `defaultScope` | `workspace` | scope a write lands in when it does not say |
|
|
110
|
+
| `captureMode` | `rules` | deterministic capture of explicit durable statements |
|
|
111
|
+
| `extractMode` | `off` | `on` asks the configured model, at turn end, what is worth remembering — always as candidates, always budgeted |
|
|
112
|
+
| `recallAssist` | `off` | one bounded first-turn recall block per session |
|
|
113
|
+
| `maintenanceMode` | `sweep` | TTL, index repair, and embedding backfill every 6h |
|
|
114
|
+
| `secretPolicy` | `redact` | redact high-confidence secrets, or refuse the write |
|
|
115
|
+
| `vectorMode` | `off` | optional HTTP embedding channel; lexical always works |
|
|
116
|
+
| `allowHardDelete` | `false` | enable `purge` in the CLI/panel |
|
|
117
|
+
|
|
118
|
+
## What it will not do
|
|
119
|
+
|
|
120
|
+
- No model call in any automatic path in v1.0 — capture and summaries are
|
|
121
|
+
deterministic, so the cost is visible and zero.
|
|
122
|
+
- No cloud, no telemetry, no network unless you configure `vectorMode: 'http'`.
|
|
123
|
+
- No cross-project leakage: reads use an explicit scope-key set, never a wildcard.
|
|
124
|
+
- No physical deletion by default; `purge` needs a config switch, a confirmation,
|
|
125
|
+
and leaves an audit line.
|
|
126
|
+
|
|
127
|
+
## Data
|
|
128
|
+
|
|
129
|
+
`$DSH_HOME/memory/loci/` only:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
memory.db SQLite (WAL) — the transactional truth
|
|
133
|
+
audit.jsonl append-only: inject / recall / write / verify / delete
|
|
134
|
+
backups/ pre-migration and manual snapshots (VACUUM INTO)
|
|
135
|
+
markdown/ human-readable projection
|
|
136
|
+
exports/ export bundles and import drops
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Uninstalling the plugin leaves the directory untouched, by design. Delete it
|
|
140
|
+
yourself when you mean it.
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT. See [NOTICE](NOTICE) for the research lineage of each mechanism.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# dsh-loci · Loci 记忆宫殿
|
|
2
|
+
|
|
3
|
+
**给 DeepSeek Harness 的长期记忆:提示词里只放「宫殿的索引」,每个房间按需推门。**
|
|
4
|
+
|
|
5
|
+
[English](README.md) · [设计](docs/DESIGN.md) · [调研](docs/RESEARCH.md) · [运维](docs/OPERATIONS.md) · [安全](docs/SECURITY.md) · [验收](docs/ACCEPTANCE.md)
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
系统提示: 约 1.3k tokens,稳定,一条记忆一行
|
|
9
|
+
你问: → loci_search "发布顺序" → total=3,一次调用
|
|
10
|
+
要细节: → loci_get m_01J… → 全文 + 历史 + 逐字证据
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 为什么需要它
|
|
14
|
+
|
|
15
|
+
本机实测过的记忆插件,要么在每次请求上交固定「税」(mnemon:系统提示 +4.8KB、16 个工具),要么无界增长(ledger-memory:每轮 7KB 涨到 27KB)。便宜的又都在静默截断召回(mnemon 要 20 条只回 4 条;auto-memory 的索引约 27 条中文就触顶)。
|
|
16
|
+
|
|
17
|
+
Loci 只有一条规则:**提示词里放索引,不放记忆。**
|
|
18
|
+
|
|
19
|
+
- 空库 → **注入 0 字节**,没有标题、没有说明、什么都没有。
|
|
20
|
+
- 100 条 → 仍是同一段,硬字节预算,超出时明确写出「… 还有 N 条」。
|
|
21
|
+
- 只读回合 → 段内容逐字节不变,前缀缓存得以保留。
|
|
22
|
+
- 宽泛提问 → `loci_search` 返回 `total` 和 cursor,「列出全部」真的能列完。
|
|
23
|
+
|
|
24
|
+
## 它记什么
|
|
25
|
+
|
|
26
|
+
| 层 | kind | 是否注入 | 例子 |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| 硬约束 | `rule` | 是,排最前 | 「删除前必须做路径断言」 |
|
|
29
|
+
| 事实 | `semantic` | 是,索引一行 | 「生产机是 prod-01」 |
|
|
30
|
+
| 流程 | `procedural` | 是,索引一行 | 「发布顺序 staging → canary → 全量」 |
|
|
31
|
+
| 经历 | `episodic` | 是,索引一行 | 「2026-11-13 会话摘要」 |
|
|
32
|
+
| 工作记忆 | `working` | 否 | 只在本次会话有效的草稿事实 |
|
|
33
|
+
|
|
34
|
+
只有 `verified` 会被注入。写入时若没有来自对话的逐字引文,条目落为 **candidate**,在面板里等你点一下。
|
|
35
|
+
|
|
36
|
+
## 安装
|
|
37
|
+
|
|
38
|
+
在需要有记忆的 profile 里:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
设置 → 插件 → 从目录或市场条目安装,或
|
|
42
|
+
dsh plugin --profile <名字> add dsh-loci
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
bundle 会插入一行 Host 插件(`dsh-loci`)和一个 Client 半边。重启 Harness,然后打开 **设置 → Loci** 或侧栏的 **记忆** 面板。
|
|
46
|
+
|
|
47
|
+
侧栏底部还有一张**便签**(和额度、advisor 卡片并排),它不是看板:顶上 `更新于 <时间>`,下面一小段话,是它此刻真正理解的东西——达成的共识、踩过的经验、事实。没有数字,看到的就是"它有没有把这件事接住"。
|
|
48
|
+
|
|
49
|
+
要求:DSH `>=0.2.2-rc.1 <0.3.0-0`,Node `>=24.9.0`(Harness 自带)。无服务、无原生模块。
|
|
50
|
+
|
|
51
|
+
包里还带一个小 CLI(`snapshot`/`export`/`import`/`doctor`/`stats`/`review`/`purge`/`migrate`/`rollback`)。插件安装后它不在 PATH 上,按 profile 调用:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
node "$HOME/.dsh/profiles/<profile>/node_modules/dsh-loci/scripts/cli.mjs" doctor --json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
会话里还有零 token 的斜杠命令:`/loci`、`/loci review`、`/loci doctor`、`/loci export`。
|
|
58
|
+
|
|
59
|
+
## 五个工具
|
|
60
|
+
|
|
61
|
+
| 工具 | 用途 |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `loci_write` | 写入、修订或合并事实。`evidenceQuote` 必须逐字;没有就落为候选。`op:'merge'` 把重复条目折进一条——源条目被 supersede,不删除。 |
|
|
64
|
+
| `loci_search` | 混合检索,返回 `total`、`hasMore`、`cursor`;`detail` 可选 snippet/overview/full。 |
|
|
65
|
+
| `loci_get` | 全文 + 修订历史 + 证据引文。 |
|
|
66
|
+
| `loci_forget` | 软删除(历史保留)或恢复。 |
|
|
67
|
+
| `loci_stats` | 计数、预算占用、通道健康、库位置。 |
|
|
68
|
+
|
|
69
|
+
工具名用 `loci_` 前缀是有意的:`ctx.tools.register` 同名会抛错,两个记忆插件都叫 `memory_search` 必然坏一个。
|
|
70
|
+
|
|
71
|
+
## 配置
|
|
72
|
+
|
|
73
|
+
配置只存在于插件自己的 Loader 行;Loci **不读项目内配置文件**。设置页里标注「即时」的字段无需重启。
|
|
74
|
+
|
|
75
|
+
| 键 | 默认 | 含义 |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `sectionMaxBytes` | `3584` | 注入段的硬字节预算(上限 16384) |
|
|
78
|
+
| `rulesMaxBytes` | `1024` | 段内规则块的字节上限 |
|
|
79
|
+
| `defaultScope` | `workspace` | 未显式指定时的写入作用域 |
|
|
80
|
+
| `captureMode` | `rules` | 规则型捕获用户的明确约定 |
|
|
81
|
+
| `extractMode` | `off` | 设为 `on` 时,在回合结束让模型提一遍「这轮有什么值得记」——一律落为候选,且有预算上限 |
|
|
82
|
+
| `recallAssist` | `off` | 每会话一次、有上限的首轮辅助召回块 |
|
|
83
|
+
| `maintenanceMode` | `sweep` | 每 6 小时做 TTL、索引修复、向量补齐 |
|
|
84
|
+
| `secretPolicy` | `redact` | 高置信密钥脱敏,或直接拒绝写入 |
|
|
85
|
+
| `vectorMode` | `off` | 可选 HTTP 向量通道;词法检索始终可用 |
|
|
86
|
+
| `allowHardDelete` | `false` | 是否允许 CLI/面板里的 `purge` |
|
|
87
|
+
|
|
88
|
+
## 它明确不做
|
|
89
|
+
|
|
90
|
+
- v1.0 的自动路径里没有模型调用 —— 捕获与摘要是确定性的,成本可见且为零。
|
|
91
|
+
- 无云、无遥测;除非你配置 `vectorMode: 'http'`,否则不出网。
|
|
92
|
+
- 不跨项目串味:读取用显式 scope-key 集合,从不通配。
|
|
93
|
+
- 默认不物理删除;`purge` 需要配置开关 + 确认 + 留审计。
|
|
94
|
+
|
|
95
|
+
## 数据位置
|
|
96
|
+
|
|
97
|
+
只在 `$DSH_HOME/memory/loci/`:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
memory.db SQLite(WAL),事务真源
|
|
101
|
+
audit.jsonl 追加写:inject / recall / write / verify / delete
|
|
102
|
+
backups/ 迁移前与手动快照(VACUUM INTO)
|
|
103
|
+
markdown/ 人类可读投影
|
|
104
|
+
exports/ 导出包与导入投递目录
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
卸载插件不会删除这个目录(有意为之);真要删请自己动手。
|
|
108
|
+
|
|
109
|
+
## 许可
|
|
110
|
+
|
|
111
|
+
MIT。每个机制的研究出处见 [NOTICE](NOTICE)。
|