superlocalmemory 3.6.5 → 3.6.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 +64 -0
- package/README.md +56 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +30 -10
- package/src/superlocalmemory/core/block_hygiene.py +147 -0
- package/src/superlocalmemory/core/config.py +36 -0
- package/src/superlocalmemory/core/consolidation_engine.py +13 -10
- package/src/superlocalmemory/core/engine.py +24 -5
- package/src/superlocalmemory/core/ingest_gate.py +133 -0
- package/src/superlocalmemory/core/injection.py +27 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +9 -0
- package/src/superlocalmemory/core/recall_pipeline.py +9 -0
- package/src/superlocalmemory/core/recall_worker.py +12 -20
- package/src/superlocalmemory/mcp/server.py +19 -6
- package/src/superlocalmemory/mcp/tools_core.py +2 -0
- package/src/superlocalmemory/retrieval/engine.py +58 -0
- package/src/superlocalmemory/server/recall_serializer.py +225 -0
- package/src/superlocalmemory/server/unified_daemon.py +85 -56
- package/src/superlocalmemory/storage/models.py +3 -0
- package/src/superlocalmemory.egg-info/PKG-INFO +57 -2
- package/src/superlocalmemory.egg-info/SOURCES.txt +3 -0
|
@@ -34,11 +34,17 @@ import signal
|
|
|
34
34
|
import sys
|
|
35
35
|
import threading
|
|
36
36
|
import time
|
|
37
|
-
from contextlib import asynccontextmanager
|
|
37
|
+
from contextlib import asynccontextmanager, AsyncExitStack
|
|
38
38
|
from datetime import datetime, timezone
|
|
39
39
|
from pathlib import Path
|
|
40
40
|
from typing import Optional
|
|
41
41
|
|
|
42
|
+
# v3.6.7: Tell mcp/server.py it is being imported inside the daemon process.
|
|
43
|
+
# This suppresses the three side-effect threads (mcp-warmup, parent-watchdog,
|
|
44
|
+
# stdin-eof-monitor) that are harmful when the MCP server runs embedded.
|
|
45
|
+
# Must be set BEFORE any import of superlocalmemory.mcp.server.
|
|
46
|
+
os.environ.setdefault("SLM_MCP_EMBEDDED", "1")
|
|
47
|
+
|
|
42
48
|
from fastapi import FastAPI, HTTPException, Request
|
|
43
49
|
from fastapi.middleware.cors import CORSMiddleware
|
|
44
50
|
from fastapi.middleware.gzip import GZipMiddleware
|
|
@@ -95,32 +101,20 @@ class EngineRecallAdapter:
|
|
|
95
101
|
self._engine._db.get_memory_content_batch(memory_ids)
|
|
96
102
|
if memory_ids else {}
|
|
97
103
|
)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
for k, v in (r.channel_scores or {}).items()
|
|
113
|
-
},
|
|
114
|
-
"fact_type": fact_type.value
|
|
115
|
-
if fact_type and hasattr(fact_type, "value") else "",
|
|
116
|
-
"lifecycle": lifecycle.value
|
|
117
|
-
if lifecycle and hasattr(lifecycle, "value") else "",
|
|
118
|
-
"access_count": getattr(r.fact, "access_count", 0),
|
|
119
|
-
"created_at": getattr(r.fact, "created_at", "") or "",
|
|
120
|
-
"evidence_chain": list(
|
|
121
|
-
getattr(r, "evidence_chain", []) or []
|
|
122
|
-
),
|
|
123
|
-
})
|
|
104
|
+
# v3.6.6: same shared chokepoint as the HTTP route — identical output.
|
|
105
|
+
from superlocalmemory.server.recall_serializer import (
|
|
106
|
+
serialize_recall_response,
|
|
107
|
+
)
|
|
108
|
+
_rc = getattr(self._engine._config, "retrieval", None)
|
|
109
|
+
results, no_confident_match = serialize_recall_response(
|
|
110
|
+
response,
|
|
111
|
+
limit=limit,
|
|
112
|
+
memory_map={k: _sanitize_json_text(v) for k, v in memory_map.items()},
|
|
113
|
+
per_fact_max=getattr(_rc, "recall_per_fact_max_chars", 2400),
|
|
114
|
+
total_max=getattr(_rc, "recall_total_max_chars", 12000),
|
|
115
|
+
)
|
|
116
|
+
for _r in results:
|
|
117
|
+
_r["content"] = _sanitize_json_text(_r.get("content", ""))
|
|
124
118
|
return {
|
|
125
119
|
"ok": True,
|
|
126
120
|
"query": query,
|
|
@@ -133,6 +127,7 @@ class EngineRecallAdapter:
|
|
|
133
127
|
},
|
|
134
128
|
"total_candidates": getattr(response, "total_candidates", 0),
|
|
135
129
|
"results": results,
|
|
130
|
+
"no_confident_match": no_confident_match,
|
|
136
131
|
}
|
|
137
132
|
|
|
138
133
|
|
|
@@ -861,7 +856,20 @@ async def lifespan(application: FastAPI):
|
|
|
861
856
|
except Exception as e:
|
|
862
857
|
logger.warning("optimize module not available: %s", e)
|
|
863
858
|
|
|
864
|
-
|
|
859
|
+
# v3.6.7: Start MCP Streamable-HTTP session manager (GOTCHA #1).
|
|
860
|
+
# streamable_http_app() carries its own Starlette lifespan that initialises
|
|
861
|
+
# an anyio task group inside the session manager. Without entering that
|
|
862
|
+
# lifespan every POST /mcp 500s with "Task group is not initialized."
|
|
863
|
+
# AsyncExitStack enters the context only when _mcp_app was mounted; if the
|
|
864
|
+
# mount failed (non-fatal) the daemon starts normally without HTTP MCP.
|
|
865
|
+
async with AsyncExitStack() as _mcp_stack:
|
|
866
|
+
if _mcp_app is not None:
|
|
867
|
+
await _mcp_stack.enter_async_context(
|
|
868
|
+
_mcp_app.router.lifespan_context(_mcp_app)
|
|
869
|
+
)
|
|
870
|
+
logger.info("MCP HTTP session manager started (Streamable-HTTP on /mcp)")
|
|
871
|
+
|
|
872
|
+
yield
|
|
865
873
|
|
|
866
874
|
# Cancel optimize metrics flush loop + run final flush before shutdown
|
|
867
875
|
try:
|
|
@@ -1160,6 +1168,28 @@ def create_app() -> FastAPI:
|
|
|
1160
1168
|
# -- Daemon-specific routes --
|
|
1161
1169
|
_register_daemon_routes(application)
|
|
1162
1170
|
|
|
1171
|
+
# -- v3.6.7: MCP Streamable-HTTP transport at /mcp --
|
|
1172
|
+
# Mount the FastMCP server as a Starlette ASGI sub-app so ALL clients
|
|
1173
|
+
# (Claude Code sessions, subagents, desktop, hermes) share ONE daemon
|
|
1174
|
+
# process instead of spawning an `slm mcp` subprocess per connection.
|
|
1175
|
+
# The session manager lifespan is started in lifespan() via AsyncExitStack.
|
|
1176
|
+
# Fail-open: if import or mount fails, stdio transport keeps working.
|
|
1177
|
+
#
|
|
1178
|
+
# streamable_http_path is set to "/" so that when mounted at "/mcp" the
|
|
1179
|
+
# effective user-facing endpoint is exactly http://127.0.0.1:8765/mcp.
|
|
1180
|
+
# (FastAPI strips the mount prefix before passing the request to the
|
|
1181
|
+
# sub-app, so the sub-app's internal route must be "/".)
|
|
1182
|
+
try:
|
|
1183
|
+
from superlocalmemory.mcp.server import server as _mcp_fastmcp
|
|
1184
|
+
_mcp_fastmcp.settings.streamable_http_path = "/"
|
|
1185
|
+
_mcp_fastmcp._session_manager = None # Defensive reset for idempotency
|
|
1186
|
+
global _mcp_app
|
|
1187
|
+
_mcp_app = _mcp_fastmcp.streamable_http_app()
|
|
1188
|
+
application.mount("/mcp", _mcp_app)
|
|
1189
|
+
logger.info("MCP HTTP transport mounted at /mcp (Streamable HTTP, port 8765)")
|
|
1190
|
+
except Exception as _mcp_exc: # pragma: no cover — defensive
|
|
1191
|
+
logger.warning("MCP HTTP mount failed (non-fatal, stdio still works): %s", _mcp_exc)
|
|
1192
|
+
|
|
1163
1193
|
return application
|
|
1164
1194
|
|
|
1165
1195
|
|
|
@@ -1213,7 +1243,9 @@ def _register_dashboard_routes(application: FastAPI) -> None:
|
|
|
1213
1243
|
# for Gemini), never X-SLM-API-Key. Verified: auth_middleware.py:50-82
|
|
1214
1244
|
# returns False for POST when api_key file exists and X-SLM-API-Key
|
|
1215
1245
|
# is absent.
|
|
1216
|
-
|
|
1246
|
+
# v3.6.7: /mcp is also exempt — MCP clients negotiate their own session
|
|
1247
|
+
# via the MCP protocol; they have no knowledge of X-SLM-API-Key.
|
|
1248
|
+
_AUTH_EXEMPT_PREFIXES = ("/v1/", "/v1beta/", "/mcp")
|
|
1217
1249
|
|
|
1218
1250
|
@application.middleware("http")
|
|
1219
1251
|
async def auth_middleware(request, call_next):
|
|
@@ -1563,6 +1595,8 @@ def _register_daemon_routes(application: FastAPI) -> None:
|
|
|
1563
1595
|
q: str = "", query: str = "", limit: int = 20,
|
|
1564
1596
|
session_id: str = "",
|
|
1565
1597
|
fast: bool = False,
|
|
1598
|
+
full: bool = False,
|
|
1599
|
+
include_source: bool = False,
|
|
1566
1600
|
):
|
|
1567
1601
|
_update_activity()
|
|
1568
1602
|
search_query = q or query # Accept both ?q= and ?query= for compatibility
|
|
@@ -1610,33 +1644,23 @@ def _register_daemon_routes(application: FastAPI) -> None:
|
|
|
1610
1644
|
engine._db.get_memory_content_batch(memory_ids)
|
|
1611
1645
|
if memory_ids else {}
|
|
1612
1646
|
)
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
if fact_type and hasattr(fact_type, "value")
|
|
1631
|
-
else getattr(r.fact, "fact_type", ""),
|
|
1632
|
-
"lifecycle": lifecycle.value
|
|
1633
|
-
if lifecycle and hasattr(lifecycle, "value") else "",
|
|
1634
|
-
"access_count": getattr(r.fact, "access_count", 0),
|
|
1635
|
-
"created_at": getattr(r.fact, "created_at", "") or "",
|
|
1636
|
-
"evidence_chain": list(
|
|
1637
|
-
getattr(r, "evidence_chain", []) or []
|
|
1638
|
-
),
|
|
1639
|
-
})
|
|
1647
|
+
# v3.6.6: single shared serialization chokepoint — budget + source
|
|
1648
|
+
# discipline + no_confident_match, identical across every surface.
|
|
1649
|
+
from superlocalmemory.server.recall_serializer import (
|
|
1650
|
+
serialize_recall_response,
|
|
1651
|
+
)
|
|
1652
|
+
_rc = getattr(engine._config, "retrieval", None)
|
|
1653
|
+
results, no_confident_match = serialize_recall_response(
|
|
1654
|
+
response,
|
|
1655
|
+
limit=limit,
|
|
1656
|
+
memory_map={k: _sanitize_json_text(v) for k, v in memory_map.items()},
|
|
1657
|
+
per_fact_max=getattr(_rc, "recall_per_fact_max_chars", 2400),
|
|
1658
|
+
total_max=getattr(_rc, "recall_total_max_chars", 12000),
|
|
1659
|
+
full=full,
|
|
1660
|
+
include_source=include_source,
|
|
1661
|
+
)
|
|
1662
|
+
for _r in results:
|
|
1663
|
+
_r["content"] = _sanitize_json_text(_r.get("content", ""))
|
|
1640
1664
|
return {
|
|
1641
1665
|
"ok": True,
|
|
1642
1666
|
"query": search_query,
|
|
@@ -1650,6 +1674,7 @@ def _register_daemon_routes(application: FastAPI) -> None:
|
|
|
1650
1674
|
"total_candidates": getattr(response, "total_candidates", 0),
|
|
1651
1675
|
"results": results,
|
|
1652
1676
|
"count": len(results),
|
|
1677
|
+
"no_confident_match": no_confident_match,
|
|
1653
1678
|
}
|
|
1654
1679
|
except Exception as exc:
|
|
1655
1680
|
raise HTTPException(500, detail=str(exc))
|
|
@@ -1838,6 +1863,10 @@ def _update_activity():
|
|
|
1838
1863
|
|
|
1839
1864
|
_start_time: float | None = None
|
|
1840
1865
|
|
|
1866
|
+
# v3.6.7: Starlette app returned by mcp_server.streamable_http_app().
|
|
1867
|
+
# Set in create_app(); consumed by lifespan() to start the session manager.
|
|
1868
|
+
_mcp_app = None
|
|
1869
|
+
|
|
1841
1870
|
|
|
1842
1871
|
# ---------------------------------------------------------------------------
|
|
1843
1872
|
# Server entry point
|
|
@@ -411,3 +411,6 @@ class RecallResponse:
|
|
|
411
411
|
channel_weights: dict[str, float] = field(default_factory=dict)
|
|
412
412
|
total_candidates: int = 0
|
|
413
413
|
retrieval_time_ms: float = 0.0
|
|
414
|
+
# v3.6.6: Evidence floor. True when floor gates out ALL results.
|
|
415
|
+
# Additive field — backward compatible (defaults to False).
|
|
416
|
+
no_confident_match: bool = False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: superlocalmemory
|
|
3
|
-
Version: 3.6.
|
|
3
|
+
Version: 3.6.7
|
|
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
|
|
@@ -401,6 +401,32 @@ slm status
|
|
|
401
401
|
|
|
402
402
|
### MCP Integration (Claude, Cursor, Windsurf, VS Code, etc.)
|
|
403
403
|
|
|
404
|
+
SLM supports **two MCP transports** — use whichever fits your tool. Both expose the same 33 tools and 7 resources.
|
|
405
|
+
|
|
406
|
+
#### Option A — HTTP transport (v3.6.7+, recommended)
|
|
407
|
+
|
|
408
|
+
One shared process handles every client. RAM is flat regardless of how many IDE windows, subagents, or concurrent sessions connect. Requires the SLM daemon to be running (`slm start`).
|
|
409
|
+
|
|
410
|
+
```json
|
|
411
|
+
{
|
|
412
|
+
"mcpServers": {
|
|
413
|
+
"superlocalmemory": {
|
|
414
|
+
"type": "http",
|
|
415
|
+
"url": "http://127.0.0.1:8765/mcp/"
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
> **Claude Code** also accepts:
|
|
422
|
+
> ```bash
|
|
423
|
+
> claude mcp add --transport http superlocalmemory http://127.0.0.1:8765/mcp/
|
|
424
|
+
> ```
|
|
425
|
+
|
|
426
|
+
#### Option B — stdio transport (universal, works everywhere)
|
|
427
|
+
|
|
428
|
+
Spawns one `slm mcp` subprocess per client connection (~90–110 MB each). Works with every MCP-compatible tool including those that do not yet support HTTP transport. No daemon required.
|
|
429
|
+
|
|
404
430
|
```json
|
|
405
431
|
{
|
|
406
432
|
"mcpServers": {
|
|
@@ -412,7 +438,36 @@ slm status
|
|
|
412
438
|
}
|
|
413
439
|
```
|
|
414
440
|
|
|
415
|
-
|
|
441
|
+
#### Option C — `mcp-remote` bridge (for stdio-only tools that want HTTP)
|
|
442
|
+
|
|
443
|
+
Some CLIs (e.g. Grok CLI) only speak stdio but you still want the RAM benefit of HTTP. The [`@modelcontextprotocol/client-cli`](https://www.npmjs.com/package/@modelcontextprotocol/client-cli) package bridges them:
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
npm install -g @modelcontextprotocol/client-cli
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
```json
|
|
450
|
+
{
|
|
451
|
+
"mcpServers": {
|
|
452
|
+
"superlocalmemory": {
|
|
453
|
+
"command": "mcp-remote",
|
|
454
|
+
"args": ["http://127.0.0.1:8765/mcp/", "--allow-http", "--transport", "http-only"]
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
#### When to use which
|
|
461
|
+
|
|
462
|
+
| Situation | Use |
|
|
463
|
+
|-----------|-----|
|
|
464
|
+
| Claude Code / Claude Desktop (v3.6.7+) | **HTTP** — zero new processes per session |
|
|
465
|
+
| Cursor, Windsurf, Gemini CLI, Antigravity | **HTTP** — native support |
|
|
466
|
+
| Grok CLI, tools that only support stdio | **`mcp-remote` bridge** |
|
|
467
|
+
| Offline / daemon-free usage | **stdio** |
|
|
468
|
+
| Any tool, any version | **stdio** always works as fallback |
|
|
469
|
+
|
|
470
|
+
See [`docs/ide-setup.md`](docs/ide-setup.md) for per-IDE configs. 33 MCP tools by default (+42 optional behind `SLM_MCP_ALL_TOOLS=1`) + 7 resources. Works with any MCP-compatible client — we ship templated configs for Claude Code, Cursor, Windsurf, VS Code Copilot, Continue, Cody, ChatGPT Desktop, Gemini CLI, JetBrains, Zed, and Antigravity (15 IDE configs in `ide/configs/`).
|
|
416
471
|
|
|
417
472
|
### Dual Interface: MCP + CLI
|
|
418
473
|
|
|
@@ -73,6 +73,7 @@ src/superlocalmemory/compliance/retention.py
|
|
|
73
73
|
src/superlocalmemory/compliance/scheduler.py
|
|
74
74
|
src/superlocalmemory/core/__init__.py
|
|
75
75
|
src/superlocalmemory/core/backend_orchestrator.py
|
|
76
|
+
src/superlocalmemory/core/block_hygiene.py
|
|
76
77
|
src/superlocalmemory/core/clock_monitor.py
|
|
77
78
|
src/superlocalmemory/core/config.py
|
|
78
79
|
src/superlocalmemory/core/consolidation_engine.py
|
|
@@ -92,6 +93,7 @@ src/superlocalmemory/core/graph_analyzer.py
|
|
|
92
93
|
src/superlocalmemory/core/graph_pruner.py
|
|
93
94
|
src/superlocalmemory/core/health_monitor.py
|
|
94
95
|
src/superlocalmemory/core/hooks.py
|
|
96
|
+
src/superlocalmemory/core/ingest_gate.py
|
|
95
97
|
src/superlocalmemory/core/injection.py
|
|
96
98
|
src/superlocalmemory/core/loop_watchdog.py
|
|
97
99
|
src/superlocalmemory/core/maintenance.py
|
|
@@ -358,6 +360,7 @@ src/superlocalmemory/retrieval/vector_store.py
|
|
|
358
360
|
src/superlocalmemory/server/__init__.py
|
|
359
361
|
src/superlocalmemory/server/api.py
|
|
360
362
|
src/superlocalmemory/server/bandit_loops.py
|
|
363
|
+
src/superlocalmemory/server/recall_serializer.py
|
|
361
364
|
src/superlocalmemory/server/security_middleware.py
|
|
362
365
|
src/superlocalmemory/server/ui.py
|
|
363
366
|
src/superlocalmemory/server/unified_daemon.py
|