nexo-brain 2.3.0 → 2.3.2
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/README.md +1 -1
- package/bin/nexo-brain.js +92 -9
- package/bin/postinstall.js +22 -15
- package/package.json +7 -4
- package/src/auto_update.py +194 -5
- package/src/crons/sync.py +6 -2
- package/src/db/_core.py +1 -0
- package/src/db/_entities.py +1 -0
- package/src/db/_episodic.py +1 -0
- package/src/db/_learnings.py +1 -0
- package/src/db/_reminders.py +1 -0
- package/src/db/_schema.py +11 -1
- package/src/db/_sessions.py +1 -0
- package/src/db/_skills.py +1 -0
- package/src/hooks/capture-tool-logs.sh +23 -6
- package/src/hooks/session-start.sh +4 -3
- package/src/plugin_loader.py +1 -0
- package/src/plugins/update.py +377 -26
- package/src/scripts/deep-sleep/apply_findings.py +1 -0
- package/src/scripts/deep-sleep/collect.py +1 -0
- package/src/scripts/deep-sleep/extract.py +1 -0
- package/src/scripts/deep-sleep/synthesize.py +1 -0
- package/src/scripts/nexo-catchup.py +29 -4
- package/src/scripts/nexo-daily-self-audit.py +21 -1
- package/src/scripts/nexo-evolution-run.py +21 -1
- package/src/scripts/nexo-learning-housekeep.py +1 -0
- package/src/scripts/nexo-postmortem-consolidator.py +34 -9
- package/src/scripts/nexo-sleep.py +32 -10
- package/src/scripts/nexo-synthesis.py +29 -9
- package/src/scripts/nexo-update.sh +109 -7
- package/src/scripts/nexo-watchdog.sh +122 -58
- package/src/server.py +66 -1
- package/src/tools_coordination.py +1 -0
- package/src/tools_sessions.py +1 -0
- package/scripts/migrate-to-unified 2.sh +0 -813
- package/scripts/migrate-to-unified.sh +0 -813
- package/scripts/migrate-v1.5-to-v1.6 2.py +0 -778
- package/scripts/migrate-v1.5-to-v1.6.py +0 -778
- package/scripts/migrate-v1.7-to-v1.8 2.py +0 -214
- package/scripts/migrate-v1.7-to-v1.8.py +0 -214
- package/scripts/nexo-preflight.sh +0 -236
- package/scripts/pre-commit-check 2.sh +0 -55
- package/scripts/pre-commit-check.sh +0 -55
- package/src/__pycache__/auto_close_sessions.cpython-314.pyc +0 -0
- package/src/__pycache__/auto_update.cpython-310.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-310.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-314.pyc +0 -0
- package/src/__pycache__/kg_populate.cpython-310.pyc +0 -0
- package/src/__pycache__/knowledge_graph.cpython-310.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-310.pyc +0 -0
- package/src/__pycache__/plugin_loader.cpython-314.pyc +0 -0
- package/src/__pycache__/tools_coordination.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_credentials.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_learnings.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_menu.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_reminders_crud.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_sessions.cpython-310.pyc +0 -0
- package/src/__pycache__/tools_task_history.cpython-310.pyc +0 -0
- package/src/auto_close_sessions 2.py +0 -159
- package/src/auto_update 2.py +0 -634
- package/src/claim_graph 2.py +0 -323
- package/src/cognitive/__init__ 2.py +0 -62
- package/src/cognitive/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_decay.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_ingest.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_memory.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_search.cpython-314.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-310.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-312.pyc +0 -0
- package/src/cognitive/__pycache__/_trust.cpython-314.pyc +0 -0
- package/src/cognitive/_core 2.py +0 -567
- package/src/cognitive/_decay 2.py +0 -382
- package/src/cognitive/_ingest 2.py +0 -892
- package/src/cognitive/_memory 2.py +0 -912
- package/src/cognitive/_search 2.py +0 -949
- package/src/cognitive/_trust 2.py +0 -464
- package/src/crons/__pycache__/sync.cpython-314.pyc +0 -0
- package/src/crons/manifest 2.json +0 -106
- package/src/crons/sync 2.py +0 -217
- package/src/dashboard/__init__ 2.py +0 -0
- package/src/dashboard/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/dashboard/__pycache__/app.cpython-310.pyc +0 -0
- package/src/dashboard/app 2.py +0 -789
- package/src/db/__init__ 2.py +0 -89
- package/src/db/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/db/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_core.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_credentials.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_cron_runs.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_cron_runs.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_entities.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_episodic.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_evolution.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_fts.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_learnings.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_reminders.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_schema.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_sessions.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_skills.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_skills.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_skills.cpython-314.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-310.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-312.pyc +0 -0
- package/src/db/__pycache__/_tasks.cpython-314.pyc +0 -0
- package/src/db/_core 2.py +0 -417
- package/src/db/_credentials 2.py +0 -124
- package/src/db/_entities 2.py +0 -178
- package/src/db/_episodic 2.py +0 -738
- package/src/db/_evolution 2.py +0 -54
- package/src/db/_fts 2.py +0 -406
- package/src/db/_learnings 2.py +0 -168
- package/src/db/_reminders 2.py +0 -338
- package/src/db/_schema 2.py +0 -364
- package/src/db/_sessions 2.py +0 -300
- package/src/db/_tasks 2.py +0 -91
- package/src/evolution_cycle 2.py +0 -266
- package/src/hnsw_index 2.py +0 -254
- package/src/hooks/auto_capture 2.py +0 -208
- package/src/hooks/caffeinate-guard 2.sh +0 -8
- package/src/hooks/capture-session 2.sh +0 -21
- package/src/hooks/capture-tool-logs 2.sh +0 -127
- package/src/hooks/daily-briefing-check 2.sh +0 -33
- package/src/hooks/inbox-hook 2.sh +0 -76
- package/src/hooks/post-compact 2.sh +0 -148
- package/src/hooks/pre-compact 2.sh +0 -151
- package/src/hooks/session-start 2.sh +0 -268
- package/src/hooks/session-stop 2.sh +0 -140
- package/src/kg_populate 2.py +0 -290
- package/src/knowledge_graph 2.py +0 -257
- package/src/maintenance 2.py +0 -59
- package/src/migrate_embeddings 2.py +0 -122
- package/src/plugin_loader 2.py +0 -202
- package/src/plugins/__init__ 2.py +0 -0
- package/src/plugins/__pycache__/__init__ 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/__init__.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/adaptive_mode.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/agents 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/agents.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/artifact_registry.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/backup 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/backup.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cognitive_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/core_rules.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cortex 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/cortex.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/entities 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/entities.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/episodic_memory.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/evolution 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/evolution.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/guard 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/guard.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/knowledge_graph_tools.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/preferences 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/preferences.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/schedule.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/schedule.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/skills.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/skills.cpython-314.pyc +0 -0
- package/src/plugins/__pycache__/update 2.cpython-310.pyc +0 -0
- package/src/plugins/__pycache__/update.cpython-310.pyc +0 -0
- package/src/plugins/adaptive_mode 2.py +0 -805
- package/src/plugins/agents 2.py +0 -52
- package/src/plugins/artifact_registry 2.py +0 -450
- package/src/plugins/backup 2.py +0 -104
- package/src/plugins/cognitive_memory 2.py +0 -564
- package/src/plugins/core_rules 2.py +0 -252
- package/src/plugins/cortex 2.py +0 -299
- package/src/plugins/entities 2.py +0 -67
- package/src/plugins/episodic_memory 2.py +0 -533
- package/src/plugins/evolution 2.py +0 -115
- package/src/plugins/guard 2.py +0 -746
- package/src/plugins/knowledge_graph_tools 2.py +0 -105
- package/src/plugins/preferences 2.py +0 -47
- package/src/plugins/update 2.py +0 -256
- package/src/requirements 2.txt +0 -12
- package/src/rules/__init__ 2.py +0 -0
- package/src/rules/core-rules 2.json +0 -331
- package/src/rules/migrate 2.py +0 -207
- package/src/scripts/__pycache__/nexo-auto-update.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-catchup.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-cognitive-decay.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-daily-self-audit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-evolution-run.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-followup-hygiene.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-immune.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-install.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-housekeep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-learning-validator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-migrate.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-postmortem-consolidator.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-pre-commit.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-proactive-dashboard.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-reflection.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-runtime-preflight.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-email.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-send-reply.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-sleep.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-synthesis.cpython-314.pyc +0 -0
- package/src/scripts/__pycache__/nexo-watchdog-smoke.cpython-314.pyc +0 -0
- package/src/scripts/check-context 2.py +0 -264
- package/src/scripts/nexo-auto-update 2.py +0 -6
- package/src/scripts/nexo-backup 2.sh +0 -25
- package/src/scripts/nexo-brain-activation 2.sh +0 -140
- package/src/scripts/nexo-catchup 2.py +0 -242
- package/src/scripts/nexo-cognitive-decay 2.py +0 -182
- package/src/scripts/nexo-daily-self-audit 2.py +0 -552
- package/src/scripts/nexo-deep-sleep 2.sh +0 -97
- package/src/scripts/nexo-evolution-run 2.py +0 -597
- package/src/scripts/nexo-followup-hygiene 2.py +0 -112
- package/src/scripts/nexo-github-monitor 2.py +0 -256
- package/src/scripts/nexo-immune 2.py +0 -927
- package/src/scripts/nexo-inbox-hook 2.sh +0 -74
- package/src/scripts/nexo-install 2.py +0 -6
- package/src/scripts/nexo-learning-housekeep 2.py +0 -245
- package/src/scripts/nexo-learning-validator 2.py +0 -207
- package/src/scripts/nexo-migrate 2.py +0 -232
- package/src/scripts/nexo-postmortem-consolidator 2.py +0 -421
- package/src/scripts/nexo-pre-commit 2.py +0 -120
- package/src/scripts/nexo-prevent-sleep 2.sh +0 -29
- package/src/scripts/nexo-proactive-dashboard 2.py +0 -345
- package/src/scripts/nexo-reflection 2.py +0 -253
- package/src/scripts/nexo-runtime-preflight 2.py +0 -274
- package/src/scripts/nexo-send-email 2.py +0 -25
- package/src/scripts/nexo-send-email.py +0 -25
- package/src/scripts/nexo-send-reply 2.py +0 -178
- package/src/scripts/nexo-send-reply.py +0 -178
- package/src/scripts/nexo-sleep 2.py +0 -592
- package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
- package/src/scripts/nexo-synthesis 2.py +0 -253
- package/src/scripts/nexo-tcc-approve 2.sh +0 -79
- package/src/scripts/nexo-update 2.sh +0 -161
- package/src/scripts/nexo-watchdog 2.sh +0 -878
- package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
- package/src/server 2.py +0 -733
- package/src/storage_router 2.py +0 -32
- package/src/tools_coordination 2.py +0 -102
- package/src/tools_credentials 2.py +0 -68
- package/src/tools_learnings 2.py +0 -220
- package/src/tools_menu 2.py +0 -227
- package/src/tools_reminders 2.py +0 -86
- package/src/tools_reminders_crud 2.py +0 -159
- package/src/tools_sessions 2.py +0 -476
- package/src/tools_task_history 2.py +0 -57
- package/templates/CLAUDE.md 2.template +0 -63
- package/templates/openclaw 2.json +0 -13
- package/tests/__init__ 2.py +0 -0
- package/tests/__init__.py +0 -0
- package/tests/conftest 2.py +0 -71
- package/tests/conftest.py +0 -71
- package/tests/test_cognitive 2.py +0 -205
- package/tests/test_cognitive.py +0 -205
- package/tests/test_knowledge_graph 2.py +0 -140
- package/tests/test_knowledge_graph.py +0 -140
- package/tests/test_migrations 2.py +0 -137
- package/tests/test_migrations.py +0 -137
|
@@ -1,382 +0,0 @@
|
|
|
1
|
-
"""NEXO Cognitive — Decay, promotion, garbage collection, dream consolidation."""
|
|
2
|
-
import math
|
|
3
|
-
import numpy as np
|
|
4
|
-
from datetime import datetime, timedelta
|
|
5
|
-
from cognitive._core import _get_db, embed, cosine_similarity, _blob_to_array, _array_to_blob, LAMBDA_STM, LAMBDA_LTM, EMBEDDING_DIM
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def _hnsw_invalidate():
|
|
9
|
-
"""Invalidate HNSW indices after bulk operations (best-effort)."""
|
|
10
|
-
try:
|
|
11
|
-
import hnsw_index
|
|
12
|
-
if hnsw_index.is_available():
|
|
13
|
-
hnsw_index.invalidate("both")
|
|
14
|
-
except Exception:
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def apply_decay(adaptive: bool = True):
|
|
19
|
-
"""Apply Ebbinghaus decay to all memories. Mark LTM as dormant if strength < 0.1.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
adaptive: If True, protect unique memories (no similar neighbors) from aggressive decay.
|
|
23
|
-
Unique memories decay at 25% of normal rate. This prevents information loss
|
|
24
|
-
in sparse memory stores where there's no redundancy to compensate.
|
|
25
|
-
"""
|
|
26
|
-
db = _get_db()
|
|
27
|
-
now = datetime.utcnow()
|
|
28
|
-
|
|
29
|
-
# Build redundancy map if adaptive mode — check which memories have similar siblings
|
|
30
|
-
_protected_stm = set()
|
|
31
|
-
_protected_ltm = set()
|
|
32
|
-
if adaptive:
|
|
33
|
-
# A memory is "protected" if it has no siblings in memory_siblings table
|
|
34
|
-
# (meaning no other memory covers similar content)
|
|
35
|
-
sibling_ids = set()
|
|
36
|
-
for row in db.execute("SELECT memory_a_id, memory_b_id FROM memory_siblings").fetchall():
|
|
37
|
-
sibling_ids.add(row["memory_a_id"])
|
|
38
|
-
sibling_ids.add(row["memory_b_id"])
|
|
39
|
-
|
|
40
|
-
# STM memories NOT in sibling_ids are unique → protect
|
|
41
|
-
for row in db.execute("SELECT id FROM stm_memories WHERE promoted_to_ltm = 0").fetchall():
|
|
42
|
-
if row["id"] not in sibling_ids:
|
|
43
|
-
_protected_stm.add(row["id"])
|
|
44
|
-
|
|
45
|
-
# LTM memories NOT in sibling_ids are unique → protect
|
|
46
|
-
for row in db.execute("SELECT id FROM ltm_memories WHERE is_dormant = 0").fetchall():
|
|
47
|
-
if row["id"] not in sibling_ids:
|
|
48
|
-
_protected_ltm.add(row["id"])
|
|
49
|
-
|
|
50
|
-
# STM decay (skip pinned)
|
|
51
|
-
rows = db.execute("SELECT id, last_accessed, strength FROM stm_memories WHERE promoted_to_ltm = 0 AND (lifecycle_state IS NULL OR lifecycle_state != 'pinned')").fetchall()
|
|
52
|
-
for row in rows:
|
|
53
|
-
last = datetime.fromisoformat(row["last_accessed"])
|
|
54
|
-
hours = (now - last).total_seconds() / 3600.0
|
|
55
|
-
decay_rate = LAMBDA_STM * 0.25 if (adaptive and row["id"] in _protected_stm) else LAMBDA_STM
|
|
56
|
-
new_strength = row["strength"] * math.exp(-decay_rate * hours)
|
|
57
|
-
db.execute("UPDATE stm_memories SET strength = ? WHERE id = ?", (new_strength, row["id"]))
|
|
58
|
-
|
|
59
|
-
# LTM decay (skip pinned)
|
|
60
|
-
rows = db.execute("SELECT id, last_accessed, strength FROM ltm_memories WHERE is_dormant = 0 AND (lifecycle_state IS NULL OR lifecycle_state != 'pinned')").fetchall()
|
|
61
|
-
for row in rows:
|
|
62
|
-
last = datetime.fromisoformat(row["last_accessed"])
|
|
63
|
-
hours = (now - last).total_seconds() / 3600.0
|
|
64
|
-
decay_rate = LAMBDA_LTM * 0.25 if (adaptive and row["id"] in _protected_ltm) else LAMBDA_LTM
|
|
65
|
-
new_strength = row["strength"] * math.exp(-decay_rate * hours)
|
|
66
|
-
if new_strength < 0.1:
|
|
67
|
-
db.execute("UPDATE ltm_memories SET strength = ?, is_dormant = 1 WHERE id = ?", (new_strength, row["id"]))
|
|
68
|
-
else:
|
|
69
|
-
db.execute("UPDATE ltm_memories SET strength = ? WHERE id = ?", (new_strength, row["id"]))
|
|
70
|
-
|
|
71
|
-
db.commit()
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def promote_stm_to_ltm():
|
|
75
|
-
"""Promote STM memories to LTM based on multiple criteria.
|
|
76
|
-
|
|
77
|
-
Promotion rules (any one is sufficient):
|
|
78
|
-
1. access_count >= 3 (actively retrieved = important)
|
|
79
|
-
2. age > 5 days AND strength > 0.4 (survived decay = worth keeping)
|
|
80
|
-
3. source_type in ('learning', 'decision', 'feedback') (high-value by nature)
|
|
81
|
-
"""
|
|
82
|
-
db = _get_db()
|
|
83
|
-
now = datetime.utcnow()
|
|
84
|
-
age_cutoff = (now - timedelta(days=5)).isoformat()
|
|
85
|
-
|
|
86
|
-
# Rule 1: frequently accessed
|
|
87
|
-
# Rule 2: old + strong (survived decay)
|
|
88
|
-
# Rule 3: high-value source types (always promote if in STM)
|
|
89
|
-
rows = db.execute(
|
|
90
|
-
"""SELECT * FROM stm_memories
|
|
91
|
-
WHERE promoted_to_ltm = 0
|
|
92
|
-
AND (
|
|
93
|
-
access_count >= 3
|
|
94
|
-
OR (created_at < ? AND strength > 0.4)
|
|
95
|
-
OR source_type IN ('learning', 'decision', 'feedback')
|
|
96
|
-
)""",
|
|
97
|
-
(age_cutoff,)
|
|
98
|
-
).fetchall()
|
|
99
|
-
|
|
100
|
-
promoted = 0
|
|
101
|
-
for row in rows:
|
|
102
|
-
redacted = row["redaction_applied"] if "redaction_applied" in row.keys() else 0
|
|
103
|
-
db.execute(
|
|
104
|
-
"""INSERT INTO ltm_memories (content, embedding, source_type, source_id, source_title, domain, original_stm_id, redaction_applied)
|
|
105
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
106
|
-
(row["content"], row["embedding"], row["source_type"], row["source_id"],
|
|
107
|
-
row["source_title"], row["domain"], row["id"], redacted)
|
|
108
|
-
)
|
|
109
|
-
db.execute("UPDATE stm_memories SET promoted_to_ltm = 1 WHERE id = ?", (row["id"],))
|
|
110
|
-
promoted += 1
|
|
111
|
-
|
|
112
|
-
db.commit()
|
|
113
|
-
if promoted > 0:
|
|
114
|
-
_hnsw_invalidate()
|
|
115
|
-
return promoted
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def gc_stm():
|
|
119
|
-
"""Garbage collect STM: delete weak old memories and anything > 45 days.
|
|
120
|
-
Pinned memories are never deleted.
|
|
121
|
-
"""
|
|
122
|
-
db = _get_db()
|
|
123
|
-
now = datetime.utcnow()
|
|
124
|
-
cutoff_7d = (now - timedelta(days=7)).isoformat()
|
|
125
|
-
cutoff_45d = (now - timedelta(days=45)).isoformat()
|
|
126
|
-
|
|
127
|
-
# Delete STM with strength < 0.3 and older than 7 days (skip pinned)
|
|
128
|
-
cur1 = db.execute(
|
|
129
|
-
"DELETE FROM stm_memories WHERE strength < 0.3 AND created_at < ? AND promoted_to_ltm = 0 "
|
|
130
|
-
"AND (lifecycle_state IS NULL OR lifecycle_state != 'pinned')",
|
|
131
|
-
(cutoff_7d,)
|
|
132
|
-
)
|
|
133
|
-
# Delete any STM older than 45 days (skip pinned)
|
|
134
|
-
cur2 = db.execute(
|
|
135
|
-
"DELETE FROM stm_memories WHERE created_at < ? AND promoted_to_ltm = 0 "
|
|
136
|
-
"AND (lifecycle_state IS NULL OR lifecycle_state != 'pinned')",
|
|
137
|
-
(cutoff_45d,)
|
|
138
|
-
)
|
|
139
|
-
db.commit()
|
|
140
|
-
deleted = (cur1.rowcount or 0) + (cur2.rowcount or 0)
|
|
141
|
-
if deleted > 0:
|
|
142
|
-
_hnsw_invalidate()
|
|
143
|
-
return deleted
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def gc_test_memories() -> int:
|
|
147
|
-
"""Purge STM memories from test/dev sessions that pollute strength metrics.
|
|
148
|
-
Removes memories with test domains or known test content patterns.
|
|
149
|
-
Returns count of deleted memories.
|
|
150
|
-
"""
|
|
151
|
-
db = _get_db()
|
|
152
|
-
test_domains = ("test", "test_session")
|
|
153
|
-
deleted = 0
|
|
154
|
-
|
|
155
|
-
# 1. Delete by test domain
|
|
156
|
-
for domain in test_domains:
|
|
157
|
-
cur = db.execute(
|
|
158
|
-
"DELETE FROM stm_memories WHERE domain = ? "
|
|
159
|
-
"AND (lifecycle_state IS NULL OR lifecycle_state != 'pinned')",
|
|
160
|
-
(domain,)
|
|
161
|
-
)
|
|
162
|
-
deleted += cur.rowcount or 0
|
|
163
|
-
|
|
164
|
-
# 2. Delete known test content patterns (empty domain, test-like content)
|
|
165
|
-
test_patterns = [
|
|
166
|
-
"%Secret redact test%",
|
|
167
|
-
"%quarantine test fact%",
|
|
168
|
-
"%Pin test memory%",
|
|
169
|
-
"%API rate limit%AM10%",
|
|
170
|
-
"%xyzzy server%",
|
|
171
|
-
"%Quantum entanglement enables FTL%",
|
|
172
|
-
"%Install Docker%AM10%",
|
|
173
|
-
"%normal safe content about coding%",
|
|
174
|
-
"%test diary%",
|
|
175
|
-
"%test critique%",
|
|
176
|
-
"%integration test diary%",
|
|
177
|
-
]
|
|
178
|
-
for pattern in test_patterns:
|
|
179
|
-
cur = db.execute(
|
|
180
|
-
"DELETE FROM stm_memories WHERE content LIKE ? "
|
|
181
|
-
"AND (lifecycle_state IS NULL OR lifecycle_state != 'pinned')",
|
|
182
|
-
(pattern,)
|
|
183
|
-
)
|
|
184
|
-
deleted += cur.rowcount or 0
|
|
185
|
-
|
|
186
|
-
if deleted > 0:
|
|
187
|
-
db.commit()
|
|
188
|
-
return deleted
|
|
189
|
-
|
|
190
|
-
def gc_sensory(max_age_hours: int = 48) -> int:
|
|
191
|
-
"""Garbage collect sensory memories older than max_age_hours. Returns count deleted."""
|
|
192
|
-
db = _get_db()
|
|
193
|
-
cutoff = (datetime.utcnow() - timedelta(hours=max_age_hours)).isoformat()
|
|
194
|
-
cur = db.execute(
|
|
195
|
-
"DELETE FROM stm_memories WHERE source_type = 'sensory' AND created_at < ? AND promoted_to_ltm = 0",
|
|
196
|
-
(cutoff,)
|
|
197
|
-
)
|
|
198
|
-
db.commit()
|
|
199
|
-
return cur.rowcount or 0
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def gc_ltm_dormant(min_age_days: int = 30) -> int:
|
|
203
|
-
"""Delete dormant LTM memories with strength < 0.1 older than min_age_days."""
|
|
204
|
-
db = _get_db()
|
|
205
|
-
cutoff = (datetime.utcnow() - timedelta(days=min_age_days)).isoformat()
|
|
206
|
-
cur = db.execute(
|
|
207
|
-
"DELETE FROM ltm_memories WHERE is_dormant = 1 AND strength < 0.1 AND created_at < ?",
|
|
208
|
-
(cutoff,)
|
|
209
|
-
)
|
|
210
|
-
db.commit()
|
|
211
|
-
return cur.rowcount or 0
|
|
212
|
-
|
|
213
|
-
def dream_cycle(max_insights: int = 50) -> dict:
|
|
214
|
-
"""Memory Dreaming — discover hidden connections between recent memories.
|
|
215
|
-
|
|
216
|
-
Retrieves memories accessed in the last 24h (STM + LTM), finds pairs with
|
|
217
|
-
moderate similarity (0.4-0.7 — related but not duplicates), and creates
|
|
218
|
-
'dream_insight' LTM memories linking them. Skips pairs already dreamed about.
|
|
219
|
-
|
|
220
|
-
Uses pure vector math — no LLM calls.
|
|
221
|
-
|
|
222
|
-
Returns:
|
|
223
|
-
Dict with 'insights_created' count and 'insights' list of details.
|
|
224
|
-
"""
|
|
225
|
-
db = _get_db()
|
|
226
|
-
cutoff_24h = (datetime.utcnow() - timedelta(hours=24)).isoformat()
|
|
227
|
-
|
|
228
|
-
# 1. Gather all memories accessed in the last 24 hours
|
|
229
|
-
recent_memories = []
|
|
230
|
-
|
|
231
|
-
stm_rows = db.execute(
|
|
232
|
-
"""SELECT id, content, embedding, source_type, source_title, domain, 'stm' as store
|
|
233
|
-
FROM stm_memories
|
|
234
|
-
WHERE last_accessed >= ? AND promoted_to_ltm = 0""",
|
|
235
|
-
(cutoff_24h,)
|
|
236
|
-
).fetchall()
|
|
237
|
-
|
|
238
|
-
ltm_rows = db.execute(
|
|
239
|
-
"""SELECT id, content, embedding, source_type, source_title, domain, 'ltm' as store
|
|
240
|
-
FROM ltm_memories
|
|
241
|
-
WHERE last_accessed >= ? AND is_dormant = 0""",
|
|
242
|
-
(cutoff_24h,)
|
|
243
|
-
).fetchall()
|
|
244
|
-
|
|
245
|
-
for row in stm_rows + ltm_rows:
|
|
246
|
-
recent_memories.append({
|
|
247
|
-
"id": row["id"],
|
|
248
|
-
"content": row["content"],
|
|
249
|
-
"vec": _blob_to_array(row["embedding"]),
|
|
250
|
-
"source_type": row["source_type"],
|
|
251
|
-
"source_title": row["source_title"] or "",
|
|
252
|
-
"domain": row["domain"] or "",
|
|
253
|
-
"store": row["store"],
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
if len(recent_memories) < 2:
|
|
257
|
-
return {"insights_created": 0, "insights": [], "memories_scanned": len(recent_memories), "candidates_found": 0}
|
|
258
|
-
|
|
259
|
-
# 2. Get already-dreamed pairs to skip
|
|
260
|
-
dreamed = set()
|
|
261
|
-
for row in db.execute("SELECT memory_a_id, memory_b_id FROM dreamed_pairs").fetchall():
|
|
262
|
-
dreamed.add((row["memory_a_id"], row["memory_b_id"]))
|
|
263
|
-
dreamed.add((row["memory_b_id"], row["memory_a_id"]))
|
|
264
|
-
|
|
265
|
-
# 3. Batch compute all pairwise cosine similarities
|
|
266
|
-
# Build matrix for fast numpy dot product
|
|
267
|
-
n = len(recent_memories)
|
|
268
|
-
vecs = np.array([m["vec"] for m in recent_memories], dtype=np.float32)
|
|
269
|
-
norms = np.linalg.norm(vecs, axis=1, keepdims=True)
|
|
270
|
-
norms[norms == 0] = 1.0 # avoid division by zero
|
|
271
|
-
normalized = vecs / norms
|
|
272
|
-
sim_matrix = normalized @ normalized.T # (n x n) cosine similarity matrix
|
|
273
|
-
|
|
274
|
-
# 4. Find pairs in the sweet spot (0.4-0.7) — related but not duplicates
|
|
275
|
-
candidate_pairs = []
|
|
276
|
-
for i in range(n):
|
|
277
|
-
for j in range(i + 1, n):
|
|
278
|
-
score = float(sim_matrix[i, j])
|
|
279
|
-
if 0.4 <= score <= 0.7:
|
|
280
|
-
# Use composite key for dreamed check (store:id to disambiguate stm vs ltm)
|
|
281
|
-
pair_key = (
|
|
282
|
-
f"{recent_memories[i]['store']}:{recent_memories[i]['id']}",
|
|
283
|
-
f"{recent_memories[j]['store']}:{recent_memories[j]['id']}",
|
|
284
|
-
)
|
|
285
|
-
# For DB tracking we use LTM IDs when both are LTM, else skip dreamed check
|
|
286
|
-
a_id, b_id = recent_memories[i]["id"], recent_memories[j]["id"]
|
|
287
|
-
if (a_id, b_id) in dreamed or (b_id, a_id) in dreamed:
|
|
288
|
-
continue
|
|
289
|
-
candidate_pairs.append((i, j, score))
|
|
290
|
-
|
|
291
|
-
# Sort by similarity descending (strongest connections first)
|
|
292
|
-
candidate_pairs.sort(key=lambda x: x[2], reverse=True)
|
|
293
|
-
|
|
294
|
-
# 5. Generate insights (capped at max_insights)
|
|
295
|
-
insights = []
|
|
296
|
-
for i, j, score in candidate_pairs[:max_insights]:
|
|
297
|
-
mem_a = recent_memories[i]
|
|
298
|
-
mem_b = recent_memories[j]
|
|
299
|
-
|
|
300
|
-
# Build titles — use source_title if available, else first 60 chars of content
|
|
301
|
-
title_a = mem_a["source_title"] or mem_a["content"][:60].replace("\n", " ").strip()
|
|
302
|
-
title_b = mem_b["source_title"] or mem_b["content"][:60].replace("\n", " ").strip()
|
|
303
|
-
|
|
304
|
-
# Build domain context
|
|
305
|
-
domains = set(filter(None, [mem_a["domain"], mem_b["domain"]]))
|
|
306
|
-
domain_str = ", ".join(domains) if domains else "general"
|
|
307
|
-
|
|
308
|
-
# Create insight content
|
|
309
|
-
insight_content = (
|
|
310
|
-
f"[Dream Insight] Connection found between:\n"
|
|
311
|
-
f" A: {title_a}\n"
|
|
312
|
-
f" B: {title_b}\n"
|
|
313
|
-
f"Similarity: {score:.3f} | Domains: {domain_str}\n"
|
|
314
|
-
f"These memories appeared together in the same 24h window and share moderate semantic overlap, "
|
|
315
|
-
f"suggesting a potential relationship worth investigating."
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
# Create embedding as average of the two source vectors (midpoint in vector space)
|
|
319
|
-
insight_vec = (mem_a["vec"] + mem_b["vec"]) / 2.0
|
|
320
|
-
insight_vec = insight_vec / (np.linalg.norm(insight_vec) or 1.0) # re-normalize
|
|
321
|
-
blob = _array_to_blob(insight_vec)
|
|
322
|
-
|
|
323
|
-
# Store as LTM with dream_insight tag
|
|
324
|
-
cur = db.execute(
|
|
325
|
-
"""INSERT INTO ltm_memories (content, embedding, source_type, source_id, source_title, domain, tags, strength)
|
|
326
|
-
VALUES (?, ?, 'dream_insight', ?, ?, ?, 'dream_insight', 0.5)""",
|
|
327
|
-
(insight_content, blob,
|
|
328
|
-
f"{mem_a['store']}:{mem_a['id']},{mem_b['store']}:{mem_b['id']}",
|
|
329
|
-
f"Dream: {title_a[:30]} <-> {title_b[:30]}",
|
|
330
|
-
domain_str)
|
|
331
|
-
)
|
|
332
|
-
insight_id = cur.lastrowid
|
|
333
|
-
|
|
334
|
-
# Track the dreamed pair
|
|
335
|
-
a_id, b_id = mem_a["id"], mem_b["id"]
|
|
336
|
-
try:
|
|
337
|
-
db.execute(
|
|
338
|
-
"INSERT OR IGNORE INTO dreamed_pairs (memory_a_id, memory_b_id, insight_id) VALUES (?, ?, ?)",
|
|
339
|
-
(min(a_id, b_id), max(a_id, b_id), insight_id)
|
|
340
|
-
)
|
|
341
|
-
except Exception:
|
|
342
|
-
pass
|
|
343
|
-
|
|
344
|
-
insights.append({
|
|
345
|
-
"insight_id": insight_id,
|
|
346
|
-
"title_a": title_a[:80],
|
|
347
|
-
"title_b": title_b[:80],
|
|
348
|
-
"similarity": round(score, 4),
|
|
349
|
-
"domain": domain_str,
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
db.commit()
|
|
353
|
-
|
|
354
|
-
# Dream cap: archive oldest dream_insights if total exceeds MAX_DREAM_INSIGHTS
|
|
355
|
-
MAX_DREAM_INSIGHTS = 50
|
|
356
|
-
dream_count = db.execute(
|
|
357
|
-
"SELECT COUNT(*) FROM ltm_memories WHERE source_type = 'dream_insight' AND is_dormant = 0"
|
|
358
|
-
).fetchone()[0]
|
|
359
|
-
archived_dreams = 0
|
|
360
|
-
if dream_count > MAX_DREAM_INSIGHTS:
|
|
361
|
-
excess = dream_count - MAX_DREAM_INSIGHTS
|
|
362
|
-
oldest = db.execute(
|
|
363
|
-
"SELECT id FROM ltm_memories WHERE source_type = 'dream_insight' AND is_dormant = 0 "
|
|
364
|
-
"ORDER BY strength ASC, created_at ASC LIMIT ?", (excess,)
|
|
365
|
-
).fetchall()
|
|
366
|
-
for row in oldest:
|
|
367
|
-
db.execute(
|
|
368
|
-
"UPDATE ltm_memories SET lifecycle_state = 'archived' WHERE id = ?", (row["id"],)
|
|
369
|
-
)
|
|
370
|
-
archived_dreams += 1
|
|
371
|
-
db.commit()
|
|
372
|
-
|
|
373
|
-
result = {
|
|
374
|
-
"insights_created": len(insights),
|
|
375
|
-
"insights": insights,
|
|
376
|
-
"memories_scanned": len(recent_memories),
|
|
377
|
-
"candidates_found": len(candidate_pairs),
|
|
378
|
-
}
|
|
379
|
-
if archived_dreams > 0:
|
|
380
|
-
result["dreams_archived"] = archived_dreams
|
|
381
|
-
result["dreams_total"] = dream_count
|
|
382
|
-
return result
|