superlocalmemory 3.6.10 → 3.6.11
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 +34 -0
- package/README.md +60 -6
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/slm-optimize/README.md +55 -0
- package/skills/slm-optimize/SKILL.md +139 -0
- package/src/superlocalmemory/mcp/server.py +4 -0
- package/src/superlocalmemory/mcp/tools_optimize.py +304 -0
- package/src/superlocalmemory/optimize/storage/db.py +24 -0
- package/src/superlocalmemory.egg-info/PKG-INFO +61 -7
- package/src/superlocalmemory.egg-info/SOURCES.txt +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ All notable changes to SuperLocalMemory V3 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
|
+
## [3.6.11] - 2026-06-14 — Optimize Everywhere: three surfaces (proxy · MCP tools · skill)
|
|
9
|
+
|
|
10
|
+
Cache + compress across **every setup** — proxy, MCP tools, or skill. Five new MCP tools land directly inside `slm mcp` (no proxy, full 1M context window preserved). A new `slm-optimize` skill makes compression and routed-result caching zero-config for Claude Code users. Overclaim in prior docs fixed; three-surfaces table added.
|
|
11
|
+
|
|
12
|
+
### Added — Surface B: MCP Optimize Tools (proxy-free)
|
|
13
|
+
|
|
14
|
+
Five new tools registered in `slm mcp` from v3.6.11+:
|
|
15
|
+
|
|
16
|
+
- **`slm_compress`** — compress text or tool output. `mode=normalize` is lossless whitespace normalization; `mode=auto`/`aggressive` delegates to the existing CompressRouter engine. Lossy + reversible stores the original in CCR and returns a `ccr_id`. Content >1 MB is stored unreversed. All fail-open.
|
|
17
|
+
- **`slm_retrieve`** — recover the exact original bytes from a `ccr_id` UUID4 returned by `slm_compress`. Validates UUID format before lookup.
|
|
18
|
+
- **`slm_cache_set`** — cache any string result the agent routes through SLM (file reads, bash output, search results, sub-model calls). Key is SHA-256 namespaced `mcpkv:{agent_id}:{key}` — no cross-agent collisions. TTL default 24 h.
|
|
19
|
+
- **`slm_cache_get`** — retrieve a cached result by key. Returns `hit:True/False`. Increments in-module `_kv_hits`/`_kv_misses` counters for stats.
|
|
20
|
+
- **`slm_optimize_stats`** — session compression + cache statistics. Proxy counters read from `CacheDB.metrics_load()` (daemon-persisted, accurate across restarts); KV counters are in-module for this MCP process session.
|
|
21
|
+
|
|
22
|
+
All five are **fail-open**: any internal error returns `{ok: False, note: <error>}` with the original content unchanged — never raises to the agent.
|
|
23
|
+
|
|
24
|
+
### Added — Surface C: slm-optimize Skill (zero-config)
|
|
25
|
+
|
|
26
|
+
- **`skills/slm-optimize/SKILL.md`** and **`ide/skills/slm-optimize/SKILL.md`** — agent-behavior instruction file. Eight behavioral rules: compress CLAUDE.md at session start, compress large tool outputs via `slm_compress`, KV-cache repeated file reads and bash/search results, recover originals with `slm_retrieve`, never-compress list (secrets, JSON, code/Edit/Write outputs), emit stats on request, fail-open on `ok:False`. Compatible with Claude Code, Cursor, Antigravity, Codex, and any IDE that supports MCP tool calls.
|
|
27
|
+
- **`skills/slm-optimize/README.md`** — human install guide: prerequisites, install steps, activation options, feature table, and explicit "what it does NOT do" section.
|
|
28
|
+
|
|
29
|
+
### Changed — `CacheDB` (new public method)
|
|
30
|
+
|
|
31
|
+
- **`CacheDB.get_value(cache_key, tenant_id)`** added to `optimize/storage/db.py` — pure `SELECT` lookup with no `hit_count` side-effect (unlike `get()`). Used by `slm_cache_get` to avoid inflating proxy cache counters when agents read KV entries. Fail-open: returns `None` on any SQLite/decryption/zlib error.
|
|
32
|
+
|
|
33
|
+
### Changed — MCP server registration
|
|
34
|
+
|
|
35
|
+
- Five optimize tool names added to `_ESSENTIAL_TOOLS` frozenset in `mcp/server.py` (v3.6.11 Surface B). Without this the `_FilteredServer` wrapper silently drops tools whose names are not in the set.
|
|
36
|
+
- `register_optimize_tools(_target)` call added after other `register_*` calls.
|
|
37
|
+
|
|
38
|
+
### Fixed — Overclaim in README / docs
|
|
39
|
+
|
|
40
|
+
- Replaced "Save up to 90% on every LLM API call" headline with accurate three-surfaces framing: proxy (full-turn caching on metered API), MCP tools (routed-result caching + compression), skill (auto-applied compression). Hard constraint documented: primary Claude conversation turn cannot be cached without a proxy — never implied otherwise.
|
|
41
|
+
|
|
8
42
|
## [3.6.10] - 2026-06-14 — Optimize correctness (cache + lossless compression) · MCP per-agent identity · runtime toggles · benchmark + shadow-capture · Issue #38
|
|
9
43
|
|
|
10
44
|
This release makes the **Optimize** subsystem (the HTTP proxy that caches and compresses LLM API calls) correct, observable, and **independently controllable at runtime**, adds **per-agent identity** to the HTTP MCP transport, ships a **benchmark + shadow-capture** harness that proves the cache and compression behaviour, and fixes GitHub issue #38. Cache and compression remain **default-OFF** and are now separately toggleable from the dashboard.
|
package/README.md
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
<img src="https://superlocalmemory.com/assets/logo-mark.png" alt="SuperLocalMemory" width="200"/>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
<h1 align="center">SuperLocalMemory V3.6</h1>
|
|
6
|
-
<p align="center"><strong>
|
|
7
|
-
<p align="center"><code>v3.6.
|
|
5
|
+
<h1 align="center">SuperLocalMemory V3.6.11</h1>
|
|
6
|
+
<p align="center"><strong>Cache. Compress. Remember. Three surfaces — proxy, MCP tools, or skill. Every setup covered.</strong><br/><em>The only local-first layer that pairs persistent memory with compression + caching across every Claude plan. Full 1M window preserved in MCP and skill mode.</em></p>
|
|
7
|
+
<p align="center"><code>v3.6.11 "Optimize Everywhere"</code> — <strong>Compress + cache on any plan, three ways in.</strong><br/>Proxy (full-turn cache): <code>slm wrap claude</code> · MCP (proxy-free): add <code>slm_compress</code> to your MCP config · Skill (zero-config): <code>~/.claude/skills/slm-optimize/</code></p>
|
|
8
8
|
<p align="center"><strong>Backed by 3 published research papers</strong> (arXiv preprints + Zenodo-archived) · <a href="https://arxiv.org/abs/2603.02240">arXiv:2603.02240</a> · <a href="https://arxiv.org/abs/2603.14588">arXiv:2603.14588</a> · <a href="https://arxiv.org/abs/2604.04514">arXiv:2604.04514</a></p>
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
|
-
<code>
|
|
11
|
+
<code>Proxy · MCP tools · Skill — three surfaces</code> · <code>+10.6pp vs Mem0 zero-LLM</code> · <code>85% Open-Domain (best zero-LLM score)</code> · <code>EU AI Act Ready</code>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
<p align="center">
|
|
15
15
|
<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>
|
|
16
|
-
<a href="
|
|
16
|
+
<a href="#three-surfaces-proxy--mcp-tools--skill"><img src="https://img.shields.io/badge/Proxy_|_MCP_|_Skill-22c55e?style=for-the-badge" alt="Three Surfaces: Proxy, MCP Tools, Skill"/></a>
|
|
17
17
|
<a href="https://pypi.org/project/superlocalmemory/"><img src="https://img.shields.io/pypi/v/superlocalmemory?style=for-the-badge&logo=pypi&logoColor=white" alt="PyPI"/></a>
|
|
18
18
|
<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>
|
|
19
19
|
<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>
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
<details>
|
|
34
34
|
<summary><strong>What's New in V3.6 — Optimize: SKIP, SHRINK, DISCOUNT, REMEMBER</strong> (click to expand)</summary>
|
|
35
35
|
|
|
36
|
-
> V3.6 is the only local-first layer that SKIPS repeat LLM calls (cache: 100%
|
|
36
|
+
> V3.6 is the only local-first layer that SKIPS repeat LLM calls (cache: 100% on a hit), SHRINKS tool outputs and injected context (compress: lossless-by-default, opt-in LLMLingua-2), and DISCOUNTS prefix costs (align: native KV-cache) — and remembers everything — in one install.
|
|
37
|
+
>
|
|
38
|
+
> **v3.6.11 "Optimize Everywhere":** Three surfaces. **Proxy** (Surface A) — full-turn cache + compress on transport; needs `ANTHROPIC_BASE_URL`, shrinks the context window. **MCP tools** (Surface B) — `slm_compress`, `slm_retrieve`, `slm_cache_set`, `slm_cache_get`, `slm_optimize_stats`; no proxy, no window shrink, works on any Claude subscription. **Skill** (Surface C) — `slm-optimize` installs in `~/.claude/skills/`; zero-config auto-compress for large tool outputs and CLAUDE.md. No proxy, full 1M window. [See Three Surfaces →](#three-surfaces-proxy--mcp-tools--skill)
|
|
37
39
|
>
|
|
38
40
|
> **v3.6.10:** cache and compression are now **independent runtime switches** (cache-only, compress-only, both, or neither — toggle live from the dashboard, no restart). Compression was rebuilt to be **lossless by default** (the old string/array/code truncation is gone); aggressive mode adds LLMLingua-2 for **prose only** — never code, numbers, structured data, or the current turn.
|
|
39
41
|
|
|
@@ -120,6 +122,57 @@ Full docs:
|
|
|
120
122
|
|
|
121
123
|
---
|
|
122
124
|
|
|
125
|
+
## Three Surfaces: Proxy · MCP Tools · Skill
|
|
126
|
+
|
|
127
|
+
v3.6.11 delivers one engine across **three ways in** — choose the surface that fits your setup:
|
|
128
|
+
|
|
129
|
+
| Surface | How you use it | Requires proxy? | Window effect | Cache scope |
|
|
130
|
+
|---------|---------------|:---------------:|:-------------:|-------------|
|
|
131
|
+
| **A — Proxy** | `slm wrap claude` or `ANTHROPIC_BASE_URL=http://127.0.0.1:8765` | **Yes** | Shrinks (proxy intercepts full context) | Full-turn cache — every Claude call |
|
|
132
|
+
| **B — MCP tools** | Add 5 tools to MCP config; call `slm_compress`, `slm_cache_set/get` | **No** | **Preserved** (full 1M) | Results you explicitly route through SLM |
|
|
133
|
+
| **C — Skill** | Copy `skills/slm-optimize/SKILL.md` → `~/.claude/skills/` | **No** | **Preserved** (full 1M) | Auto-applied by the agent per skill rules |
|
|
134
|
+
|
|
135
|
+
**How to choose:**
|
|
136
|
+
- On a **metered API** (pay-per-token) and want to cache every call → **Proxy (A)**
|
|
137
|
+
- On a **Pro/Max/Team subscription** or any plan where you can't or won't run a proxy → **MCP tools (B)** or **Skill (C)**
|
|
138
|
+
- Want zero configuration → **Skill (C)**: install once, auto-compresses CLAUDE.md and large outputs
|
|
139
|
+
- Want agent-controlled caching of repeated file reads and tool outputs → **MCP tools (B)**
|
|
140
|
+
|
|
141
|
+
**The hard constraint:** The primary Claude conversation turn cannot be cached without a proxy — the MCP/skill path caches results you explicitly route through SLM (tool outputs, file reads, sub-model calls).
|
|
142
|
+
|
|
143
|
+
### MCP Tools Setup (Surface B)
|
|
144
|
+
|
|
145
|
+
Add to your `claude_desktop_config.json` or IDE MCP config alongside your existing SLM entry:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"mcpServers": {
|
|
150
|
+
"superlocalmemory": {
|
|
151
|
+
"command": "slm",
|
|
152
|
+
"args": ["mcp"]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The 5 optimize tools (`slm_compress`, `slm_retrieve`, `slm_cache_set`, `slm_cache_get`, `slm_optimize_stats`) are included automatically from v3.6.11+. Verify with `slm_optimize_stats()`.
|
|
159
|
+
|
|
160
|
+
### Skill Setup (Surface C)
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
mkdir -p ~/.claude/skills/slm-optimize
|
|
164
|
+
cp $(pip show superlocalmemory | grep Location | awk '{print $2}')/superlocalmemory/skills/slm-optimize/SKILL.md \
|
|
165
|
+
~/.claude/skills/slm-optimize/SKILL.md
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Then reference in your `CLAUDE.md`:
|
|
169
|
+
```markdown
|
|
170
|
+
## Context Management
|
|
171
|
+
Use the `slm-optimize` skill to compress large outputs and cache repeated reads.
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
123
176
|
<details>
|
|
124
177
|
<summary><strong>What's New in V3.3 — The Living Brain Evolves</strong> (click to expand)</summary>
|
|
125
178
|
|
|
@@ -296,6 +349,7 @@ No manual commands. No data loss. Zero downtime.
|
|
|
296
349
|
|
|
297
350
|
| Version | Codename | Key Features |
|
|
298
351
|
|---|---|---|
|
|
352
|
+
| **v3.6.11** | Optimize Everywhere | **Three surfaces** — Proxy (A: full-turn cache), MCP tools (B: `slm_compress`/`slm_retrieve`/`slm_cache_set`/`slm_cache_get`/`slm_optimize_stats` — proxy-free, 1M window), Skill (C: `slm-optimize` zero-config). `CacheDB.get_value()` (pure KV lookup). 23 new tests. Links: [Three Surfaces →](#three-surfaces-proxy--mcp-tools--skill) · [docs/optimize-overview.md](docs/optimize-overview.md) |
|
|
299
353
|
| **v3.6.0** | Optimize | **Cache** (skip repeat calls, 100% on hit) · **Compress** (shrink prompts 60-95%) · **Align** (KV-cache stabilization) · `slm optimize\|cache\|compress\|proxy\|wrap` CLI · Live savings dashboard (USD/INR/tokens) · Hot-reload config · Safe defaults · Links: [docs/optimize-overview.md](docs/optimize-overview.md) · [V3.6 Wiki](https://github.com/qualixar/superlocalmemory/wiki/V3.6-Overview) |
|
|
300
354
|
| **v3.5.0** | Scale-Ready + Context Injection v2 | CozoDB/LanceDB migration, 6-channel recall <1s, Core Memory Block, BM25→FTS5, context injection v2, score normalization |
|
|
301
355
|
| **v3.4.5** | Scale-Ready (foundation) | Tiered storage (active/warm/cold), graph pruning, BackendOrchestrator scaffolding, CozoDB + LanceDB init + migration code (read path wired in v3.5.0) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superlocalmemory",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.11",
|
|
4
4
|
"description": "Information-geometric agent memory with mathematical guarantees. 4-channel retrieval, Fisher-Rao similarity, zero-LLM mode, EU AI Act compliant. Works with Claude, Cursor, Windsurf, and 17+ AI tools.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-memory",
|
package/pyproject.toml
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# slm-optimize — Context Optimizer Skill
|
|
2
|
+
|
|
3
|
+
Automatically compresses large tool outputs and caches repeated reads, reducing
|
|
4
|
+
context window usage without a proxy and without losing the full 1M window.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
- SuperLocalMemory v3.6.11+
|
|
9
|
+
- `slm mcp` daemon running
|
|
10
|
+
- `slm_compress` visible in `tools/list` (verify with your IDE's MCP tool inspector)
|
|
11
|
+
|
|
12
|
+
## Install (Claude Code)
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cp skills/slm-optimize/SKILL.md ~/.claude/skills/slm-optimize/SKILL.md
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Activate
|
|
19
|
+
|
|
20
|
+
**Option A — On demand:**
|
|
21
|
+
Invoke via the `Skill` tool: `Skill("slm-optimize")`
|
|
22
|
+
|
|
23
|
+
**Option B — Auto-activate:**
|
|
24
|
+
Add to your project or global `CLAUDE.md`:
|
|
25
|
+
```markdown
|
|
26
|
+
## Context Management
|
|
27
|
+
Use the `slm-optimize` skill to compress large outputs and cache repeated reads.
|
|
28
|
+
Invoke at session start if context > 50k tokens.
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Verify
|
|
32
|
+
|
|
33
|
+
After activation, run `slm_optimize_stats()` from Claude Code. If it returns `ok:True`,
|
|
34
|
+
all 5 optimize tools are reachable and the skill is working.
|
|
35
|
+
|
|
36
|
+
## What it does
|
|
37
|
+
|
|
38
|
+
| Feature | How |
|
|
39
|
+
|---|---|
|
|
40
|
+
| Compresses large tool outputs | `slm_compress` → compressed text + optional `ccr_id` |
|
|
41
|
+
| Caches repeated file reads | `slm_cache_set` / `slm_cache_get` keyed by file path |
|
|
42
|
+
| Caches repeated bash/search | Same KV tools, keyed by command |
|
|
43
|
+
| Recovers exact originals | `slm_retrieve(ccr_id)` when byte-identical content needed |
|
|
44
|
+
| Session stats | `slm_optimize_stats()` |
|
|
45
|
+
|
|
46
|
+
## What it does NOT do
|
|
47
|
+
|
|
48
|
+
- **Full-turn caching**: impossible without a proxy (`ANTHROPIC_BASE_URL`).
|
|
49
|
+
Use Surface A (proxy mode) for that.
|
|
50
|
+
- **Guarantee savings**: results depend on content type and daemon compress config.
|
|
51
|
+
|
|
52
|
+
## For Cursor, Antigravity, Codex
|
|
53
|
+
|
|
54
|
+
Same SKILL.md works — all IDEs that support MCP tool calls can use the 5 tools.
|
|
55
|
+
The skill itself is pure Markdown with no IDE-specific code.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slm-optimize
|
|
3
|
+
description: >
|
|
4
|
+
Context optimizer for Claude Code + SLM. Compresses large outputs via
|
|
5
|
+
slm_compress (reversible CCR), caches repeated reads via slm_cache_set/get,
|
|
6
|
+
recovers originals via slm_retrieve. Works on any Claude plan, no proxy,
|
|
7
|
+
full context window preserved.
|
|
8
|
+
Requires SuperLocalMemory v3.6.11+ with optimize MCP tools enabled.
|
|
9
|
+
version: "3.6.11"
|
|
10
|
+
license: AGPL-3.0-or-later
|
|
11
|
+
triggers:
|
|
12
|
+
- "slm-optimize"
|
|
13
|
+
- "compress context"
|
|
14
|
+
- "optimize context"
|
|
15
|
+
- "reduce tokens"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# slm-optimize: Context Optimizer for Claude Code
|
|
19
|
+
|
|
20
|
+
Reduces context window usage through compression and caching.
|
|
21
|
+
No proxy required. Full 1M window preserved.
|
|
22
|
+
|
|
23
|
+
**What this skill does NOT do:**
|
|
24
|
+
- Cache the primary Claude conversation turn (impossible without a proxy).
|
|
25
|
+
- Guarantee any specific savings percentage — results depend on content type and daemon config.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## When This Skill Is Active
|
|
30
|
+
|
|
31
|
+
Apply these rules automatically throughout the session once this skill is loaded.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Decision Rules
|
|
36
|
+
|
|
37
|
+
### RULE 1 — Session start: compress CLAUDE.md if large
|
|
38
|
+
|
|
39
|
+
At the start of any session where this skill is loaded:
|
|
40
|
+
1. Read `CLAUDE.md` (or project CLAUDE.md).
|
|
41
|
+
2. If `len(content) > 3000` characters: call `slm_compress(content=<text>, mode="normalize", reversible=False)`.
|
|
42
|
+
3. Replace the in-context CLAUDE.md block with the compressed version.
|
|
43
|
+
4. **Do not modify the actual file** — compress only the in-context representation.
|
|
44
|
+
|
|
45
|
+
### RULE 2 — Large tool output: compress before processing
|
|
46
|
+
|
|
47
|
+
After any tool call that returns > 2000 characters:
|
|
48
|
+
1. Call `slm_compress(content=<output>, mode="auto", reversible=True)`.
|
|
49
|
+
2. Work from `compressed` in the response.
|
|
50
|
+
3. If `lossy=True`, store the returned `ccr_id` — call `slm_retrieve` only if you need the exact original for verification, diffing, or code parsing.
|
|
51
|
+
|
|
52
|
+
**Never compress** (see RULE 6 for the complete exclusion list).
|
|
53
|
+
|
|
54
|
+
### RULE 3 — Repeated file reads: KV cache
|
|
55
|
+
|
|
56
|
+
Before calling `Read(file_path)` for a file you have already read this session:
|
|
57
|
+
1. `slm_cache_get(key=f"file:{file_path}")`.
|
|
58
|
+
2. If `hit=True` → use `value` directly. Skip the `Read` call.
|
|
59
|
+
3. If `hit=False` → call `Read`, then `slm_cache_set(key=f"file:{file_path}", value=<content>, ttl_seconds=1800)`.
|
|
60
|
+
|
|
61
|
+
If you edit a file via Edit/Write, immediately update or invalidate the cache entry.
|
|
62
|
+
|
|
63
|
+
### RULE 4 — Repeated bash/search results: KV cache
|
|
64
|
+
|
|
65
|
+
For any Bash or WebSearch call whose result you expect to reuse:
|
|
66
|
+
1. `slm_cache_set(key=f"bash:{command[:64]}", value=<output>, ttl_seconds=600)`.
|
|
67
|
+
2. On the next identical call: `slm_cache_get` first.
|
|
68
|
+
|
|
69
|
+
Use `ttl_seconds=3600` for web search results (more expensive to re-run).
|
|
70
|
+
|
|
71
|
+
### RULE 5 — Retrieve original via slm_retrieve
|
|
72
|
+
|
|
73
|
+
If you compressed content with `lossy=True` and need the exact original:
|
|
74
|
+
1. Call `slm_retrieve(ccr_id=<ccr_id from prior compress response>)`.
|
|
75
|
+
2. Check `size_bytes` before retrieving if context is nearly full.
|
|
76
|
+
3. Do not retrieve unless you actually need byte-identical content.
|
|
77
|
+
|
|
78
|
+
### RULE 6 — What NOT to compress or cache
|
|
79
|
+
|
|
80
|
+
- **Code being sent to Edit/Write**: never compress — the editor needs exact bytes.
|
|
81
|
+
- **JSON or structured data you will parse**: compression can alter whitespace and break parsers.
|
|
82
|
+
- **Secrets, API keys, credentials, tokens**: never store via `slm_cache_set`.
|
|
83
|
+
- **CCR IDs**: never cache via `slm_cache_set` — treat as ephemeral session values.
|
|
84
|
+
- **Very short outputs (< 500 chars)**: compression overhead exceeds the gain.
|
|
85
|
+
|
|
86
|
+
### RULE 7 — Stats and awareness
|
|
87
|
+
|
|
88
|
+
If the user asks "how much context have you saved?" or "what is the optimization status?":
|
|
89
|
+
1. Call `slm_optimize_stats()`.
|
|
90
|
+
2. Report `cache_kv_hits`, `compress_runs`, and `tokens_saved_compress` clearly.
|
|
91
|
+
3. Note that proxy stats are daemon-persisted; KV stats are this session only.
|
|
92
|
+
|
|
93
|
+
### RULE 8 — Fail-open behavior
|
|
94
|
+
|
|
95
|
+
If any `slm_compress`, `slm_retrieve`, `slm_cache_set`, `slm_cache_get`, or
|
|
96
|
+
`slm_optimize_stats` call returns `ok:False`:
|
|
97
|
+
- Continue with the original (uncompressed/uncached) content.
|
|
98
|
+
- Do not retry. Do not surface the error unless the user asks about optimization.
|
|
99
|
+
- Optimization is always advisory — it must never block any primary task.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Examples
|
|
104
|
+
|
|
105
|
+
### Compress a large file read
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
# Instead of working with 8000-char file output directly:
|
|
109
|
+
result = slm_compress(content=<file_content>, mode="auto", reversible=True)
|
|
110
|
+
# Work from result["compressed"]; keep result["ccr_id"] if lossy=True
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Cache a repeated grep result
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
cached = slm_cache_get(key="bash:grep -rn MyClass src/")
|
|
117
|
+
if not cached["hit"]:
|
|
118
|
+
output = Bash("grep -rn MyClass src/")
|
|
119
|
+
slm_cache_set(key="bash:grep -rn MyClass src/", value=output, ttl_seconds=600)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Recover original when needed
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
original = slm_retrieve(ccr_id=<ccr_id>)
|
|
126
|
+
# original["content"] is byte-identical to what was compressed
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Stats and Troubleshooting
|
|
132
|
+
|
|
133
|
+
Run `slm_optimize_stats()` to see current session counters.
|
|
134
|
+
|
|
135
|
+
If tools return `ok:False`, the daemon may be down or the optimize module may not be
|
|
136
|
+
configured. Continue with normal tool calls — this skill degrades gracefully.
|
|
137
|
+
|
|
138
|
+
Verify tools are available: check that `slm_compress` appears in your MCP `tools/list`.
|
|
139
|
+
Requires SuperLocalMemory v3.6.11+ with `slm mcp` running.
|
|
@@ -104,6 +104,8 @@ _ESSENTIAL_TOOLS: set[str] = {
|
|
|
104
104
|
"reinforce_assertion", "contradict_assertion",
|
|
105
105
|
# v3.4.11: Skill evolution (3)
|
|
106
106
|
"evolve_skill", "skill_health", "skill_lineage",
|
|
107
|
+
# v3.6.11: Surface B Optimize tools (5)
|
|
108
|
+
"slm_compress", "slm_retrieve", "slm_cache_set", "slm_cache_get", "slm_optimize_stats",
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
# v3.4.4: Mesh tools — enabled if mesh_enabled in config or SLM_MCP_MESH_TOOLS=1
|
|
@@ -189,6 +191,8 @@ register_code_graph_tools(_target, get_engine) # CodeGraph: filtered like other
|
|
|
189
191
|
register_mesh_tools(_target, get_engine) # v3.4.4: Mesh P2P tools — ships with SLM, no separate slm-mesh needed
|
|
190
192
|
register_learning_tools(_target, get_engine) # v3.4.7: Two-way learning tools
|
|
191
193
|
register_evolution_tools(_target, get_engine) # v3.4.11: Skill evolution tools
|
|
194
|
+
from superlocalmemory.mcp.tools_optimize import register_optimize_tools
|
|
195
|
+
register_optimize_tools(_target) # v3.6.11: Surface B Optimize tools (proxy-free)
|
|
192
196
|
|
|
193
197
|
|
|
194
198
|
# V3.3.21: Eager engine warmup — start initializing BEFORE first tool call.
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""SLM v3.6.11 — Surface B: MCP Optimize Tools.
|
|
6
|
+
|
|
7
|
+
Five proxy-free tools exposing compression (reversible via CCR) and
|
|
8
|
+
routed-result caching WITHOUT touching ANTHROPIC_BASE_URL, so the full
|
|
9
|
+
1M context window is preserved on any Claude subscription.
|
|
10
|
+
|
|
11
|
+
Primary Claude conversation turns CANNOT be cached without a proxy.
|
|
12
|
+
These tools cache results the agent explicitly routes through SLM.
|
|
13
|
+
|
|
14
|
+
Fail-open: every tool body is wrapped in try/except Exception.
|
|
15
|
+
Any internal error returns the input unchanged with ok:False — never raises.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import hashlib
|
|
21
|
+
import logging
|
|
22
|
+
import threading
|
|
23
|
+
import time
|
|
24
|
+
|
|
25
|
+
from mcp.types import ToolAnnotations
|
|
26
|
+
|
|
27
|
+
from superlocalmemory.mcp.agent_context import get_current_agent_id
|
|
28
|
+
from superlocalmemory.optimize.compress.ccr import CCRStore, _UUID4_RE
|
|
29
|
+
from superlocalmemory.optimize.compress.router import CompressRouter
|
|
30
|
+
from superlocalmemory.optimize.storage.db import CacheDB, _normalize_tenant_id
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger("slm.mcp.tools_optimize")
|
|
33
|
+
|
|
34
|
+
# ─── Size caps (CWE-400 guards) ───────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
_MAX_COMPRESS_BYTES: int = 1_000_000
|
|
37
|
+
_MAX_KV_VALUE_BYTES: int = 1_000_000
|
|
38
|
+
_MAX_KV_KEY_CHARS: int = 512
|
|
39
|
+
|
|
40
|
+
# ─── Exported tool name list (used by server.py + tests) ─────────────────────
|
|
41
|
+
|
|
42
|
+
_OPTIMIZE_TOOL_NAMES = (
|
|
43
|
+
"slm_compress",
|
|
44
|
+
"slm_retrieve",
|
|
45
|
+
"slm_cache_set",
|
|
46
|
+
"slm_cache_get",
|
|
47
|
+
"slm_optimize_stats",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# ─── In-module KV counters (thread-safe; MetricsCollector is process-scoped) ─
|
|
51
|
+
|
|
52
|
+
_kv_lock = threading.Lock()
|
|
53
|
+
_kv_hits: int = 0
|
|
54
|
+
_kv_misses: int = 0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _tenant() -> str:
|
|
58
|
+
# get_current_agent_id() never returns ""; "mcp_client" is its stdio sentinel.
|
|
59
|
+
return get_current_agent_id()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ─── Tool registration ────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def register_optimize_tools(server) -> None:
|
|
66
|
+
"""Register the 5 Surface B optimize tools on *server*.
|
|
67
|
+
|
|
68
|
+
*server* is duck-typed: must support @server.tool() decorator pattern.
|
|
69
|
+
Compatible with FastMCP, _FilteredServer, and test _MockServer.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False))
|
|
73
|
+
async def slm_compress(
|
|
74
|
+
content: str,
|
|
75
|
+
mode: str = "auto",
|
|
76
|
+
reversible: bool = True,
|
|
77
|
+
ttl_seconds: int = 86400,
|
|
78
|
+
) -> dict:
|
|
79
|
+
"""Compress text or tool output to reduce context window usage.
|
|
80
|
+
|
|
81
|
+
Returns compressed text. If lossy and reversible=True, also returns a
|
|
82
|
+
ccr_id — pass it to slm_retrieve to recover the exact original.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
content: Text to compress (max 1MB).
|
|
86
|
+
mode: "normalize" (lossless whitespace) | "auto" | "aggressive".
|
|
87
|
+
reversible: Store original in CCR for later retrieval.
|
|
88
|
+
ttl_seconds: CCR lifetime in seconds (default 24h).
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
if not isinstance(content, str) or not content:
|
|
92
|
+
return {
|
|
93
|
+
"ok": False, "compressed": content or "",
|
|
94
|
+
"strategy": "none", "tokens_before": 0, "tokens_after": 0,
|
|
95
|
+
"ratio": 1.0, "lossy": False, "ccr_id": None,
|
|
96
|
+
"note": "empty input",
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
note_parts: list[str] = []
|
|
100
|
+
if len(content.encode("utf-8")) > _MAX_COMPRESS_BYTES:
|
|
101
|
+
reversible = False
|
|
102
|
+
note_parts.append("content over 1MB: ccr skipped")
|
|
103
|
+
|
|
104
|
+
if mode == "normalize":
|
|
105
|
+
# @staticmethod — lossless whitespace collapse, no config/daemon dep.
|
|
106
|
+
normalized = CompressRouter._normalize_whitespace(content)
|
|
107
|
+
tb = len(content.split())
|
|
108
|
+
ta = len(normalized.split())
|
|
109
|
+
ratio = round(ta / tb, 4) if tb else 1.0
|
|
110
|
+
return {
|
|
111
|
+
"ok": True, "compressed": normalized, "strategy": "normalize",
|
|
112
|
+
"tokens_before": tb, "tokens_after": ta, "ratio": ratio,
|
|
113
|
+
"lossy": False, "ccr_id": None,
|
|
114
|
+
"note": " | ".join(note_parts) or None,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if mode == "aggressive":
|
|
118
|
+
note_parts.append(
|
|
119
|
+
"aggressive mode requires daemon compress_mode=aggressive in config"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
res = CompressRouter.get_instance().compress_text(content)
|
|
123
|
+
|
|
124
|
+
ccr_id = None
|
|
125
|
+
if res.lossy and reversible:
|
|
126
|
+
stored = CCRStore.get_instance().store(
|
|
127
|
+
content.encode("utf-8"),
|
|
128
|
+
tenant_id=_tenant(),
|
|
129
|
+
ttl_seconds=ttl_seconds,
|
|
130
|
+
)
|
|
131
|
+
ccr_id = stored or None
|
|
132
|
+
if ccr_id:
|
|
133
|
+
note_parts.append("reversible: call slm_retrieve with this ccr_id")
|
|
134
|
+
|
|
135
|
+
ratio = (
|
|
136
|
+
round(res.tokens_after / res.tokens_before, 4)
|
|
137
|
+
if res.tokens_before else 1.0
|
|
138
|
+
)
|
|
139
|
+
return {
|
|
140
|
+
"ok": True, "compressed": res.compressed_text, "strategy": res.strategy,
|
|
141
|
+
"tokens_before": res.tokens_before, "tokens_after": res.tokens_after,
|
|
142
|
+
"ratio": ratio, "lossy": res.lossy, "ccr_id": ccr_id,
|
|
143
|
+
"note": " | ".join(note_parts) or None,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
except Exception as exc:
|
|
147
|
+
logger.error("slm_compress failed (fail-open): %s", exc)
|
|
148
|
+
t = len(content.split()) if isinstance(content, str) else 0
|
|
149
|
+
return {
|
|
150
|
+
"ok": False,
|
|
151
|
+
"compressed": content if isinstance(content, str) else "",
|
|
152
|
+
"strategy": "none", "tokens_before": t, "tokens_after": t,
|
|
153
|
+
"ratio": 1.0, "lossy": False, "ccr_id": None,
|
|
154
|
+
"note": f"internal error: {exc}",
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
158
|
+
async def slm_retrieve(ccr_id: str) -> dict:
|
|
159
|
+
"""Retrieve original text stored during a lossy slm_compress call.
|
|
160
|
+
|
|
161
|
+
Do not log or share ccr_ids — they are unguessable session tokens, but
|
|
162
|
+
if exposed they allow retrieval by anyone with the daemon's decryption key.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
ccr_id: UUID4 returned by slm_compress when reversible=True.
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
if not ccr_id or not _UUID4_RE.match(ccr_id):
|
|
169
|
+
return {
|
|
170
|
+
"ok": False, "content": None, "size_bytes": 0,
|
|
171
|
+
"error": "ccr_id must be a UUID4",
|
|
172
|
+
}
|
|
173
|
+
original = CCRStore.get_instance().retrieve(ccr_id)
|
|
174
|
+
if original is None:
|
|
175
|
+
return {
|
|
176
|
+
"ok": False, "content": None, "size_bytes": 0,
|
|
177
|
+
"error": "not found (expired / never stored / wrong id)",
|
|
178
|
+
}
|
|
179
|
+
size = len(original)
|
|
180
|
+
try:
|
|
181
|
+
text = original.decode("utf-8")
|
|
182
|
+
except UnicodeDecodeError:
|
|
183
|
+
text = original.decode("latin-1")
|
|
184
|
+
return {"ok": True, "content": text, "size_bytes": size, "error": None}
|
|
185
|
+
|
|
186
|
+
except Exception as exc:
|
|
187
|
+
logger.error("slm_retrieve failed (fail-open): %s", exc)
|
|
188
|
+
return {
|
|
189
|
+
"ok": False, "content": None, "size_bytes": 0,
|
|
190
|
+
"error": f"internal error: {exc}",
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False))
|
|
194
|
+
async def slm_cache_set(key: str, value: str, ttl_seconds: int = 86400) -> dict:
|
|
195
|
+
"""Cache a result you want to reuse (tool output, file read, search result).
|
|
196
|
+
|
|
197
|
+
This caches results the agent explicitly routes through SLM — NOT the
|
|
198
|
+
Claude conversation turn (impossible without a proxy).
|
|
199
|
+
|
|
200
|
+
Do not cache secrets, credentials, or ccr_ids via this tool.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
key: Cache key (max 512 chars). Namespaced per agent automatically.
|
|
204
|
+
value: Value to store as string (max 1MB).
|
|
205
|
+
ttl_seconds: Time-to-live in seconds (default 24h).
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
if not key or len(key) > _MAX_KV_KEY_CHARS:
|
|
209
|
+
return {
|
|
210
|
+
"ok": False, "stored": False,
|
|
211
|
+
"note": f"key must be 1–{_MAX_KV_KEY_CHARS} chars",
|
|
212
|
+
}
|
|
213
|
+
value_bytes = value.encode("utf-8")
|
|
214
|
+
if len(value_bytes) > _MAX_KV_VALUE_BYTES:
|
|
215
|
+
return {"ok": False, "stored": False, "note": "value exceeds 1MB limit"}
|
|
216
|
+
|
|
217
|
+
tenant = _tenant()
|
|
218
|
+
cache_key = hashlib.sha256(f"mcpkv:{tenant}:{key}".encode()).hexdigest()
|
|
219
|
+
norm_tid = _normalize_tenant_id(tenant)
|
|
220
|
+
ttl_exp = time.time() + ttl_seconds
|
|
221
|
+
|
|
222
|
+
CacheDB.get_default().set(
|
|
223
|
+
cache_key, norm_tid, value_bytes,
|
|
224
|
+
model="mcp-kv", ttl_expires=ttl_exp, tags=["mcp-kv"],
|
|
225
|
+
)
|
|
226
|
+
return {"ok": True, "stored": True, "note": None}
|
|
227
|
+
|
|
228
|
+
except Exception as exc:
|
|
229
|
+
logger.error("slm_cache_set failed (fail-open): %s", exc)
|
|
230
|
+
return {"ok": False, "stored": False, "note": f"internal error: {exc}"}
|
|
231
|
+
|
|
232
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
233
|
+
async def slm_cache_get(key: str) -> dict:
|
|
234
|
+
"""Retrieve a previously cached result.
|
|
235
|
+
|
|
236
|
+
Returns hit:True + value if the key exists and has not expired.
|
|
237
|
+
Returns hit:False (never raises) on miss, expiry, or any error.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
key: Cache key used in slm_cache_set.
|
|
241
|
+
"""
|
|
242
|
+
global _kv_hits, _kv_misses
|
|
243
|
+
try:
|
|
244
|
+
if not key or len(key) > _MAX_KV_KEY_CHARS:
|
|
245
|
+
return {
|
|
246
|
+
"ok": False, "hit": False, "value": None,
|
|
247
|
+
"note": f"key must be 1–{_MAX_KV_KEY_CHARS} chars",
|
|
248
|
+
}
|
|
249
|
+
tenant = _tenant()
|
|
250
|
+
cache_key = hashlib.sha256(f"mcpkv:{tenant}:{key}".encode()).hexdigest()
|
|
251
|
+
norm_tid = _normalize_tenant_id(tenant)
|
|
252
|
+
|
|
253
|
+
blob = CacheDB.get_default().get_value(cache_key, norm_tid)
|
|
254
|
+
if blob is None:
|
|
255
|
+
with _kv_lock:
|
|
256
|
+
_kv_misses += 1
|
|
257
|
+
return {"ok": True, "hit": False, "value": None, "note": None}
|
|
258
|
+
with _kv_lock:
|
|
259
|
+
_kv_hits += 1
|
|
260
|
+
return {"ok": True, "hit": True, "value": blob.decode("utf-8"), "note": None}
|
|
261
|
+
|
|
262
|
+
except Exception as exc:
|
|
263
|
+
logger.error("slm_cache_get failed (fail-open): %s", exc)
|
|
264
|
+
return {
|
|
265
|
+
"ok": False, "hit": False, "value": None,
|
|
266
|
+
"note": f"internal error: {exc}",
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
270
|
+
async def slm_optimize_stats() -> dict:
|
|
271
|
+
"""Return compression and cache statistics.
|
|
272
|
+
|
|
273
|
+
Proxy/compress stats are daemon-persisted (accurate across restarts).
|
|
274
|
+
KV stats are in-module counters for this MCP process session only.
|
|
275
|
+
"""
|
|
276
|
+
try:
|
|
277
|
+
snap = CacheDB.get_default().metrics_load()
|
|
278
|
+
with _kv_lock:
|
|
279
|
+
kv_h = _kv_hits
|
|
280
|
+
kv_m = _kv_misses
|
|
281
|
+
return {
|
|
282
|
+
"ok": True,
|
|
283
|
+
"compress_runs": snap.compress_runs,
|
|
284
|
+
"tokens_saved_compress": snap.tokens_saved_compress,
|
|
285
|
+
"cache_proxy_hits": snap.hits,
|
|
286
|
+
"cache_proxy_misses": snap.misses,
|
|
287
|
+
"cache_kv_hits": kv_h,
|
|
288
|
+
"cache_kv_misses": kv_m,
|
|
289
|
+
"ccr_note": (
|
|
290
|
+
"CCR entry count not tracked per-session; "
|
|
291
|
+
"see daemon /api/v1/metrics"
|
|
292
|
+
),
|
|
293
|
+
"note": "proxy stats are daemon-persisted; kv stats are this session only",
|
|
294
|
+
}
|
|
295
|
+
except Exception as exc:
|
|
296
|
+
logger.error("slm_optimize_stats failed (fail-open): %s", exc)
|
|
297
|
+
return {
|
|
298
|
+
"ok": False,
|
|
299
|
+
"compress_runs": 0, "tokens_saved_compress": 0,
|
|
300
|
+
"cache_proxy_hits": 0, "cache_proxy_misses": 0,
|
|
301
|
+
"cache_kv_hits": 0, "cache_kv_misses": 0,
|
|
302
|
+
"ccr_note": None,
|
|
303
|
+
"note": f"internal error: {exc}",
|
|
304
|
+
}
|
|
@@ -421,6 +421,30 @@ class CacheDB:
|
|
|
421
421
|
logger.warning("CacheDB.get failed (cache miss): %s", exc)
|
|
422
422
|
return None
|
|
423
423
|
|
|
424
|
+
def get_value(self, cache_key: str, tenant_id: str) -> bytes | None:
|
|
425
|
+
"""Pure value lookup — no hit_count increment (unlike get()).
|
|
426
|
+
|
|
427
|
+
Used by MCP KV tools which manage their own hit/miss counters.
|
|
428
|
+
Caller must have already normalized tenant_id. Fail-open: returns None on error.
|
|
429
|
+
"""
|
|
430
|
+
try:
|
|
431
|
+
rows = self._db.execute(
|
|
432
|
+
"SELECT value_blob, compressed FROM llmcache_entries "
|
|
433
|
+
"WHERE cache_key = ? AND tenant_id = ? "
|
|
434
|
+
"AND (ttl_expires IS NULL OR ttl_expires > ?) LIMIT 1",
|
|
435
|
+
(cache_key, tenant_id, time.time()),
|
|
436
|
+
)
|
|
437
|
+
if not rows:
|
|
438
|
+
return None
|
|
439
|
+
row = dict(rows[0])
|
|
440
|
+
plaintext = self._decrypt(row["value_blob"])
|
|
441
|
+
if row.get("compressed", 0):
|
|
442
|
+
plaintext = zlib.decompress(plaintext)
|
|
443
|
+
return plaintext
|
|
444
|
+
except (sqlite3.Error, ValueError, zlib.error) as exc:
|
|
445
|
+
logger.warning("CacheDB.get_value failed (fail-open): %s", exc)
|
|
446
|
+
return None
|
|
447
|
+
|
|
424
448
|
def set(
|
|
425
449
|
self,
|
|
426
450
|
key: str,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: superlocalmemory
|
|
3
|
-
Version: 3.6.
|
|
3
|
+
Version: 3.6.11
|
|
4
4
|
Summary: Information-geometric agent memory with mathematical guarantees
|
|
5
5
|
Author-email: Varun Pratap Bhardwaj <admin@superlocalmemory.com>
|
|
6
6
|
License: AGPL-3.0-or-later
|
|
@@ -95,18 +95,18 @@ Dynamic: license-file
|
|
|
95
95
|
<img src="https://superlocalmemory.com/assets/logo-mark.png" alt="SuperLocalMemory" width="200"/>
|
|
96
96
|
</p>
|
|
97
97
|
|
|
98
|
-
<h1 align="center">SuperLocalMemory V3.6</h1>
|
|
99
|
-
<p align="center"><strong>
|
|
100
|
-
<p align="center"><code>v3.6.
|
|
98
|
+
<h1 align="center">SuperLocalMemory V3.6.11</h1>
|
|
99
|
+
<p align="center"><strong>Cache. Compress. Remember. Three surfaces — proxy, MCP tools, or skill. Every setup covered.</strong><br/><em>The only local-first layer that pairs persistent memory with compression + caching across every Claude plan. Full 1M window preserved in MCP and skill mode.</em></p>
|
|
100
|
+
<p align="center"><code>v3.6.11 "Optimize Everywhere"</code> — <strong>Compress + cache on any plan, three ways in.</strong><br/>Proxy (full-turn cache): <code>slm wrap claude</code> · MCP (proxy-free): add <code>slm_compress</code> to your MCP config · Skill (zero-config): <code>~/.claude/skills/slm-optimize/</code></p>
|
|
101
101
|
<p align="center"><strong>Backed by 3 published research papers</strong> (arXiv preprints + Zenodo-archived) · <a href="https://arxiv.org/abs/2603.02240">arXiv:2603.02240</a> · <a href="https://arxiv.org/abs/2603.14588">arXiv:2603.14588</a> · <a href="https://arxiv.org/abs/2604.04514">arXiv:2604.04514</a></p>
|
|
102
102
|
|
|
103
103
|
<p align="center">
|
|
104
|
-
<code>
|
|
104
|
+
<code>Proxy · MCP tools · Skill — three surfaces</code> · <code>+10.6pp vs Mem0 zero-LLM</code> · <code>85% Open-Domain (best zero-LLM score)</code> · <code>EU AI Act Ready</code>
|
|
105
105
|
</p>
|
|
106
106
|
|
|
107
107
|
<p align="center">
|
|
108
108
|
<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>
|
|
109
|
-
<a href="
|
|
109
|
+
<a href="#three-surfaces-proxy--mcp-tools--skill"><img src="https://img.shields.io/badge/Proxy_|_MCP_|_Skill-22c55e?style=for-the-badge" alt="Three Surfaces: Proxy, MCP Tools, Skill"/></a>
|
|
110
110
|
<a href="https://pypi.org/project/superlocalmemory/"><img src="https://img.shields.io/pypi/v/superlocalmemory?style=for-the-badge&logo=pypi&logoColor=white" alt="PyPI"/></a>
|
|
111
111
|
<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>
|
|
112
112
|
<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>
|
|
@@ -126,7 +126,9 @@ Dynamic: license-file
|
|
|
126
126
|
<details>
|
|
127
127
|
<summary><strong>What's New in V3.6 — Optimize: SKIP, SHRINK, DISCOUNT, REMEMBER</strong> (click to expand)</summary>
|
|
128
128
|
|
|
129
|
-
> V3.6 is the only local-first layer that SKIPS repeat LLM calls (cache: 100%
|
|
129
|
+
> V3.6 is the only local-first layer that SKIPS repeat LLM calls (cache: 100% on a hit), SHRINKS tool outputs and injected context (compress: lossless-by-default, opt-in LLMLingua-2), and DISCOUNTS prefix costs (align: native KV-cache) — and remembers everything — in one install.
|
|
130
|
+
>
|
|
131
|
+
> **v3.6.11 "Optimize Everywhere":** Three surfaces. **Proxy** (Surface A) — full-turn cache + compress on transport; needs `ANTHROPIC_BASE_URL`, shrinks the context window. **MCP tools** (Surface B) — `slm_compress`, `slm_retrieve`, `slm_cache_set`, `slm_cache_get`, `slm_optimize_stats`; no proxy, no window shrink, works on any Claude subscription. **Skill** (Surface C) — `slm-optimize` installs in `~/.claude/skills/`; zero-config auto-compress for large tool outputs and CLAUDE.md. No proxy, full 1M window. [See Three Surfaces →](#three-surfaces-proxy--mcp-tools--skill)
|
|
130
132
|
>
|
|
131
133
|
> **v3.6.10:** cache and compression are now **independent runtime switches** (cache-only, compress-only, both, or neither — toggle live from the dashboard, no restart). Compression was rebuilt to be **lossless by default** (the old string/array/code truncation is gone); aggressive mode adds LLMLingua-2 for **prose only** — never code, numbers, structured data, or the current turn.
|
|
132
134
|
|
|
@@ -213,6 +215,57 @@ Full docs:
|
|
|
213
215
|
|
|
214
216
|
---
|
|
215
217
|
|
|
218
|
+
## Three Surfaces: Proxy · MCP Tools · Skill
|
|
219
|
+
|
|
220
|
+
v3.6.11 delivers one engine across **three ways in** — choose the surface that fits your setup:
|
|
221
|
+
|
|
222
|
+
| Surface | How you use it | Requires proxy? | Window effect | Cache scope |
|
|
223
|
+
|---------|---------------|:---------------:|:-------------:|-------------|
|
|
224
|
+
| **A — Proxy** | `slm wrap claude` or `ANTHROPIC_BASE_URL=http://127.0.0.1:8765` | **Yes** | Shrinks (proxy intercepts full context) | Full-turn cache — every Claude call |
|
|
225
|
+
| **B — MCP tools** | Add 5 tools to MCP config; call `slm_compress`, `slm_cache_set/get` | **No** | **Preserved** (full 1M) | Results you explicitly route through SLM |
|
|
226
|
+
| **C — Skill** | Copy `skills/slm-optimize/SKILL.md` → `~/.claude/skills/` | **No** | **Preserved** (full 1M) | Auto-applied by the agent per skill rules |
|
|
227
|
+
|
|
228
|
+
**How to choose:**
|
|
229
|
+
- On a **metered API** (pay-per-token) and want to cache every call → **Proxy (A)**
|
|
230
|
+
- On a **Pro/Max/Team subscription** or any plan where you can't or won't run a proxy → **MCP tools (B)** or **Skill (C)**
|
|
231
|
+
- Want zero configuration → **Skill (C)**: install once, auto-compresses CLAUDE.md and large outputs
|
|
232
|
+
- Want agent-controlled caching of repeated file reads and tool outputs → **MCP tools (B)**
|
|
233
|
+
|
|
234
|
+
**The hard constraint:** The primary Claude conversation turn cannot be cached without a proxy — the MCP/skill path caches results you explicitly route through SLM (tool outputs, file reads, sub-model calls).
|
|
235
|
+
|
|
236
|
+
### MCP Tools Setup (Surface B)
|
|
237
|
+
|
|
238
|
+
Add to your `claude_desktop_config.json` or IDE MCP config alongside your existing SLM entry:
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"mcpServers": {
|
|
243
|
+
"superlocalmemory": {
|
|
244
|
+
"command": "slm",
|
|
245
|
+
"args": ["mcp"]
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The 5 optimize tools (`slm_compress`, `slm_retrieve`, `slm_cache_set`, `slm_cache_get`, `slm_optimize_stats`) are included automatically from v3.6.11+. Verify with `slm_optimize_stats()`.
|
|
252
|
+
|
|
253
|
+
### Skill Setup (Surface C)
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
mkdir -p ~/.claude/skills/slm-optimize
|
|
257
|
+
cp $(pip show superlocalmemory | grep Location | awk '{print $2}')/superlocalmemory/skills/slm-optimize/SKILL.md \
|
|
258
|
+
~/.claude/skills/slm-optimize/SKILL.md
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Then reference in your `CLAUDE.md`:
|
|
262
|
+
```markdown
|
|
263
|
+
## Context Management
|
|
264
|
+
Use the `slm-optimize` skill to compress large outputs and cache repeated reads.
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
216
269
|
<details>
|
|
217
270
|
<summary><strong>What's New in V3.3 — The Living Brain Evolves</strong> (click to expand)</summary>
|
|
218
271
|
|
|
@@ -389,6 +442,7 @@ No manual commands. No data loss. Zero downtime.
|
|
|
389
442
|
|
|
390
443
|
| Version | Codename | Key Features |
|
|
391
444
|
|---|---|---|
|
|
445
|
+
| **v3.6.11** | Optimize Everywhere | **Three surfaces** — Proxy (A: full-turn cache), MCP tools (B: `slm_compress`/`slm_retrieve`/`slm_cache_set`/`slm_cache_get`/`slm_optimize_stats` — proxy-free, 1M window), Skill (C: `slm-optimize` zero-config). `CacheDB.get_value()` (pure KV lookup). 23 new tests. Links: [Three Surfaces →](#three-surfaces-proxy--mcp-tools--skill) · [docs/optimize-overview.md](docs/optimize-overview.md) |
|
|
392
446
|
| **v3.6.0** | Optimize | **Cache** (skip repeat calls, 100% on hit) · **Compress** (shrink prompts 60-95%) · **Align** (KV-cache stabilization) · `slm optimize\|cache\|compress\|proxy\|wrap` CLI · Live savings dashboard (USD/INR/tokens) · Hot-reload config · Safe defaults · Links: [docs/optimize-overview.md](docs/optimize-overview.md) · [V3.6 Wiki](https://github.com/qualixar/superlocalmemory/wiki/V3.6-Overview) |
|
|
393
447
|
| **v3.5.0** | Scale-Ready + Context Injection v2 | CozoDB/LanceDB migration, 6-channel recall <1s, Core Memory Block, BM25→FTS5, context injection v2, score normalization |
|
|
394
448
|
| **v3.4.5** | Scale-Ready (foundation) | Tiered storage (active/warm/cold), graph pruning, BackendOrchestrator scaffolding, CozoDB + LanceDB init + migration code (read path wired in v3.5.0) |
|
|
@@ -283,6 +283,7 @@ src/superlocalmemory/mcp/tools_core.py
|
|
|
283
283
|
src/superlocalmemory/mcp/tools_evolution.py
|
|
284
284
|
src/superlocalmemory/mcp/tools_learning.py
|
|
285
285
|
src/superlocalmemory/mcp/tools_mesh.py
|
|
286
|
+
src/superlocalmemory/mcp/tools_optimize.py
|
|
286
287
|
src/superlocalmemory/mcp/tools_v28.py
|
|
287
288
|
src/superlocalmemory/mcp/tools_v3.py
|
|
288
289
|
src/superlocalmemory/mcp/tools_v33.py
|