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.
- localmem-0.1.1/.githooks/audit-triad.sh +61 -0
- localmem-0.1.1/.githooks/pre-commit +58 -0
- localmem-0.1.1/.github/dependabot.yml +31 -0
- localmem-0.1.1/.github/workflows/ci.yml +53 -0
- localmem-0.1.1/.gitignore +52 -0
- localmem-0.1.1/CHANGELOG.md +149 -0
- localmem-0.1.1/LICENSE +21 -0
- localmem-0.1.1/PKG-INFO +203 -0
- localmem-0.1.1/README.md +160 -0
- localmem-0.1.1/dashboard/.gitignore +6 -0
- localmem-0.1.1/dashboard/eslint.config.js +23 -0
- localmem-0.1.1/dashboard/index.html +15 -0
- localmem-0.1.1/dashboard/package-lock.json +3354 -0
- localmem-0.1.1/dashboard/package.json +35 -0
- localmem-0.1.1/dashboard/src/App.tsx +160 -0
- localmem-0.1.1/dashboard/src/api.ts +148 -0
- localmem-0.1.1/dashboard/src/main.tsx +9 -0
- localmem-0.1.1/dashboard/src/panels/AdminPanel.tsx +168 -0
- localmem-0.1.1/dashboard/src/panels/AlertsPanel.tsx +62 -0
- localmem-0.1.1/dashboard/src/panels/DiariesPanel.tsx +68 -0
- localmem-0.1.1/dashboard/src/panels/EntryBrowser.tsx +162 -0
- localmem-0.1.1/dashboard/src/panels/GraphPanel.tsx +90 -0
- localmem-0.1.1/dashboard/src/panels/HealthPanel.tsx +81 -0
- localmem-0.1.1/dashboard/src/panels/LogsPanel.tsx +34 -0
- localmem-0.1.1/dashboard/src/panels/MetricsPanel.tsx +75 -0
- localmem-0.1.1/dashboard/src/panels/TaxonomyTree.tsx +69 -0
- localmem-0.1.1/dashboard/src/panels/TriplesPanel.tsx +82 -0
- localmem-0.1.1/dashboard/src/panels/index.ts +10 -0
- localmem-0.1.1/dashboard/src/store.ts +64 -0
- localmem-0.1.1/dashboard/src/theme.ts +118 -0
- localmem-0.1.1/dashboard/src/types.ts +194 -0
- localmem-0.1.1/dashboard/src/useWebSocket.ts +107 -0
- localmem-0.1.1/dashboard/tsconfig.app.json +26 -0
- localmem-0.1.1/dashboard/tsconfig.json +7 -0
- localmem-0.1.1/dashboard/tsconfig.node.json +24 -0
- localmem-0.1.1/dashboard/vite.config.ts +23 -0
- localmem-0.1.1/deploy/com.localmem.server.plist +49 -0
- localmem-0.1.1/deploy/cron/README.md +76 -0
- localmem-0.1.1/deploy/cron/com.localmem.prune.plist +36 -0
- localmem-0.1.1/deploy/cron/localmem-prune.service +13 -0
- localmem-0.1.1/deploy/cron/localmem-prune.timer +10 -0
- localmem-0.1.1/deploy/localmem.service +50 -0
- localmem-0.1.1/deploy/setup-macos.sh +212 -0
- localmem-0.1.1/deploy/setup-ubuntu.sh +293 -0
- localmem-0.1.1/deploy/setup-windows.ps1 +236 -0
- localmem-0.1.1/docs/ARCHITECTURE.md +635 -0
- localmem-0.1.1/docs/DASHBOARD.md +202 -0
- localmem-0.1.1/docs/LIFECYCLE.md +229 -0
- localmem-0.1.1/docs/LOGGING.md +128 -0
- localmem-0.1.1/docs/MIGRATIONS.md +222 -0
- localmem-0.1.1/docs/assets/dashboard.png +0 -0
- localmem-0.1.1/localmem.yaml +100 -0
- localmem-0.1.1/manifests/default.yaml +14 -0
- localmem-0.1.1/pyproject.toml +81 -0
- localmem-0.1.1/src/localmem/__init__.py +32 -0
- localmem-0.1.1/src/localmem/__main__.py +6 -0
- localmem-0.1.1/src/localmem/api/__init__.py +1 -0
- localmem-0.1.1/src/localmem/api/app.py +700 -0
- localmem-0.1.1/src/localmem/api/models.py +216 -0
- localmem-0.1.1/src/localmem/api/websocket.py +83 -0
- localmem-0.1.1/src/localmem/archiver.py +605 -0
- localmem-0.1.1/src/localmem/cli.py +935 -0
- localmem-0.1.1/src/localmem/config.py +305 -0
- localmem-0.1.1/src/localmem/consolidator.py +377 -0
- localmem-0.1.1/src/localmem/contradiction.py +68 -0
- localmem-0.1.1/src/localmem/embedder.py +103 -0
- localmem-0.1.1/src/localmem/embedding_migrator.py +299 -0
- localmem-0.1.1/src/localmem/graph_store.py +228 -0
- localmem-0.1.1/src/localmem/health.py +91 -0
- localmem-0.1.1/src/localmem/intelligence.py +433 -0
- localmem-0.1.1/src/localmem/logging_config.py +77 -0
- localmem-0.1.1/src/localmem/mcp_server.py +644 -0
- localmem-0.1.1/src/localmem/metadata_store.py +524 -0
- localmem-0.1.1/src/localmem/metrics.py +85 -0
- localmem-0.1.1/src/localmem/migrations/__init__.py +14 -0
- localmem-0.1.1/src/localmem/migrations/runner.py +174 -0
- localmem-0.1.1/src/localmem/migrations/v001_retention_foundations.py +66 -0
- localmem-0.1.1/src/localmem/models.py +188 -0
- localmem-0.1.1/src/localmem/prometheus_exposition.py +133 -0
- localmem-0.1.1/src/localmem/summarizer.py +93 -0
- localmem-0.1.1/src/localmem/summarizer_llm.py +244 -0
- localmem-0.1.1/src/localmem/taxonomy.py +89 -0
- localmem-0.1.1/src/localmem/vector_store.py +376 -0
- localmem-0.1.1/src/localmem/wake_up.py +115 -0
- localmem-0.1.1/src/localmem/worker.py +228 -0
- localmem-0.1.1/tests/conftest.py +29 -0
- localmem-0.1.1/tests/integration/__init__.py +0 -0
- localmem-0.1.1/tests/integration/conftest.py +107 -0
- localmem-0.1.1/tests/integration/test_live_mcp_client.py +247 -0
- localmem-0.1.1/tests/integration/test_ws_handshake.py +161 -0
- localmem-0.1.1/tests/test_api.py +377 -0
- localmem-0.1.1/tests/test_archiver.py +337 -0
- localmem-0.1.1/tests/test_consolidator.py +302 -0
- localmem-0.1.1/tests/test_contradiction.py +155 -0
- localmem-0.1.1/tests/test_embedding_migrator.py +221 -0
- localmem-0.1.1/tests/test_graph_store.py +221 -0
- localmem-0.1.1/tests/test_hardening.py +189 -0
- localmem-0.1.1/tests/test_health.py +150 -0
- localmem-0.1.1/tests/test_importance_decay.py +105 -0
- localmem-0.1.1/tests/test_intelligence.py +413 -0
- localmem-0.1.1/tests/test_mcp_server.py +344 -0
- localmem-0.1.1/tests/test_metadata_store.py +347 -0
- localmem-0.1.1/tests/test_metrics.py +109 -0
- localmem-0.1.1/tests/test_migrations.py +175 -0
- localmem-0.1.1/tests/test_prometheus_exposition.py +213 -0
- localmem-0.1.1/tests/test_qdrant_server_mode.py +146 -0
- localmem-0.1.1/tests/test_security_hardening_v011.py +151 -0
- localmem-0.1.1/tests/test_server_boot.py +102 -0
- localmem-0.1.1/tests/test_simulation.py +698 -0
- localmem-0.1.1/tests/test_summarizer_llm.py +164 -0
- localmem-0.1.1/tests/test_vector_store.py +242 -0
- 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.
|
localmem-0.1.1/PKG-INFO
ADDED
|
@@ -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
|
+

|
|
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).
|