localmem 0.1.1__tar.gz

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.
Files changed (112) hide show
  1. localmem-0.1.1/.githooks/audit-triad.sh +61 -0
  2. localmem-0.1.1/.githooks/pre-commit +58 -0
  3. localmem-0.1.1/.github/dependabot.yml +31 -0
  4. localmem-0.1.1/.github/workflows/ci.yml +53 -0
  5. localmem-0.1.1/.gitignore +52 -0
  6. localmem-0.1.1/CHANGELOG.md +149 -0
  7. localmem-0.1.1/LICENSE +21 -0
  8. localmem-0.1.1/PKG-INFO +203 -0
  9. localmem-0.1.1/README.md +160 -0
  10. localmem-0.1.1/dashboard/.gitignore +6 -0
  11. localmem-0.1.1/dashboard/eslint.config.js +23 -0
  12. localmem-0.1.1/dashboard/index.html +15 -0
  13. localmem-0.1.1/dashboard/package-lock.json +3354 -0
  14. localmem-0.1.1/dashboard/package.json +35 -0
  15. localmem-0.1.1/dashboard/src/App.tsx +160 -0
  16. localmem-0.1.1/dashboard/src/api.ts +148 -0
  17. localmem-0.1.1/dashboard/src/main.tsx +9 -0
  18. localmem-0.1.1/dashboard/src/panels/AdminPanel.tsx +168 -0
  19. localmem-0.1.1/dashboard/src/panels/AlertsPanel.tsx +62 -0
  20. localmem-0.1.1/dashboard/src/panels/DiariesPanel.tsx +68 -0
  21. localmem-0.1.1/dashboard/src/panels/EntryBrowser.tsx +162 -0
  22. localmem-0.1.1/dashboard/src/panels/GraphPanel.tsx +90 -0
  23. localmem-0.1.1/dashboard/src/panels/HealthPanel.tsx +81 -0
  24. localmem-0.1.1/dashboard/src/panels/LogsPanel.tsx +34 -0
  25. localmem-0.1.1/dashboard/src/panels/MetricsPanel.tsx +75 -0
  26. localmem-0.1.1/dashboard/src/panels/TaxonomyTree.tsx +69 -0
  27. localmem-0.1.1/dashboard/src/panels/TriplesPanel.tsx +82 -0
  28. localmem-0.1.1/dashboard/src/panels/index.ts +10 -0
  29. localmem-0.1.1/dashboard/src/store.ts +64 -0
  30. localmem-0.1.1/dashboard/src/theme.ts +118 -0
  31. localmem-0.1.1/dashboard/src/types.ts +194 -0
  32. localmem-0.1.1/dashboard/src/useWebSocket.ts +107 -0
  33. localmem-0.1.1/dashboard/tsconfig.app.json +26 -0
  34. localmem-0.1.1/dashboard/tsconfig.json +7 -0
  35. localmem-0.1.1/dashboard/tsconfig.node.json +24 -0
  36. localmem-0.1.1/dashboard/vite.config.ts +23 -0
  37. localmem-0.1.1/deploy/com.localmem.server.plist +49 -0
  38. localmem-0.1.1/deploy/cron/README.md +76 -0
  39. localmem-0.1.1/deploy/cron/com.localmem.prune.plist +36 -0
  40. localmem-0.1.1/deploy/cron/localmem-prune.service +13 -0
  41. localmem-0.1.1/deploy/cron/localmem-prune.timer +10 -0
  42. localmem-0.1.1/deploy/localmem.service +50 -0
  43. localmem-0.1.1/deploy/setup-macos.sh +212 -0
  44. localmem-0.1.1/deploy/setup-ubuntu.sh +293 -0
  45. localmem-0.1.1/deploy/setup-windows.ps1 +236 -0
  46. localmem-0.1.1/docs/ARCHITECTURE.md +635 -0
  47. localmem-0.1.1/docs/DASHBOARD.md +202 -0
  48. localmem-0.1.1/docs/LIFECYCLE.md +229 -0
  49. localmem-0.1.1/docs/LOGGING.md +128 -0
  50. localmem-0.1.1/docs/MIGRATIONS.md +222 -0
  51. localmem-0.1.1/docs/assets/dashboard.png +0 -0
  52. localmem-0.1.1/localmem.yaml +100 -0
  53. localmem-0.1.1/manifests/default.yaml +14 -0
  54. localmem-0.1.1/pyproject.toml +81 -0
  55. localmem-0.1.1/src/localmem/__init__.py +32 -0
  56. localmem-0.1.1/src/localmem/__main__.py +6 -0
  57. localmem-0.1.1/src/localmem/api/__init__.py +1 -0
  58. localmem-0.1.1/src/localmem/api/app.py +700 -0
  59. localmem-0.1.1/src/localmem/api/models.py +216 -0
  60. localmem-0.1.1/src/localmem/api/websocket.py +83 -0
  61. localmem-0.1.1/src/localmem/archiver.py +605 -0
  62. localmem-0.1.1/src/localmem/cli.py +935 -0
  63. localmem-0.1.1/src/localmem/config.py +305 -0
  64. localmem-0.1.1/src/localmem/consolidator.py +377 -0
  65. localmem-0.1.1/src/localmem/contradiction.py +68 -0
  66. localmem-0.1.1/src/localmem/embedder.py +103 -0
  67. localmem-0.1.1/src/localmem/embedding_migrator.py +299 -0
  68. localmem-0.1.1/src/localmem/graph_store.py +228 -0
  69. localmem-0.1.1/src/localmem/health.py +91 -0
  70. localmem-0.1.1/src/localmem/intelligence.py +433 -0
  71. localmem-0.1.1/src/localmem/logging_config.py +77 -0
  72. localmem-0.1.1/src/localmem/mcp_server.py +644 -0
  73. localmem-0.1.1/src/localmem/metadata_store.py +524 -0
  74. localmem-0.1.1/src/localmem/metrics.py +85 -0
  75. localmem-0.1.1/src/localmem/migrations/__init__.py +14 -0
  76. localmem-0.1.1/src/localmem/migrations/runner.py +174 -0
  77. localmem-0.1.1/src/localmem/migrations/v001_retention_foundations.py +66 -0
  78. localmem-0.1.1/src/localmem/models.py +188 -0
  79. localmem-0.1.1/src/localmem/prometheus_exposition.py +133 -0
  80. localmem-0.1.1/src/localmem/summarizer.py +93 -0
  81. localmem-0.1.1/src/localmem/summarizer_llm.py +244 -0
  82. localmem-0.1.1/src/localmem/taxonomy.py +89 -0
  83. localmem-0.1.1/src/localmem/vector_store.py +376 -0
  84. localmem-0.1.1/src/localmem/wake_up.py +115 -0
  85. localmem-0.1.1/src/localmem/worker.py +228 -0
  86. localmem-0.1.1/tests/conftest.py +29 -0
  87. localmem-0.1.1/tests/integration/__init__.py +0 -0
  88. localmem-0.1.1/tests/integration/conftest.py +107 -0
  89. localmem-0.1.1/tests/integration/test_live_mcp_client.py +247 -0
  90. localmem-0.1.1/tests/integration/test_ws_handshake.py +161 -0
  91. localmem-0.1.1/tests/test_api.py +377 -0
  92. localmem-0.1.1/tests/test_archiver.py +337 -0
  93. localmem-0.1.1/tests/test_consolidator.py +302 -0
  94. localmem-0.1.1/tests/test_contradiction.py +155 -0
  95. localmem-0.1.1/tests/test_embedding_migrator.py +221 -0
  96. localmem-0.1.1/tests/test_graph_store.py +221 -0
  97. localmem-0.1.1/tests/test_hardening.py +189 -0
  98. localmem-0.1.1/tests/test_health.py +150 -0
  99. localmem-0.1.1/tests/test_importance_decay.py +105 -0
  100. localmem-0.1.1/tests/test_intelligence.py +413 -0
  101. localmem-0.1.1/tests/test_mcp_server.py +344 -0
  102. localmem-0.1.1/tests/test_metadata_store.py +347 -0
  103. localmem-0.1.1/tests/test_metrics.py +109 -0
  104. localmem-0.1.1/tests/test_migrations.py +175 -0
  105. localmem-0.1.1/tests/test_prometheus_exposition.py +213 -0
  106. localmem-0.1.1/tests/test_qdrant_server_mode.py +146 -0
  107. localmem-0.1.1/tests/test_security_hardening_v011.py +151 -0
  108. localmem-0.1.1/tests/test_server_boot.py +102 -0
  109. localmem-0.1.1/tests/test_simulation.py +698 -0
  110. localmem-0.1.1/tests/test_summarizer_llm.py +164 -0
  111. localmem-0.1.1/tests/test_vector_store.py +242 -0
  112. localmem-0.1.1/tests/test_worker.py +233 -0
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ # Wide-sweep audit across the entire working tree. Run before publishing
3
+ # and in CI to gate every PR.
4
+ #
5
+ # Exit codes:
6
+ # 0 — clean (no triad/upstream identifiers found)
7
+ # 1 — violations found (CI should fail on this)
8
+ # 2 — script error (git not available, etc.)
9
+ #
10
+ # Skipped: this hooks directory (intentional patterns), .git, .venv,
11
+ # node_modules, dashboard build output.
12
+
13
+ set -euo pipefail
14
+
15
+ cd "$(git rev-parse --show-toplevel)" || exit 2
16
+
17
+ PATTERNS=(
18
+ 'IRIS'
19
+ 'Iris'
20
+ 'iris'
21
+ 'SORIEL'
22
+ 'Soriel'
23
+ 'soriel'
24
+ 'ECHO'
25
+ 'Echo'
26
+ 'MNEMOSYNE'
27
+ 'Mnemosyne'
28
+ 'mnemosyne'
29
+ )
30
+
31
+ JOINED="$(IFS='|'; echo "${PATTERNS[*]}")"
32
+
33
+ # Collect candidate matches first, then filter false positives, then decide.
34
+ # `set -e` plus the explicit `|| true` keeps a "no matches" exit (1) from
35
+ # tearing down the script before we can interpret it.
36
+ raw_hits="$(
37
+ git ls-files \
38
+ | grep -vE '^(\.githooks/|dashboard/(node_modules|dist)/)' \
39
+ | xargs grep -nE "\\b(${JOINED})\\b" 2>/dev/null \
40
+ || true
41
+ )"
42
+
43
+ # Drop comment lines that just say "echo ..." (shell builtin, not an agent).
44
+ hits="$(
45
+ echo "${raw_hits}" \
46
+ | grep -vE '^[^:]*:[0-9]+:[[:space:]]*#.*echo' \
47
+ || true
48
+ )"
49
+
50
+ if [[ -z "${hits//[[:space:]]/}" ]]; then
51
+ echo "no triad identifiers found"
52
+ exit 0
53
+ fi
54
+
55
+ echo "audit-triad: violations found"
56
+ echo ""
57
+ echo "${hits}"
58
+ echo ""
59
+ echo "Run with LOCALMEM_BYPASS_TRIAD_SCAN=1 git commit ... to override locally,"
60
+ echo "but CI will still fail on these matches. Rewrite to generic naming first."
61
+ exit 1
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env bash
2
+ # localmem pre-commit triad scanner.
3
+ #
4
+ # Blocks any staged change that re-introduces private-project identifiers
5
+ # from the upstream MNEMOSYNE codebase. Override only when you know what
6
+ # you are doing: LOCALMEM_BYPASS_TRIAD_SCAN=1 git commit ...
7
+ #
8
+ # Patterns are intentionally narrow: case-sensitive whole-word matches for
9
+ # the agent names as identifiers. Lowercase "echo" is too noisy (shell
10
+ # builtin) so only the agent-cased forms are blocked. Run
11
+ # `./.githooks/audit-triad.sh` for a wider sweep across the working tree.
12
+
13
+ set -euo pipefail
14
+
15
+ if [[ "${LOCALMEM_BYPASS_TRIAD_SCAN:-0}" == "1" ]]; then
16
+ echo "pre-commit: LOCALMEM_BYPASS_TRIAD_SCAN=1 — skipping triad scanner" >&2
17
+ exit 0
18
+ fi
19
+
20
+ PATTERNS=(
21
+ '\bIRIS\b'
22
+ '\bIris\b'
23
+ '\bSORIEL\b'
24
+ '\bSoriel\b'
25
+ '\bsoriel\b'
26
+ '\bECHO\b'
27
+ '\bMNEMOSYNE\b'
28
+ '\bMnemosyne\b'
29
+ '\bmnemosyne\b'
30
+ )
31
+
32
+ # Combine into one alternation regex for a single grep pass.
33
+ JOINED="$(IFS='|'; echo "${PATTERNS[*]}")"
34
+
35
+ # Inspect only added lines in staged diff (lines starting with "+" but not
36
+ # "+++"). This way we don't re-flag content that already exists in HEAD.
37
+ STAGED_ADDITIONS="$(git diff --cached --no-color -U0 -- ':(exclude).githooks/*' \
38
+ | grep -E '^\+' | grep -v '^\+\+\+' || true)"
39
+
40
+ if [[ -z "${STAGED_ADDITIONS}" ]]; then
41
+ exit 0
42
+ fi
43
+
44
+ HITS="$(echo "${STAGED_ADDITIONS}" | grep -nE "${JOINED}" || true)"
45
+
46
+ if [[ -n "${HITS}" ]]; then
47
+ echo "" >&2
48
+ echo "pre-commit: triad scanner blocked the commit." >&2
49
+ echo " The following added lines contain private upstream identifiers:" >&2
50
+ echo "" >&2
51
+ echo "${HITS}" | sed 's/^/ /' >&2
52
+ echo "" >&2
53
+ echo " Rewrite to use generic naming (wings are user-configurable;" >&2
54
+ echo " intelligence detectors are pluggable). To bypass intentionally:" >&2
55
+ echo " LOCALMEM_BYPASS_TRIAD_SCAN=1 git commit ..." >&2
56
+ echo "" >&2
57
+ exit 1
58
+ fi
@@ -0,0 +1,31 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: pip
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ day: monday
8
+ open-pull-requests-limit: 5
9
+ labels: ["dependencies", "python"]
10
+ groups:
11
+ python-minor-and-patch:
12
+ update-types: ["minor", "patch"]
13
+
14
+ - package-ecosystem: npm
15
+ directory: /dashboard
16
+ schedule:
17
+ interval: weekly
18
+ day: monday
19
+ open-pull-requests-limit: 5
20
+ labels: ["dependencies", "javascript"]
21
+ groups:
22
+ dashboard-minor-and-patch:
23
+ update-types: ["minor", "patch"]
24
+
25
+ - package-ecosystem: github-actions
26
+ directory: /
27
+ schedule:
28
+ interval: weekly
29
+ day: monday
30
+ open-pull-requests-limit: 3
31
+ labels: ["dependencies", "github-actions"]
@@ -0,0 +1,53 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ audit:
17
+ name: triad scan
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v7
21
+ - name: Wide-sweep private-identifier audit
22
+ run: bash .githooks/audit-triad.sh
23
+
24
+ tests:
25
+ name: pytest
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v7
29
+ - uses: actions/setup-python@v6
30
+ with:
31
+ python-version: "3.12"
32
+ cache: pip
33
+ - name: Install package + dev extras
34
+ run: pip install -e ".[dev,dashboard,analytics]"
35
+ - name: Run test suite
36
+ run: pytest --tb=short -q
37
+
38
+ dashboard:
39
+ name: dashboard build
40
+ runs-on: ubuntu-latest
41
+ steps:
42
+ - uses: actions/checkout@v7
43
+ - uses: actions/setup-node@v6
44
+ with:
45
+ node-version: "20"
46
+ cache: npm
47
+ cache-dependency-path: dashboard/package-lock.json
48
+ - name: Install
49
+ working-directory: dashboard
50
+ run: npm ci
51
+ - name: Build
52
+ working-directory: dashboard
53
+ run: npm run build
@@ -0,0 +1,52 @@
1
+ # Archives
2
+ *.tar.gz
3
+
4
+ # Runtime data
5
+ data/
6
+ *.db
7
+ *.db-wal
8
+ *.db-shm
9
+ graph.json
10
+
11
+ # Python
12
+ __pycache__/
13
+ *.py[cod]
14
+ *$py.class
15
+ *.egg-info/
16
+ dist/
17
+ build/
18
+ .eggs/
19
+
20
+ # Virtual env
21
+ .venv/
22
+ venv/
23
+
24
+ # IDE
25
+ .vscode/
26
+ .idea/
27
+ *.swp
28
+ *.swo
29
+
30
+ # OS
31
+ .DS_Store
32
+ Thumbs.db
33
+
34
+ # Testing
35
+ .pytest_cache/
36
+ htmlcov/
37
+ .coverage
38
+
39
+ # Frontend
40
+ node_modules/
41
+ dashboard/dist/
42
+
43
+ # Local env / secrets
44
+ .env
45
+ .env.*
46
+ .env.local
47
+ prune.env
48
+ .secrets
49
+ .mcp.json
50
+
51
+ # Certificates
52
+ *.pem
@@ -0,0 +1,149 @@
1
+ # Changelog
2
+
3
+ All notable changes to localmem.
4
+
5
+ ## [0.1.1] — Security hardening pass
6
+
7
+ Findings from a multi-model code/security review. Each item below is gated
8
+ by a regression test under `tests/test_security_hardening_v011.py` or
9
+ `tests/integration/test_ws_handshake.py`.
10
+
11
+ ### Security
12
+
13
+ - **DuckDB SQL allowlist rewritten as AST validation.** The pre-v0.1.1
14
+ regex blocklist could not stop UNION-SELECT data exfiltration, subquery
15
+ shapes (`WHERE wing IN (SELECT secret FROM ...)`), DuckDB file readers
16
+ (`read_csv_auto('/etc/passwd')`, `read_text(...)`, `load_extension(...)`),
17
+ CHR-encoded keyword reconstruction, recursive-CTE resource exhaustion,
18
+ or qualified-column probes. `archiver.query_sql(sql_where=...)` now
19
+ parses the user clause with `sqlglot` (DuckDB dialect) and walks the
20
+ AST; only the explicit allowlist of node types (boolean ops,
21
+ comparisons, literals, schema-allowlisted bare columns, `IN`/`BETWEEN`/
22
+ `LIKE`/`IS`) survives. Any function call, subquery, `UNION`, `WITH`,
23
+ `CAST`, window, or qualified column rejects the clause. Lexical
24
+ pre-checks for `;`, `--`, and `/* */` close the parse-truncation hole
25
+ where `parse_one` silently stopped at the first statement separator.
26
+ Requires `pip install 'localmem[analytics]'` for `sqlglot>=23`.
27
+
28
+ - **WebSocket bearer subprotocol no longer echoes the token.** Pre-v0.1.1
29
+ accepted `Sec-WebSocket-Protocol: bearer.<token>` and echoed the same
30
+ string back in the 101 Switching Protocols response — exposing the
31
+ token in proxy logs, browser devtools, and service worker scope. The
32
+ new handshake takes two subprotocol values
33
+ (`Sec-WebSocket-Protocol: bearer, <token>`), validates `<token>` with
34
+ `hmac.compare_digest` against the configured api_key, and accepts the
35
+ upgrade with `subprotocol="bearer"` only. The token never appears in
36
+ the response. **Breaking change** for any pre-v0.1.1 dashboard bundle
37
+ or custom WS client; rebuild the frontend and update clients to send
38
+ the new two-value subprotocol list.
39
+
40
+ - **Wing names are charset-constrained.** Wing values flow into archive
41
+ filesystem paths, JSON payloads, WebSocket frames, and Qdrant payload
42
+ keys. `LocalmemConfig._validate_wings` now enforces
43
+ `^[a-z0-9][a-z0-9_-]{0,62}$` (via `re.fullmatch`, so a trailing
44
+ newline cannot slip past `$`). Rejects `..`, `/`, `\`, whitespace,
45
+ control characters, non-ASCII payloads (`café`, RTL overrides), and
46
+ >63-char strings before they can be interpolated into a path.
47
+
48
+ - **`Entry.importance` is Pydantic-bounded.** Field now declares
49
+ `Field(default=0.5, ge=0.0, le=1.0)`. The `localmem_update` MCP tool
50
+ also enforces the bound explicitly (direct attribute assignment
51
+ bypasses Pydantic). Prevents a hostile client from setting
52
+ out-of-range importance to dominate retrieval rankings or evade
53
+ retention thresholds.
54
+
55
+ ### Tests
56
+
57
+ - 367 unit tests (+62 from v0.1.0). New `tests/test_security_hardening_v011.py`
58
+ covers AST allowlist accept/reject cases, wing-name charset rules,
59
+ importance bounds. New `tests/integration/test_ws_handshake.py`
60
+ proves the WS server never echoes the token in the 101 response and
61
+ rejects the pre-v0.1.1 `bearer.<token>` form.
62
+
63
+ ## [0.1.0] — Initial public release
64
+
65
+ First open-source release. Everything below is shipped in v0.1.0.
66
+
67
+ ### Core
68
+
69
+ - 22 MCP tools (memory CRUD, hybrid search, graph, knowledge triples,
70
+ wake-up, intelligence, retention, health, metrics) over FastMCP/SSE on
71
+ port 8781.
72
+ - User-configurable agent wings + reserved `shared` wing for cross-agent
73
+ context.
74
+ - **Storage**: Qdrant (local or server mode) for hybrid dense + sparse
75
+ search with RRF fusion; NetworkX directed multigraph with multi-hop
76
+ traversal and Louvain community detection; SQLite WAL for temporal
77
+ knowledge triples, agent diaries, taxonomy, and importance scoring with
78
+ time-decay.
79
+ - **Layered loading** (L0 manifest → L1 critical context → L2 scoped
80
+ search → L3 verbatim) for token-efficient agent wake-up.
81
+ - **Intelligence engine** with four opt-in pattern detectors (tool
82
+ sequences, provider preferences, node clusters, cross-wing temporal
83
+ correlations). Each detector is off by default until you point it at a
84
+ wing/room or graph selector.
85
+
86
+ ### Lifecycle
87
+
88
+ - Three-tier graceful forgetting: hot (verbatim) → warm (consolidated
89
+ summaries grouped by wing/room/week) → cold (`jsonl.zst` archive,
90
+ hive-partitioned).
91
+ - Pinned entries bypass every retention gate.
92
+ - Per-wing retention policy overrides; `shared` may set `max_age_days:
93
+ null` to never archive.
94
+ - Background worker with single-flight semantics (Qdrant single-writer).
95
+ - REST trigger endpoints + cron units (launchd, systemd) + CLI
96
+ (`pin`, `prune`, `archive`).
97
+ - Optional Ollama LLM summarizer (`consolidation.summarizer: "llm"`) with
98
+ automatic fallback to the deterministic template summarizer.
99
+
100
+ ### Schema & embeddings
101
+
102
+ - File-based schema migrations under `src/localmem/migrations/v00X_*.py`
103
+ with `up()`/`down()` callables and hash-edit detection.
104
+ - `localmem migrate-embeddings --to <model>` re-embeds the entire
105
+ collection offline (snapshot → dump → recreate → re-embed).
106
+
107
+ ### Dashboard
108
+
109
+ - Read-only browser UI at `dashboard/` (Vite + React + dockview): 10
110
+ panels for Health, Entries, Metrics, Alerts, Graph, Wings/Rooms,
111
+ Triples, Diaries, Logs, Admin.
112
+ - FastAPI sidecar on port 8782 with REST + WebSocket.
113
+
114
+ ### Observability
115
+
116
+ - `localmem_health` and `localmem_metrics` MCP tools with per-tool call
117
+ counts, p50/p95/p99 latency, per-wing entry counts, retention worker
118
+ status.
119
+ - `/metrics` Prometheus exposition endpoint (`text/plain;
120
+ version=0.0.4`) on the dashboard sidecar.
121
+ - Structured logging (text or JSON) with optional
122
+ `RotatingFileHandler`.
123
+
124
+ ### Security
125
+
126
+ - Bearer auth on `/api/*` and `/ws` via `dashboard.auth_enabled`.
127
+ - WebSocket auth via `Sec-WebSocket-Protocol: bearer.<key>` subprotocol
128
+ (no `?token=` in URLs).
129
+ - Constant-time API-key compare (`hmac.compare_digest`).
130
+ - DuckDB SQL allowlist on `archiver.query_sql(sql_where=...)` —
131
+ rejects statement separators, comments, and mutating keywords.
132
+ - LLM prompt-injection defense — stored content wrapped in delimiters
133
+ with explicit "data, not commands" instruction; control chars and
134
+ delimiter mimicry stripped.
135
+ - `${VAR}` / `${VAR:-default}` env-var interpolation across the YAML
136
+ config so secrets never need to live in the file on disk.
137
+ - CORS `allow_headers` scoped to `["Authorization", "Content-Type"]`.
138
+
139
+ ### Cross-platform deploy
140
+
141
+ - `deploy/setup-ubuntu.sh`, `deploy/setup-macos.sh`,
142
+ `deploy/setup-windows.ps1` — each generates a config dir, registers a
143
+ service (systemd / launchd / Scheduled Tasks), and writes an api_key to
144
+ a perms-restricted env file.
145
+ - `--auth` and `--qdrant-server <URL>` flags on each installer.
146
+
147
+ ### Tests
148
+
149
+ - 300 tests across 22 files. Run with `pytest`.
localmem-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jordanaftermidnight
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: localmem
3
+ Version: 0.1.1
4
+ Summary: Local-first multi-agent memory MCP server with hybrid search, behavioral graphs, knowledge triples, and lifecycle management
5
+ Project-URL: Homepage, https://github.com/jordanaftermidnight/localmem
6
+ Project-URL: Repository, https://github.com/jordanaftermidnight/localmem
7
+ Project-URL: Issues, https://github.com/jordanaftermidnight/localmem/issues
8
+ Author-email: jordanaftermidnight <jordanaftermidnight@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agents,knowledge-graph,llm,local-first,mcp,memory,qdrant
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: aiosqlite>=0.20.0
23
+ Requires-Dist: fastembed>=0.4.0
24
+ Requires-Dist: fastmcp>=2.0.0
25
+ Requires-Dist: networkx>=3.3
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Requires-Dist: pyyaml>=6.0
28
+ Requires-Dist: qdrant-client>=1.12.0
29
+ Requires-Dist: sentence-transformers>=3.0.0
30
+ Requires-Dist: zstandard>=0.22
31
+ Provides-Extra: analytics
32
+ Requires-Dist: duckdb>=1.0; extra == 'analytics'
33
+ Requires-Dist: sqlglot>=23.0; extra == 'analytics'
34
+ Provides-Extra: dashboard
35
+ Requires-Dist: fastapi>=0.110; extra == 'dashboard'
36
+ Requires-Dist: uvicorn[standard]>=0.29; extra == 'dashboard'
37
+ Provides-Extra: dev
38
+ Requires-Dist: httpx>=0.27; extra == 'dev'
39
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
40
+ Requires-Dist: pytest>=8.0; extra == 'dev'
41
+ Requires-Dist: ruff>=0.5.0; extra == 'dev'
42
+ Description-Content-Type: text/markdown
43
+
44
+ # localmem
45
+
46
+ **Local-first multi-agent memory MCP server.** Persistent storage for LLM
47
+ agents — hybrid (dense + sparse) vector search, behavioral pattern graphs,
48
+ temporal knowledge triples, layered wake-up context, lifecycle management,
49
+ and a read-only browser dashboard. All on-device, no cloud dependencies.
50
+
51
+ Exposes its functionality over the Model Context Protocol (SSE transport), so
52
+ any MCP-capable client — Claude Code, Cursor, Continue, custom agents — can
53
+ read and write memory through a single shared server.
54
+
55
+ ## Why
56
+
57
+ Most "memory" for LLM agents is either a flat key-value store or a
58
+ single-agent knowledge graph. Real agent systems need more:
59
+
60
+ - **Per-agent namespaces.** Each agent's notes, decisions, and observations
61
+ stay in its own wing. A reserved `shared` wing carries cross-agent context.
62
+ - **Hybrid retrieval.** Dense embeddings catch semantic matches, sparse BM25
63
+ catches exact terms, RRF fuses both. Either signal alone misses too much.
64
+ - **Behavioral graphs.** Some relationships live in entries; others live in
65
+ the connections between them — co-occurrence, sequence, community structure.
66
+ - **Temporal knowledge.** Facts change. Knowledge triples track validity
67
+ windows and surface contradictions automatically.
68
+ - **Graceful forgetting.** Three-tier lifecycle (hot → warm summaries →
69
+ cold compressed archive) keeps the working set fast without losing history.
70
+ - **Token-aware loading.** Layered wake-up context (L0 manifest → L1 critical
71
+ → L2 scoped search → L3 verbatim) gives an agent ~170 tokens of high-signal
72
+ context without pulling the whole store.
73
+
74
+ ## Quick start
75
+
76
+ ```bash
77
+ git clone https://github.com/jordanaftermidnight/localmem.git
78
+ cd localmem
79
+ pip install -e ".[dev]"
80
+
81
+ # 1. Edit localmem.yaml — at minimum list your agent wings:
82
+ # wings:
83
+ # - my_assistant
84
+ #
85
+ # 2. Start the MCP server:
86
+ localmem serve # SSE on http://localhost:8781
87
+
88
+ # 3. (Optional) Start the read-only dashboard:
89
+ pip install -e ".[dashboard]"
90
+ localmem dashboard # REST + WS on http://localhost:8782
91
+ ( cd dashboard && npm install && npm run dev ) # UI on http://localhost:5173
92
+ ```
93
+
94
+ Connect any MCP client to `http://localhost:8781/sse` and the 22 tools below
95
+ become available.
96
+
97
+ ## MCP tools
98
+
99
+ | Group | Tool | Purpose |
100
+ | --- | --- | --- |
101
+ | Memory (6) | `localmem_store`, `localmem_search`, `localmem_retrieve`, `localmem_update`, `localmem_pin`, `localmem_unpin` | Entry CRUD + hybrid search |
102
+ | Graph (5) | `localmem_graph_add_node`, `localmem_graph_add_edge`, `localmem_graph_query`, `localmem_graph_neighbors`, `localmem_graph_communities` | Behavioral pattern graph |
103
+ | Knowledge (3) | `localmem_triple_assert`, `localmem_triple_query`, `localmem_triple_contradictions` | Temporal triples with contradiction detection |
104
+ | System (3) | `localmem_wake`, `localmem_health`, `localmem_metrics` | Layered wake-up + observability |
105
+ | Intelligence (3) | `localmem_intel_detect`, `localmem_intel_alerts`, `localmem_intel_report` | Pattern detection (opt-in via config) |
106
+ | Operations (2) | `localmem_prune`, `localmem_archive` | Retention triggers |
107
+
108
+ ## Storage stack
109
+
110
+ - **[Qdrant](https://qdrant.tech/)** — embedded by default (path-backed,
111
+ single-writer). Switch to a remote Qdrant via `storage.qdrant_mode: server`
112
+ + `storage.qdrant_url` to unblock live embedding migrations and
113
+ multi-process writers.
114
+ - **[NetworkX](https://networkx.org/)** — in-process directed multigraph with
115
+ multi-hop traversal and Louvain community detection.
116
+ - **SQLite** (WAL) — temporal triples, agent diaries, wing/room taxonomy,
117
+ importance scoring with time-decay.
118
+
119
+ ## Configuration
120
+
121
+ `localmem.yaml` at the repo root is the single source of truth. The shipped
122
+ defaults run locally with zero edits — set `wings:` to name your agents and
123
+ you're done. See inline comments for every section. Highlights:
124
+
125
+ - `wings: [list]` — your agent namespaces. `shared` is implicit.
126
+ - `embedding.model` — `all-MiniLM-L6-v2` (384d, fast) or BGE-large (1024d,
127
+ quality). Switch live with `localmem migrate-embeddings --to <model>`.
128
+ - `retention.enabled: true` — opt in to the three-tier lifecycle.
129
+ - `dashboard.auth_enabled: true` + bearer key for remote dashboard access.
130
+ - `intelligence.detectors.*` — each pattern detector is off until you point
131
+ it at a specific wing/room (or node selector for the graph cluster
132
+ detector). Nothing runs you didn't ask for.
133
+
134
+ Any string value supports `${VAR}` or `${VAR:-default}` env-var
135
+ interpolation, so secrets stay out of YAML on disk.
136
+
137
+ ## Dashboard
138
+
139
+ A read-only browser UI under `dashboard/` (Vite + React + dockview). 10
140
+ panels: Health, Entries, Metrics, Alerts, Graph, Wings/Rooms, Triples,
141
+ Diaries, Logs, Admin. Pin/unpin and lifecycle triggers live in Admin.
142
+ Localhost-only by default; flip on bearer auth to expose it remotely.
143
+
144
+ ![localmem dashboard](docs/assets/dashboard.png)
145
+
146
+ ## Observability
147
+
148
+ - `localmem health` and `localmem_health` MCP tool — per-wing entry counts,
149
+ store connectivity, embedding device, retention worker status.
150
+ - `localmem_metrics` MCP tool — per-tool call counts, p50/p95/p99 latency,
151
+ error counts (rolling window).
152
+ - `/metrics` Prometheus exposition endpoint on the dashboard sidecar (`text/plain;
153
+ version=0.0.4`). See [docs/DASHBOARD.md](docs/DASHBOARD.md) for the metric
154
+ reference and example scrape config.
155
+ - Structured logging (text or JSON) with optional `RotatingFileHandler`. See
156
+ [docs/LOGGING.md](docs/LOGGING.md) for Loki + Promtail and ELK + Filebeat
157
+ shipping configs.
158
+
159
+ ## Deployment
160
+
161
+ `deploy/` contains installer scripts for the three major platforms — each
162
+ generates a config dir, sets up a service (systemd / launchd / Scheduled
163
+ Tasks), and writes an `api_key` to a perms-restricted env file:
164
+
165
+ ```bash
166
+ deploy/setup-ubuntu.sh --auth --qdrant-server http://qdrant:6333
167
+ deploy/setup-macos.sh --auth
168
+ deploy/setup-windows.ps1 -Auth
169
+ ```
170
+
171
+ ## Documentation
172
+
173
+ - [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — full specification
174
+ - [`docs/DASHBOARD.md`](docs/DASHBOARD.md) — dashboard panels, auth, metrics
175
+ - [`docs/LIFECYCLE.md`](docs/LIFECYCLE.md) — retention / consolidation / archive
176
+ - [`docs/MIGRATIONS.md`](docs/MIGRATIONS.md) — schema and embedding migrations
177
+ - [`docs/LOGGING.md`](docs/LOGGING.md) — log shipping recipes
178
+
179
+ ## Project layout
180
+
181
+ ```
182
+ localmem/
183
+ ├── src/localmem/ # Package source
184
+ ├── dashboard/ # React + dockview frontend
185
+ ├── deploy/ # Installers + service units
186
+ ├── docs/ # Architecture, dashboard, lifecycle, migrations, logging
187
+ ├── manifests/ # Per-agent wake-up manifests
188
+ ├── tests/ # 300+ tests
189
+ ├── localmem.yaml # Default configuration
190
+ └── pyproject.toml
191
+ ```
192
+
193
+ ## Known issues
194
+
195
+ - **Python 3.14 + Apple Silicon + sentence-transformers**: the `loky`
196
+ process pool used by sentence-transformers can crash silently at shutdown
197
+ on Python 3.14 / arm64 macOS. Python 3.13 and earlier are unaffected.
198
+ Either use Python 3.13 (verified end-to-end on this build) or switch to
199
+ the sparse-only retrieval path via `embedding.model: "Qdrant/bm25"`.
200
+
201
+ ## License
202
+
203
+ MIT. See [LICENSE](LICENSE).