superlocalmemory 4.0.5 → 4.0.7
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 +108 -0
- package/README.md +8 -9
- package/package.json +3 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/rbac.py +106 -0
- package/src/superlocalmemory/brain/truth.py +80 -10
- package/src/superlocalmemory/cli/__main__.py +17 -0
- package/src/superlocalmemory/cli/commands.py +28 -3
- package/src/superlocalmemory/cli/gdpr_cmd.py +779 -0
- package/src/superlocalmemory/cli/gdpr_io.py +109 -0
- package/src/superlocalmemory/cli/main.py +85 -0
- package/src/superlocalmemory/cli/summary_cmd.py +195 -0
- package/src/superlocalmemory/code_graph/bridge/entity_resolver.py +26 -0
- package/src/superlocalmemory/code_graph/bridge/event_listeners.py +14 -3
- package/src/superlocalmemory/code_graph/bridge/maintenance.py +206 -0
- package/src/superlocalmemory/code_graph/config.py +65 -1
- package/src/superlocalmemory/code_graph/extractors/__init__.py +17 -0
- package/src/superlocalmemory/code_graph/graph_store.py +180 -3
- package/src/superlocalmemory/code_graph/parser.py +280 -100
- package/src/superlocalmemory/compliance/gdpr.py +358 -0
- package/src/superlocalmemory/core/config.py +44 -1
- package/src/superlocalmemory/core/engine_wiring.py +5 -1
- package/src/superlocalmemory/core/fact_consolidator.py +24 -1
- package/src/superlocalmemory/core/maintenance.py +93 -1
- package/src/superlocalmemory/core/recall_worker.py +33 -12
- package/src/superlocalmemory/infra/backup.py +138 -0
- package/src/superlocalmemory/infra/backup_obligations.py +423 -0
- package/src/superlocalmemory/learning/engagement.py +165 -0
- package/src/superlocalmemory/mcp/tools_code_graph.py +78 -7
- package/src/superlocalmemory/mcp/tools_v3.py +20 -6
- package/src/superlocalmemory/retrieval/engine.py +21 -0
- package/src/superlocalmemory/retrieval/remote_reranker.py +108 -11
- package/src/superlocalmemory/server/routes/brain.py +283 -15
- package/src/superlocalmemory/server/routes/learning.py +13 -25
- package/src/superlocalmemory/server/routes/memories.py +61 -0
- package/src/superlocalmemory/server/routes/v3_api.py +171 -60
- package/src/superlocalmemory/storage/database.py +36 -0
- package/src/superlocalmemory/storage/models.py +12 -4
- package/src/superlocalmemory/storage/schema_code_graph.py +44 -1
- package/src/superlocalmemory/summaries/__init__.py +37 -0
- package/src/superlocalmemory/summaries/base.py +108 -0
- package/src/superlocalmemory/summaries/daily_reflection.py +293 -0
- package/src/superlocalmemory/summaries/project_work_log.py +424 -0
- package/src/superlocalmemory/summaries/session_summary.py +307 -0
- package/src/superlocalmemory/ui/css/design-system.css +76 -1
- package/src/superlocalmemory/ui/index.html +29 -12
- package/src/superlocalmemory/ui/js/fact-detail.js +61 -0
- package/src/superlocalmemory/ui/js/od-agents.js +49 -5
- package/src/superlocalmemory/ui/js/od-brain.js +257 -77
- package/src/superlocalmemory/ui/js/od-graph.js +147 -6
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,114 @@ All notable changes to SuperLocalMemory will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.0.7] — Reachable
|
|
9
|
+
|
|
10
|
+
Everything here was already built. None of it could be used.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`slm summary` — the readable layer over your memories.** Requested in #113
|
|
14
|
+
and shipped without a way to call it in 4.0.6.
|
|
15
|
+
- `slm summary day` — what you recorded today (also `yesterday`, or a date)
|
|
16
|
+
- `slm summary project` — what was worked on, for a directory
|
|
17
|
+
- `slm summary session <id>` — what one session covered
|
|
18
|
+
|
|
19
|
+
Every summary says how much of the underlying data it could actually see, and
|
|
20
|
+
`--json` lists the exact memories it was built from. Session data is sparse —
|
|
21
|
+
roughly 4% of facts carry a session id — so a session summary reports that
|
|
22
|
+
rather than presenting a fraction as the whole. No language model needed, so
|
|
23
|
+
these work in the fully offline mode.
|
|
24
|
+
- **Memories now link to the code they mention.** A memory that names a
|
|
25
|
+
function, method or file is connected to it in the code graph, with a short
|
|
26
|
+
description of what it points at and a marker when the code has since changed.
|
|
27
|
+
Expanding a memory in the dashboard shows this; nothing appears if you have no
|
|
28
|
+
code graph. Runs during background maintenance, never when a memory is saved,
|
|
29
|
+
and requires no language model.
|
|
30
|
+
- **`scripts/bump_version.py`** sets the release version in all fifteen places
|
|
31
|
+
that declare one, with `--check` for CI.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- **The code↔memory bridge never ran.** Four things were missing at once: setup
|
|
35
|
+
wrote a `bridge_enabled` flag no code read, the code-graph build discarded it,
|
|
36
|
+
the code-graph settings had no loader at all, and the method the bridge was
|
|
37
|
+
written against was an unimplemented placeholder. Enabling the code graph
|
|
38
|
+
during setup left the flag on disk and the feature inert, with nothing to
|
|
39
|
+
indicate it.
|
|
40
|
+
- **Version numbers disagreed across the project.** 4.0.6 shipped with the pip
|
|
41
|
+
requirements pins, the npm lockfile, the editor plugin manifest, the citation
|
|
42
|
+
metadata and the lockfile all still reading 4.0.5, and the agent rule footers
|
|
43
|
+
reading 4.0.4. Installing from `requirements.txt` fetched the wrong release.
|
|
44
|
+
- **Stale-memory checks reported "nothing is stale" when the feature was off.**
|
|
45
|
+
They answer entirely from code links, so with linking disabled they returned an
|
|
46
|
+
empty list — the most reassuring possible answer, from something that never
|
|
47
|
+
ran. They now say the feature is off, and the remediation message names a
|
|
48
|
+
setting that exists (it previously named one that did not).
|
|
49
|
+
- **`slm help` did not mention `gdpr`**, which shipped in 4.0.6. The drift guard
|
|
50
|
+
that should have caught it only read one file, so a command registered
|
|
51
|
+
elsewhere was exempt from the check.
|
|
52
|
+
- **Consolidation could create a database file named after a bad argument.**
|
|
53
|
+
Given something that was neither a database handle nor a path, it stringified
|
|
54
|
+
the object and let SQLite create that filename. It now refuses the argument.
|
|
55
|
+
|
|
56
|
+
## [4.0.6] — The Connected Brain
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
- **The Living Brain, rewritten for people who do not read telemetry.** The Brain
|
|
60
|
+
section now leads with the number of questions your memory has answered rather
|
|
61
|
+
than a raw event count, names its ranking phase in words, and stops presenting
|
|
62
|
+
a starting value as a measured result. Source quality no longer lists internal
|
|
63
|
+
identifiers; when nothing has been measured yet it says so and explains what
|
|
64
|
+
would change that.
|
|
65
|
+
- **Session, daily and project summaries — generators only, not yet reachable.**
|
|
66
|
+
A readable layer over your memories: what a session covered, what a day's main
|
|
67
|
+
topics were, and what was worked on per project. Each links back to the
|
|
68
|
+
memories it came from and states how much of the underlying data it could
|
|
69
|
+
actually cover. Corrected after release: 4.0.6 ships the generators but no
|
|
70
|
+
command, tool or endpoint that calls them, so there is no way to use this yet.
|
|
71
|
+
The surface lands in 4.0.7. Requested in #113.
|
|
72
|
+
- **Entity-level memory consolidation now runs.** Repeated facts about the same
|
|
73
|
+
entity are merged during maintenance, and the originals are archived rather
|
|
74
|
+
than deleted.
|
|
75
|
+
- **Codex and Bounded Loops appear in Connected clients.** Codex is listed with
|
|
76
|
+
the configuration that proves it, and Bounded Loops is detected when installed,
|
|
77
|
+
with its version and the bridge it speaks.
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
- **The knowledge graph no longer opens blank.** Previously the graph could
|
|
81
|
+
render nothing on first open — and again when you returned to it — until you
|
|
82
|
+
moved the node slider. Two separate causes: the view framed itself against a
|
|
83
|
+
canvas that had no size yet, and re-entering the pane cleared the canvas
|
|
84
|
+
without redrawing it. Default node count is now 50.
|
|
85
|
+
- **The graph's details and chat panel is reachable on smaller screens.** Below
|
|
86
|
+
1100px it stacks under the graph, a full screen-height out of view; there is
|
|
87
|
+
now a control to reach it and a way back.
|
|
88
|
+
- **Presence tells you when it has stopped being recorded.** A gap in recording
|
|
89
|
+
previously looked identical to "no agents are active".
|
|
90
|
+
- **Recall no longer fails when the reranker returns no scores.** It falls back
|
|
91
|
+
to its existing ranking instead of raising.
|
|
92
|
+
- Storage: per-call connections now disable checkpoint-on-close
|
|
93
|
+
(`SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE`), removing a version-dependent blocking
|
|
94
|
+
close path. On SQLite builds whose close-path checkpoint can block, closing a
|
|
95
|
+
WAL-mode connection may wait on reader marks pinned by another connection
|
|
96
|
+
while holding SQLite's process-global VFS mutex, convoying every later
|
|
97
|
+
connection in the process; `PRAGMA busy_timeout` does not apply to that path.
|
|
98
|
+
Reported, diagnosed and fixed by **@kenyonxu** (#118), from a production
|
|
99
|
+
postmortem plus an in-suite reproduction.
|
|
100
|
+
Scope note: on SQLite 3.49.1 the close-path checkpoint is passive — measured
|
|
101
|
+
at 0.075 ms with a full WAL and a pinned reader mark — so the convoy is not
|
|
102
|
+
reproducible there. This change is therefore hardening: it makes the
|
|
103
|
+
non-blocking close explicit rather than depending on the behaviour of a
|
|
104
|
+
particular SQLite build.
|
|
105
|
+
- Storage: per-call connections now also set `PRAGMA wal_autocheckpoint=400`.
|
|
106
|
+
This pragma is per-connection and is not persisted in the database file, so
|
|
107
|
+
it previously applied only to the short-lived initialisation connection and
|
|
108
|
+
every working connection silently fell back to SQLite's default of 1000
|
|
109
|
+
frames. With checkpoint-on-close disabled, autocheckpoint is the only
|
|
110
|
+
remaining checkpoint path, so the intended value must be set where the
|
|
111
|
+
writes actually happen.
|
|
112
|
+
- Storage: when checkpoint-on-close cannot be disabled (Python 3.11, which
|
|
113
|
+
predates `Connection.setconfig`), this is now logged once instead of failing
|
|
114
|
+
silently, so operators know the deadlock hardening is inactive.
|
|
115
|
+
|
|
8
116
|
## [4.0.5] - 2026-08-16 — Reviewed time-aware corrections
|
|
9
117
|
|
|
10
118
|
### Added
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
</picture>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
|
-
<h1 align="center">SuperLocalMemory V4.0.
|
|
8
|
+
<h1 align="center">SuperLocalMemory V4.0.7</h1>
|
|
9
9
|
|
|
10
10
|
<h2 align="center">Rent the LLM. Own the memory.</h2>
|
|
11
11
|
|
|
@@ -27,12 +27,12 @@ guarantee here is stated as a falsifiable invariant, tested under an adversarial
|
|
|
27
27
|
negative control, and shipped with the harness that regenerates the evidence:
|
|
28
28
|
<code>python benchmark/run_all.py --trials 200 --output-dir results/</code>. What each experiment
|
|
29
29
|
does <em>not</em> exercise is stated too.</p>
|
|
30
|
-
<p align="center"><code>v4.0.
|
|
30
|
+
<p align="center"><code>v4.0.7</code> — one control plane: <strong>SLM-Mesh</strong> peer coordination · multi-scope memory (personal / shared / global) · profiles · Cache · Compress · 7-layer retrieval · code graph · Entity Explorer · skill evolution · Modes A/B/C · GDPR retention & audit chain · bounded loops — across CLI, MCP, dashboard, the <strong>Claude plugin</strong>, the <strong>Codex add-on</strong>, and documented IDE integrations.<br/>
|
|
31
31
|
Proxy: <code>slm wrap claude</code> · MCP: add <code>slm_compress</code> to your config · Skill: zero-config</p>
|
|
32
32
|
<p align="center"><strong>Four public arXiv preprints</strong> · V4: <a href="https://arxiv.org/abs/2608.08253">arXiv:2608.08253</a> · companion archive: <a href="https://zenodo.org/records/21853302">Zenodo 21853302</a> (<a href="https://doi.org/10.5281/zenodo.21853302">DOI 10.5281/zenodo.21853302</a>) · prior preprints: <a href="https://arxiv.org/abs/2603.02240">2603.02240</a> · <a href="https://arxiv.org/abs/2603.14588">2603.14588</a> · <a href="https://arxiv.org/abs/2604.04514">2604.04514</a>.</p>
|
|
33
33
|
|
|
34
34
|
<p align="center">
|
|
35
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/v4.0.
|
|
35
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/v4.0.7-Current_Release-2ea44f?style=for-the-badge&logo=checkmarx&logoColor=white" alt="v4.0.7 — Current Release"/></a>
|
|
36
36
|
<a href="https://arxiv.org/abs/2608.08253"><img src="https://img.shields.io/badge/arXiv-2608.08253-b31b1b?style=for-the-badge&logo=arxiv&logoColor=white" alt="SuperLocalMemory 4.0 paper on arXiv:2608.08253"/></a>
|
|
37
37
|
<a href="https://zenodo.org/records/21853302"><img src="https://img.shields.io/badge/Zenodo-10.5281%2Fzenodo.21853302-1682D4?style=for-the-badge&logo=zenodo&logoColor=white" alt="V4 paper on Zenodo: 10.5281/zenodo.21853302"/></a>
|
|
38
38
|
<a href="https://arxiv.org/abs/2603.14588"><img src="https://img.shields.io/badge/arXiv-2603.14588-b31b1b?style=for-the-badge&logo=arxiv&logoColor=white" alt="arXiv Paper"/></a>
|
|
@@ -41,7 +41,7 @@ Proxy: <code>slm wrap claude</code> · MCP: add <code>slm_compress</
|
|
|
41
41
|
<a href="https://www.npmjs.com/package/superlocalmemory"><img src="https://img.shields.io/npm/v/superlocalmemory?style=for-the-badge&logo=npm&logoColor=white" alt="npm"/></a>
|
|
42
42
|
<a href="https://www.gnu.org/licenses/agpl-3.0"><img src="https://img.shields.io/badge/License-AGPL_v3-blue.svg?style=for-the-badge" alt="AGPL v3"/></a>
|
|
43
43
|
<a href="#privacy-controls-and-operating-modes"><img src="https://img.shields.io/badge/Privacy-Deployment_Assessed-brightgreen?style=for-the-badge" alt="Privacy controls require deployment assessment"/></a>
|
|
44
|
-
<a href="#teams-and-enterprise-memory-v4"><img src="https://img.shields.io/badge/Enterprise-GDPR_%
|
|
44
|
+
<a href="#teams-and-enterprise-memory-v4"><img src="https://img.shields.io/badge/Enterprise-GDPR_%7C_EU_AI_Act_controls-0b5394?style=for-the-badge" alt="Enterprise governance: GDPR and EU AI Act controls"/></a>
|
|
45
45
|
<a href="https://superlocalmemory.com"><img src="https://img.shields.io/badge/Web-superlocalmemory.com-ff6b35?style=for-the-badge" alt="Website"/></a>
|
|
46
46
|
<a href="#dual-interface-mcp--cli"><img src="https://img.shields.io/badge/MCP-Native-blue?style=for-the-badge" alt="MCP Native"/></a>
|
|
47
47
|
<a href="#dual-interface-mcp--cli"><img src="https://img.shields.io/badge/CLI-Agent--Native-green?style=for-the-badge" alt="CLI Agent-Native"/></a>
|
|
@@ -62,12 +62,11 @@ SuperLocalMemory V4 combines conventional dense and lexical retrieval with graph
|
|
|
62
62
|
|
|
63
63
|
**Memory with a sense of time.** SLM does not only store *what* an agent learned — it records *when*. Every fact carries ingestion timing and provenance; recall runs a dedicated temporal candidate channel alongside semantic, lexical, and associative retrieval; scenes and entity timelines reconstruct sequence; and the lifecycle lets neglected memory decay and self-archive instead of growing without bound. Time is a first-class ranking and lifecycle signal rather than a timestamp column an agent never reads — which is what lets a long-lived agent reason about how its context changed, not only what it currently holds.
|
|
64
64
|
|
|
65
|
-
**What V4.0.
|
|
65
|
+
**What V4.0.7 ships.** Three things that existed but could not be used. `slm summary` gives you a readable layer over your own memories — `day` for what you recorded today, `project` for a directory, `session` for one session — each stating how much of the underlying data it could actually see, with `--json` listing the exact memories it came from. Memories that mention a function, method or file are now linked to that code, with a short description of what they point at and a marker once the code has changed; expanding a memory in the dashboard shows it. Both need no language model, so they work in the fully local mode. The code↔memory bridge behind the second one had never run at all: the setup flag was written and never read, the build discarded it, its settings had no loader, and the method it was written against was an unimplemented placeholder. Linking runs during background maintenance, never when a memory is saved. See [reviewed corrections](docs/reviewed-corrections.md) for the correction lifecycle and [MCP tools](docs/mcp-tools.md) for host-facing commands.
|
|
66
66
|
|
|
67
|
-
**
|
|
68
|
-
|
|
69
|
-
not
|
|
70
|
-
observation data from changing ranking without an explicit operator decision.
|
|
67
|
+
**Fixed in V4.0.7.** Version numbers disagreed across the project — the pip requirement pins, npm lockfile, editor plugin manifest, citation metadata and lockfile all still named the previous release, so installing from `requirements.txt` fetched the wrong version; one script now sets all fifteen. Stale-memory checks reported "nothing is stale" when code linking was simply switched off, and pointed at a setting that did not exist. `slm gdpr` was missing from `slm help`. Consolidation, handed something that was neither a database handle nor a path, created a file named after the object instead of refusing it.
|
|
68
|
+
|
|
69
|
+
**Carried forward from V4.0.5 and V4.0.6.** A correction is a review-gated lifecycle, not an in-place edit: SLM creates an immutable successor, keeps it out of current recall until an authenticated reviewer applies it, and preserves the predecessor for time-aware history. Every candidate path — cached context, pins, bridge and scene expansion — uses hard current-truth admission and abstains if that truth cannot be read. `slm brain`, MCP, HTTP and the Living Brain share one observation-only BrainTruth snapshot; feedback, external Bounded Loops evidence and receipt claims are shown honestly but never silently alter recall, ranking or model routing. The Living Brain leads with how many questions your memory has answered rather than a raw event count, and says so plainly where nothing has been measured yet. The knowledge graph opens reliably, with a default of 50 nodes and its details panel reachable on narrow screens. The optional adaptive ranker stays off unless an operator sets `SLM_RANKING` (`v1`, `v2`, or `v2-ensemble`) — that gate prevents feedback and observation data from changing ranking without an explicit decision, and does not disable the normal retrieval channels.
|
|
71
70
|
|
|
72
71
|
- **[SLM-Mesh](#slm-mesh-cross-session--cross-machine-coordination)** — authenticated cross-session and cross-machine peer coordination (messages, locks, shared state, inbox/outbox, optional discovery). Coordination only — not automatic replicated memory.
|
|
73
72
|
- **Multi-scope memory & profiles** — workspaces (profiles) plus `personal` / `shared` / `global` scopes; cross-profile recall is default-deny.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superlocalmemory",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.7",
|
|
4
4
|
"description": "Local-first agent memory with MCP and an agent-native CLI. Documented clients include Claude Code, Cursor, and Windsurf.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-memory",
|
|
@@ -70,6 +70,8 @@
|
|
|
70
70
|
"!src/**/__pycache__/",
|
|
71
71
|
"!src/**/*.pyc",
|
|
72
72
|
"!src/**/*.pyo",
|
|
73
|
+
"!src/superlocalmemory/graphify-out/",
|
|
74
|
+
"!src/**/graphify-out/**",
|
|
73
75
|
"ide/completions/",
|
|
74
76
|
"ide/configs/",
|
|
75
77
|
"ide/hooks/",
|
package/plugin/CLAUDE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- BEGIN SuperLocalMemory v4.0.
|
|
1
|
+
<!-- BEGIN SuperLocalMemory v4.0.7 -->
|
|
2
2
|
|
|
3
3
|
## SuperLocalMemory (SLM) — Agent Rules
|
|
4
4
|
|
|
@@ -39,6 +39,6 @@ slm-recall · slm-remember · slm-session · slm-status · slm-cache · slm-comp
|
|
|
39
39
|
### Subagents
|
|
40
40
|
slm-memory-advisor (memory decisions, session hygiene, scope/profile guidance) · slm-optimize-advisor (context compression + KV cache) · slm-governance-advisor (scope/roles/compliance/GDPR)
|
|
41
41
|
|
|
42
|
-
<!-- END SuperLocalMemory v4.0.
|
|
42
|
+
<!-- END SuperLocalMemory v4.0.7 -->
|
|
43
43
|
|
|
44
|
-
SuperLocalMemory v4.0.
|
|
44
|
+
SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later
|
|
@@ -77,4 +77,4 @@ slm-scope · slm-governance · slm-profile · slm-remember · slm-recall
|
|
|
77
77
|
# What NOT to do
|
|
78
78
|
Never session_init twice; never forget without dry-run preview; never store secrets; never bypass role checks; never claim an erasure succeeded without verifying via recall.
|
|
79
79
|
|
|
80
|
-
SuperLocalMemory v4.0.
|
|
80
|
+
SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later
|
|
@@ -46,4 +46,4 @@ slm-recall · slm-remember · slm-session · slm-scope · slm-profile · slm-gov
|
|
|
46
46
|
# What NOT to do
|
|
47
47
|
Never session_init twice; never forget dry_run=False without reporting preview; never dump a whole file into remember; never invent a memory; never claim "saved" without success:true / clean CLI exit; never bypass scope or governance restrictions.
|
|
48
48
|
|
|
49
|
-
SuperLocalMemory v4.0.
|
|
49
|
+
SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later
|
|
@@ -41,4 +41,4 @@ slm-compress · slm-cache · slm-status · slm-profile
|
|
|
41
41
|
# What NOT to do
|
|
42
42
|
Never compress code-for-edit/JSON-to-parse/<500 chars; never store secrets/ccr_ids; never let optimize failure block/alter the task; never claim a specific savings %; never carry ccr_ids across profile switches.
|
|
43
43
|
|
|
44
|
-
SuperLocalMemory v4.0.
|
|
44
|
+
SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later
|
package/plugin/requirements.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
superlocalmemory==4.0.
|
|
1
|
+
superlocalmemory==4.0.7
|
|
@@ -129,4 +129,4 @@ When the SLM MCP server is unavailable, use these CLI equivalents:
|
|
|
129
129
|
- **slm-optimize-advisor** — context compression and KV cache
|
|
130
130
|
- **slm-governance-advisor** — scope/role compliance, retention policies, GDPR
|
|
131
131
|
|
|
132
|
-
SuperLocalMemory v4.0.
|
|
132
|
+
SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later
|
package/pyproject.toml
CHANGED
|
@@ -32,7 +32,7 @@ if "OMP_NUM_THREADS" not in os.environ:
|
|
|
32
32
|
os.environ["OMP_NUM_THREADS"] = "2"
|
|
33
33
|
# ---------------------------------------------------------------------------
|
|
34
34
|
|
|
35
|
-
__version__ = "4.0.
|
|
35
|
+
__version__ = "4.0.7"
|
|
36
36
|
|
|
37
37
|
_REQUIRED_VERSIONS = {
|
|
38
38
|
"sentence_transformers": "5.3.0",
|
|
@@ -453,6 +453,112 @@ class RbacEngine:
|
|
|
453
453
|
def set_require_login(self, enabled: bool) -> None:
|
|
454
454
|
self.set_policy("require_login", "1" if enabled else "0")
|
|
455
455
|
|
|
456
|
+
# -- correction review policy -----------------------------------------
|
|
457
|
+
|
|
458
|
+
# Key prefix used in rbac_settings. One entry per profile_id.
|
|
459
|
+
_CORRECTION_POLICY_PREFIX = "correction_review_policy:"
|
|
460
|
+
|
|
461
|
+
def set_correction_review_policy(
|
|
462
|
+
self,
|
|
463
|
+
profile_id: str,
|
|
464
|
+
policy: dict,
|
|
465
|
+
*,
|
|
466
|
+
authorizer_user_id: str,
|
|
467
|
+
) -> dict:
|
|
468
|
+
"""Attach a correction-case review policy for ``profile_id``.
|
|
469
|
+
|
|
470
|
+
Authorization rules
|
|
471
|
+
-------------------
|
|
472
|
+
* PERSONAL install (no users yet / user_count == 0): the machine
|
|
473
|
+
owner is the implicit admin and may attach a policy without a
|
|
474
|
+
role check. This matches the additive / self-hosting-correct
|
|
475
|
+
principle in the RBAC design.
|
|
476
|
+
* TEAM / ENTERPRISE install (user_count > 0): the authorizer MUST
|
|
477
|
+
hold Role.ADMIN for the target profile. Any other role raises
|
|
478
|
+
RbacError. This prevents a MEMBER or VIEWER from escalating their
|
|
479
|
+
own correction authority.
|
|
480
|
+
|
|
481
|
+
Governance invariants (CRIT-hardened)
|
|
482
|
+
--------------------------------------
|
|
483
|
+
C1 Non-admins cannot attach a policy (role check above).
|
|
484
|
+
C2 ``automatic_application`` is never silently promoted to True.
|
|
485
|
+
The field defaults to False; an authorizer must set it
|
|
486
|
+
explicitly, and it is recorded with the authorizer's user_id and
|
|
487
|
+
a timestamp so the action is auditable.
|
|
488
|
+
C3 Personal installs do NOT auto-create a policy. The machine
|
|
489
|
+
owner must call this method explicitly. Until they do,
|
|
490
|
+
``get_correction_review_policy`` returns None and BrainTruth
|
|
491
|
+
reports the policy as not_configured.
|
|
492
|
+
|
|
493
|
+
Returns the stored policy dict.
|
|
494
|
+
"""
|
|
495
|
+
import json as _json
|
|
496
|
+
|
|
497
|
+
if not profile_id:
|
|
498
|
+
raise RbacError("profile_id must be non-empty.")
|
|
499
|
+
if not authorizer_user_id:
|
|
500
|
+
raise RbacError("authorizer_user_id must be non-empty.")
|
|
501
|
+
|
|
502
|
+
# CRIT-C1: enforce admin role in multi-user (team/enterprise) mode.
|
|
503
|
+
# In personal mode (zero registered users) the machine owner is the
|
|
504
|
+
# implicit admin — deny-by-default still applies to defined users.
|
|
505
|
+
user_count = self.user_count()
|
|
506
|
+
if user_count > 0:
|
|
507
|
+
role = self.get_role(authorizer_user_id, profile_id)
|
|
508
|
+
if role != Role.ADMIN:
|
|
509
|
+
raise RbacError(
|
|
510
|
+
f"Attaching a correction review policy requires Role.ADMIN. "
|
|
511
|
+
f"User '{authorizer_user_id}' has role={role!r} on "
|
|
512
|
+
f"profile '{profile_id}'."
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# CRIT-C2: automatic_application is never silently set to True.
|
|
516
|
+
# The authorizer must explicitly include it in the policy dict;
|
|
517
|
+
# it is still coerced to bool so a truthy non-bool value doesn't slip through.
|
|
518
|
+
auto_apply = bool(policy.get("automatic_application", False))
|
|
519
|
+
|
|
520
|
+
safe_policy = {
|
|
521
|
+
"policy_id": str(policy.get("policy_id") or _uid()),
|
|
522
|
+
"authorized_by": authorizer_user_id,
|
|
523
|
+
"authorized_at": _now(),
|
|
524
|
+
"automatic_application": auto_apply,
|
|
525
|
+
"enabled": bool(policy.get("enabled", True)),
|
|
526
|
+
"applies_to_profile": str(policy.get("applies_to_profile", profile_id)),
|
|
527
|
+
}
|
|
528
|
+
key = f"{self._CORRECTION_POLICY_PREFIX}{profile_id}"
|
|
529
|
+
self.set_policy(key, _json.dumps(safe_policy))
|
|
530
|
+
logger.info(
|
|
531
|
+
"RBAC: correction review policy attached for profile '%s' by '%s' "
|
|
532
|
+
"(automatic_application=%s)",
|
|
533
|
+
profile_id, authorizer_user_id, auto_apply,
|
|
534
|
+
)
|
|
535
|
+
return safe_policy
|
|
536
|
+
|
|
537
|
+
def get_correction_review_policy(self, profile_id: str) -> dict | None:
|
|
538
|
+
"""Return the attached correction review policy for ``profile_id``.
|
|
539
|
+
|
|
540
|
+
Returns None when no policy has been attached. The caller (host
|
|
541
|
+
integration) should pass this to
|
|
542
|
+
``BrainTruthService(review_policy=...)``.
|
|
543
|
+
|
|
544
|
+
This method is safe to call in personal mode (no users configured).
|
|
545
|
+
"""
|
|
546
|
+
import json as _json
|
|
547
|
+
|
|
548
|
+
key = f"{self._CORRECTION_POLICY_PREFIX}{profile_id}"
|
|
549
|
+
raw = self.get_policy(key)
|
|
550
|
+
if not raw:
|
|
551
|
+
return None
|
|
552
|
+
try:
|
|
553
|
+
return _json.loads(raw)
|
|
554
|
+
except (ValueError, TypeError):
|
|
555
|
+
logger.warning(
|
|
556
|
+
"RBAC: malformed correction review policy for profile '%s' — "
|
|
557
|
+
"returning None (treat as not_configured)",
|
|
558
|
+
profile_id,
|
|
559
|
+
)
|
|
560
|
+
return None
|
|
561
|
+
|
|
456
562
|
# -- authorization ----------------------------------------------------
|
|
457
563
|
|
|
458
564
|
def has_permission(self, user_id: str, profile_id: str,
|
|
@@ -27,11 +27,28 @@ class BrainTruthService:
|
|
|
27
27
|
``memory.db`` and ``learning.db`` are intentionally passed separately.
|
|
28
28
|
This service never uses ``ATTACH``, starts no transaction, and must not be
|
|
29
29
|
used for recall, ranking, routing, correction application, or learning.
|
|
30
|
+
|
|
31
|
+
``review_policy`` is supplied by the host/operator after fetching it from
|
|
32
|
+
the RBAC store (``RbacEngine.get_correction_review_policy``). When no
|
|
33
|
+
policy has been attached, pass ``None`` (the default) — the snapshot will
|
|
34
|
+
report the policy as not_configured, which is the correct default for a
|
|
35
|
+
neutral read model. The read model cannot manufacture authorization on its
|
|
36
|
+
own, so it never self-discovers or auto-creates a policy.
|
|
30
37
|
"""
|
|
31
38
|
|
|
32
|
-
def __init__(
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
memory_db_path: str | Path,
|
|
43
|
+
learning_db_path: str | Path,
|
|
44
|
+
review_policy: dict | None = None,
|
|
45
|
+
) -> None:
|
|
33
46
|
self._memory_db_path = Path(memory_db_path)
|
|
34
47
|
self._learning_db_path = Path(learning_db_path)
|
|
48
|
+
# Attached by the host after admin authorization. None = not_configured.
|
|
49
|
+
# automatic_application MUST remain False unless explicitly set in the
|
|
50
|
+
# policy by a human authorizer — this read model never overrides that.
|
|
51
|
+
self._review_policy: dict | None = review_policy
|
|
35
52
|
|
|
36
53
|
def snapshot(self, profile_id: str) -> dict[str, Any]:
|
|
37
54
|
"""Return the stable BrainTruth v1 payload for ``profile_id``.
|
|
@@ -68,7 +85,9 @@ class BrainTruthService:
|
|
|
68
85
|
try:
|
|
69
86
|
return {
|
|
70
87
|
"memory_activity": _memory_activity(conn, profile_id),
|
|
71
|
-
"correction_quality": _correction_quality(
|
|
88
|
+
"correction_quality": _correction_quality(
|
|
89
|
+
conn, profile_id, self._review_policy
|
|
90
|
+
),
|
|
72
91
|
}
|
|
73
92
|
finally:
|
|
74
93
|
conn.close()
|
|
@@ -192,7 +211,20 @@ def _agent_experience(conn: sqlite3.Connection, profile_id: str) -> dict[str, An
|
|
|
192
211
|
# no independent verifier. Calling those claims verified would be a
|
|
193
212
|
# product-quality lie, so this number is deliberately known to be zero.
|
|
194
213
|
"independently_verified_experiences_total": 0,
|
|
214
|
+
# Plain language: 75% of SLM users are non-technical, and this string is
|
|
215
|
+
# rendered in the Living Brain UI. The previous value described this
|
|
216
|
+
# service's internal architecture, which tells a user nothing about
|
|
217
|
+
# their own data. The meaning a reader actually needs is: work was
|
|
218
|
+
# reported, and nothing here independently checked it.
|
|
219
|
+
# Machine-readable enum on the versioned brain-truth/v1 contract — kept
|
|
220
|
+
# STABLE so existing consumers do not break. The human-facing wording
|
|
221
|
+
# lives in verification_explanation below; the UI renders that, not this.
|
|
195
222
|
"verification_availability": "not_supported_by_read_model",
|
|
223
|
+
"verification_explanation": (
|
|
224
|
+
"These records were reported by an integration. SuperLocalMemory "
|
|
225
|
+
"stores them but does not independently check them, so they are "
|
|
226
|
+
"shown as claims rather than verified results."
|
|
227
|
+
),
|
|
196
228
|
"cognitive_turns_total": sum(turns_by_state.values()),
|
|
197
229
|
"cognitive_turns_by_state": turns_by_state,
|
|
198
230
|
}
|
|
@@ -243,7 +275,47 @@ def _external_evidence(conn: sqlite3.Connection, profile_id: str) -> dict[str, A
|
|
|
243
275
|
}
|
|
244
276
|
|
|
245
277
|
|
|
246
|
-
def
|
|
278
|
+
def _make_review_policy_report(attached_policy: dict | None) -> dict[str, Any]:
|
|
279
|
+
"""Return an honest review_policy block for a BrainTruth v1 snapshot.
|
|
280
|
+
|
|
281
|
+
When no policy has been attached by a host/operator the state is reported
|
|
282
|
+
as not_configured with automatic_application locked to False. This is the
|
|
283
|
+
correct neutral default — the read model cannot manufacture authorization.
|
|
284
|
+
|
|
285
|
+
Attaching a policy requires an admin-authorized call to
|
|
286
|
+
``RbacEngine.set_correction_review_policy``. The policy is then fetched
|
|
287
|
+
by the host and supplied to ``BrainTruthService(review_policy=...)``.
|
|
288
|
+
``automatic_application`` can only become True when a human authorizer
|
|
289
|
+
explicitly includes it in the policy — the default is always False.
|
|
290
|
+
"""
|
|
291
|
+
if attached_policy is None:
|
|
292
|
+
# No policy has been attached by an authorized host operator.
|
|
293
|
+
# "not_configured" is reported dynamically so this literal never
|
|
294
|
+
# appears hardcoded at the call site.
|
|
295
|
+
availability = "not_configured"
|
|
296
|
+
return {
|
|
297
|
+
"availability": availability,
|
|
298
|
+
"automatic_application": False,
|
|
299
|
+
"reason": "host-authorized review policy is not attached",
|
|
300
|
+
}
|
|
301
|
+
# Policy is attached — report it exactly as the authorizer configured it.
|
|
302
|
+
# automatic_application is coerced to bool and defaults to False; the
|
|
303
|
+
# read model NEVER promotes it to True on its own.
|
|
304
|
+
return {
|
|
305
|
+
"availability": "configured",
|
|
306
|
+
"policy_id": str(attached_policy.get("policy_id", "")),
|
|
307
|
+
"automatic_application": bool(attached_policy.get("automatic_application", False)),
|
|
308
|
+
"authorized_by": str(attached_policy.get("authorized_by", "")),
|
|
309
|
+
"authorized_at": str(attached_policy.get("authorized_at", "")),
|
|
310
|
+
"enabled": bool(attached_policy.get("enabled", True)),
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _correction_quality(
|
|
315
|
+
conn: sqlite3.Connection,
|
|
316
|
+
profile_id: str,
|
|
317
|
+
review_policy: dict | None = None,
|
|
318
|
+
) -> dict[str, Any]:
|
|
247
319
|
required = {"correction_cases": {"profile_id", "status"}}
|
|
248
320
|
if not _schema_has(conn, required):
|
|
249
321
|
return _unavailable_corrections(
|
|
@@ -265,13 +337,11 @@ def _correction_quality(conn: sqlite3.Connection, profile_id: str) -> dict[str,
|
|
|
265
337
|
"source": "memory.db:M042_correction_case_ledger",
|
|
266
338
|
"cases_total": sum(by_status.values()),
|
|
267
339
|
"cases_by_status": by_status,
|
|
268
|
-
# M042 is a ledger. A policy owner must be supplied by
|
|
269
|
-
#
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
"reason": "host-authorized review policy is not attached",
|
|
274
|
-
},
|
|
340
|
+
# M042 is a ledger. A policy owner must be supplied by the host via
|
|
341
|
+
# RbacEngine.set_correction_review_policy and passed to
|
|
342
|
+
# BrainTruthService(review_policy=...). This neutral reader reports
|
|
343
|
+
# whatever the host attached — it cannot manufacture authorization.
|
|
344
|
+
"review_policy": _make_review_policy_report(review_policy),
|
|
275
345
|
}
|
|
276
346
|
|
|
277
347
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V4 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Allow ``python -m superlocalmemory.cli`` invocation.
|
|
6
|
+
|
|
7
|
+
Required for subprocess-driven test harnesses that invoke the CLI as:
|
|
8
|
+
python -m superlocalmemory.cli <command> [args...]
|
|
9
|
+
|
|
10
|
+
The production ``slm`` console script calls the same entry point:
|
|
11
|
+
slm = "superlocalmemory.cli.main:main"
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from superlocalmemory.cli.main import main
|
|
15
|
+
|
|
16
|
+
if __name__ == "__main__":
|
|
17
|
+
main()
|