superlocalmemory 3.6.22 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +275 -72
- package/bin/slm-npm +43 -89
- package/docs/pi-dev-integration.md +43 -0
- package/ide/configs/antigravity-mcp.json +2 -2
- package/ide/configs/chatgpt-desktop-mcp.json +1 -1
- package/ide/configs/claude-desktop-mcp.json +2 -2
- package/ide/configs/windsurf-mcp.json +2 -2
- package/ide/hooks/context-hook.js +6 -2
- package/ide/hooks/post-recall-hook.js +7 -3
- package/ide/hooks/tool-event-hook.sh +2 -1
- package/package.json +19 -10
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/_GENERATED.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/pyproject.toml +40 -8
- package/scripts/postinstall-interactive.js +17 -94
- package/scripts/postinstall.js +185 -258
- package/scripts/preuninstall.js +9 -50
- package/src/superlocalmemory/__init__.py +2 -2
- package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
- package/src/superlocalmemory/attribution/signer.py +34 -19
- package/src/superlocalmemory/attribution/watermark.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +3 -5
- package/src/superlocalmemory/cli/commands.py +490 -195
- package/src/superlocalmemory/cli/context_commands.py +5 -4
- package/src/superlocalmemory/cli/daemon.py +282 -187
- package/src/superlocalmemory/cli/db_migrate.py +3 -1
- package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
- package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
- package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
- package/src/superlocalmemory/cli/main.py +128 -31
- package/src/superlocalmemory/cli/pending_store.py +54 -38
- package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
- package/src/superlocalmemory/cli/service_installer.py +57 -52
- package/src/superlocalmemory/cli/setup_wizard.py +142 -88
- package/src/superlocalmemory/cli/version_banner.py +2 -1
- package/src/superlocalmemory/code_graph/config.py +3 -1
- package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
- package/src/superlocalmemory/core/config.py +65 -20
- package/src/superlocalmemory/core/consolidation_engine.py +9 -7
- package/src/superlocalmemory/core/context_cache.py +56 -8
- package/src/superlocalmemory/core/derivation_lineage.py +246 -0
- package/src/superlocalmemory/core/embedding_worker.py +32 -20
- package/src/superlocalmemory/core/embeddings.py +54 -18
- package/src/superlocalmemory/core/engine.py +150 -104
- package/src/superlocalmemory/core/engine_ingestion.py +513 -0
- package/src/superlocalmemory/core/engine_wiring.py +2 -0
- package/src/superlocalmemory/core/evidence_bundle.py +526 -0
- package/src/superlocalmemory/core/fact_consolidator.py +5 -11
- package/src/superlocalmemory/core/graph_analyzer.py +2 -2
- package/src/superlocalmemory/core/health_monitor.py +4 -2
- package/src/superlocalmemory/core/ingestion_command.py +636 -0
- package/src/superlocalmemory/core/injection.py +69 -18
- package/src/superlocalmemory/core/lifecycle_state.py +153 -0
- package/src/superlocalmemory/core/maintenance.py +23 -22
- package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
- package/src/superlocalmemory/core/mutations.py +143 -0
- package/src/superlocalmemory/core/platform_utils.py +7 -4
- package/src/superlocalmemory/core/ram_lock.py +16 -5
- package/src/superlocalmemory/core/rate_limit.py +1 -1
- package/src/superlocalmemory/core/recall_pipeline.py +60 -101
- package/src/superlocalmemory/core/recall_worker.py +76 -59
- package/src/superlocalmemory/core/registry.py +1 -1
- package/src/superlocalmemory/core/scale_engine.py +293 -0
- package/src/superlocalmemory/core/score_contract.py +62 -0
- package/src/superlocalmemory/core/security_primitives.py +3 -1
- package/src/superlocalmemory/core/slm_disabled.py +3 -5
- package/src/superlocalmemory/core/store_pipeline.py +172 -40
- package/src/superlocalmemory/core/tier_manager.py +32 -20
- package/src/superlocalmemory/core/worker_pool.py +13 -4
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
- package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
- package/src/superlocalmemory/encoding/auto_linker.py +1 -1
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
- package/src/superlocalmemory/encoding/consolidator.py +22 -5
- package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
- package/src/superlocalmemory/encoding/foresight.py +2 -0
- package/src/superlocalmemory/encoding/graph_builder.py +1 -1
- package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
- package/src/superlocalmemory/evaluation/__init__.py +13 -0
- package/src/superlocalmemory/evaluation/calibration.py +308 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
- package/src/superlocalmemory/graph/cozo_backend.py +256 -23
- package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
- package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
- package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
- package/src/superlocalmemory/hooks/auto_recall.py +31 -6
- package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
- package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
- package/src/superlocalmemory/hooks/claude_code_hooks.py +126 -39
- package/src/superlocalmemory/hooks/codex_assets.py +59 -0
- package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
- package/src/superlocalmemory/hooks/context_payload.py +1 -1
- package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
- package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
- package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
- package/src/superlocalmemory/hooks/hook_handlers.py +241 -55
- package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
- package/src/superlocalmemory/hooks/session_registry.py +15 -8
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
- package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
- package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
- package/src/superlocalmemory/infra/auth_middleware.py +38 -5
- package/src/superlocalmemory/infra/backup.py +7 -5
- package/src/superlocalmemory/infra/cloud_backup.py +18 -8
- package/src/superlocalmemory/infra/daemon_identity.py +248 -0
- package/src/superlocalmemory/infra/data_root.py +199 -0
- package/src/superlocalmemory/infra/event_bus.py +3 -1
- package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
- package/src/superlocalmemory/infra/process_reaper.py +23 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
- package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
- package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/credentials.py +14 -7
- package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
- package/src/superlocalmemory/learning/ensemble.py +11 -0
- package/src/superlocalmemory/learning/entity_compiler.py +1 -1
- package/src/superlocalmemory/learning/feedback.py +1 -1
- package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
- package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
- package/src/superlocalmemory/learning/ranker.py +4 -1
- package/src/superlocalmemory/learning/source_quality.py +1 -1
- package/src/superlocalmemory/learning/trigram_index.py +3 -2
- package/src/superlocalmemory/llm/backbone.py +13 -8
- package/src/superlocalmemory/math/ebbinghaus.py +1 -1
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/fisher_quantized.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +1 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/polar_quant.py +3 -4
- package/src/superlocalmemory/math/qjl.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +1 -1
- package/src/superlocalmemory/math/turbo_quant.py +3 -2
- package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
- package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
- package/src/superlocalmemory/mcp/http_transport.py +53 -0
- package/src/superlocalmemory/mcp/server.py +39 -13
- package/src/superlocalmemory/mcp/shared.py +69 -3
- package/src/superlocalmemory/mcp/tools_active.py +141 -31
- package/src/superlocalmemory/mcp/tools_core.py +128 -29
- package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
- package/src/superlocalmemory/mcp/tools_learning.py +42 -2
- package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
- package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
- package/src/superlocalmemory/mcp/tools_v28.py +23 -2
- package/src/superlocalmemory/mcp/tools_v3.py +26 -1
- package/src/superlocalmemory/mcp/tools_v33.py +56 -17
- package/src/superlocalmemory/mesh/broker.py +2 -0
- package/src/superlocalmemory/mesh/remote_sync.py +50 -12
- package/src/superlocalmemory/optimize/cache/manager.py +77 -1
- package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
- package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
- package/src/superlocalmemory/optimize/compress/router.py +6 -1
- package/src/superlocalmemory/optimize/config/__init__.py +5 -0
- package/src/superlocalmemory/optimize/config/store.py +6 -4
- package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
- package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
- package/src/superlocalmemory/optimize/proxy/server.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +14 -13
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
- package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
- package/src/superlocalmemory/retrieval/engine.py +169 -79
- package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
- package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
- package/src/superlocalmemory/retrieval/fusion.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
- package/src/superlocalmemory/retrieval/reranker.py +56 -20
- package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
- package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
- package/src/superlocalmemory/retrieval/strategy.py +1 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
- package/src/superlocalmemory/retrieval/vector_store.py +1 -1
- package/src/superlocalmemory/server/api.py +10 -7
- package/src/superlocalmemory/server/bandit_loops.py +4 -2
- package/src/superlocalmemory/server/recall_serializer.py +24 -0
- package/src/superlocalmemory/server/route_mutations.py +84 -0
- package/src/superlocalmemory/server/routes/agents.py +8 -6
- package/src/superlocalmemory/server/routes/brain.py +14 -12
- package/src/superlocalmemory/server/routes/chat.py +29 -12
- package/src/superlocalmemory/server/routes/data_io.py +55 -24
- package/src/superlocalmemory/server/routes/helpers.py +29 -4
- package/src/superlocalmemory/server/routes/ingest.py +53 -36
- package/src/superlocalmemory/server/routes/memories.py +104 -43
- package/src/superlocalmemory/server/routes/mesh.py +31 -0
- package/src/superlocalmemory/server/routes/profiles.py +26 -4
- package/src/superlocalmemory/server/routes/tiers.py +43 -11
- package/src/superlocalmemory/server/routes/timeline.py +5 -1
- package/src/superlocalmemory/server/routes/v3_api.py +76 -21
- package/src/superlocalmemory/server/security_middleware.py +1 -1
- package/src/superlocalmemory/server/ui.py +6 -3
- package/src/superlocalmemory/server/unified_daemon.py +680 -293
- package/src/superlocalmemory/server/write_identity.py +147 -0
- package/src/superlocalmemory/storage/access_log.py +4 -3
- package/src/superlocalmemory/storage/database.py +118 -25
- package/src/superlocalmemory/storage/migration_runner.py +84 -1
- package/src/superlocalmemory/storage/migration_v33.py +1 -1
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
- package/src/superlocalmemory/storage/models.py +16 -0
- package/src/superlocalmemory/storage/quantized_store.py +20 -3
- package/src/superlocalmemory/storage/v2_migrator.py +5 -3
- package/src/superlocalmemory/ui/favicon.svg +5 -0
- package/src/superlocalmemory/ui/index.html +1 -0
- package/src/superlocalmemory/ui/js/compliance.js +1 -1
- package/src/superlocalmemory/ui/js/core.js +49 -8
- package/src/superlocalmemory/ui/js/dashboard.js +23 -2
- package/src/superlocalmemory/ui/js/feedback.js +1 -1
- package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
- package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
- package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
- package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
- package/src/superlocalmemory/ui/js/settings.js +4 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
- package/bin/slm +0 -59
- package/bin/slm.bat +0 -77
- package/bin/slm.cmd +0 -5
- package/ide/integrations/langchain/README.md +0 -106
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
- package/ide/integrations/langchain/pyproject.toml +0 -38
- package/ide/integrations/langchain/tests/__init__.py +0 -3
- package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
- package/ide/integrations/langchain/tests/test_security.py +0 -117
- package/ide/integrations/llamaindex/README.md +0 -81
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
- package/ide/integrations/llamaindex/pyproject.toml +0 -43
- package/ide/integrations/llamaindex/tests/__init__.py +0 -3
- package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
- package/ide/integrations/llamaindex/tests/test_security.py +0 -241
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-memory-advisor.md +0 -44
- package/plugin-src/agents/slm-optimize-advisor.md +0 -38
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -23
- package/plugin-src/manifest.json +0 -25
- package/plugin-src/requirements.txt +0 -1
- package/plugin-src/rules/CLAUDE.md.fragment +0 -44
- package/plugin-src/scripts/ensure-venv.bat +0 -122
- package/plugin-src/scripts/ensure-venv.sh +0 -105
- package/plugin-src/scripts/slm-launch +0 -15
- package/plugin-src/scripts/slm-launch.bat +0 -17
- package/plugin-src/settings.json +0 -16
- package/plugin-src/skills/slm-cache/SKILL.md +0 -140
- package/plugin-src/skills/slm-compress/SKILL.md +0 -143
- package/plugin-src/skills/slm-graph/SKILL.md +0 -300
- package/plugin-src/skills/slm-recall/SKILL.md +0 -204
- package/plugin-src/skills/slm-remember/SKILL.md +0 -194
- package/plugin-src/skills/slm-session/SKILL.md +0 -207
- package/plugin-src/skills/slm-status/SKILL.md +0 -149
- package/scripts/__tests__/build-plugin.test.mjs +0 -613
- package/scripts/_savings_math.py +0 -270
- package/scripts/build-dmg.sh +0 -417
- package/scripts/build-plugin.js +0 -742
- package/scripts/build-slm-hook.ps1 +0 -40
- package/scripts/build-slm-hook.sh +0 -45
- package/scripts/build_entry.py +0 -452
- package/scripts/ci/stage5b_gate.sh +0 -50
- package/scripts/dogfood_savings.py +0 -490
- package/scripts/generate-thumbnails.py +0 -218
- package/scripts/install-skills.ps1 +0 -4
- package/scripts/install-skills.sh +0 -5
- package/scripts/install.ps1 +0 -701
- package/scripts/install.sh +0 -1015
- package/scripts/postinstall_binary.js +0 -287
- package/scripts/prepack.js +0 -33
- package/scripts/release_manifest.py +0 -273
- package/scripts/slm-hook.spec +0 -56
- package/scripts/start-dashboard.ps1 +0 -52
- package/scripts/start-dashboard.sh +0 -41
- package/scripts/sync-wiki.ps1 +0 -127
- package/scripts/sync-wiki.sh +0 -82
- package/scripts/test-dmg.sh +0 -161
- package/scripts/test-npm-package.ps1 +0 -252
- package/scripts/test-npm-package.sh +0 -207
- package/scripts/verify-install.ps1 +0 -294
- package/scripts/verify-install.sh +0 -266
- package/scripts/verify-v27.ps1 +0 -301
- package/scripts/verify-v27.sh +0 -233
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -513
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -71
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -51,6 +51,62 @@ class CozoDBQueryError(CozoDBError):
|
|
|
51
51
|
"""Datalog query execution failed."""
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
class _CozoRows:
|
|
55
|
+
"""Small pandas-values compatible view for PyCozo's dict results."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, rows: list[list[Any]]) -> None:
|
|
58
|
+
self._rows = rows
|
|
59
|
+
|
|
60
|
+
def tolist(self) -> list[list[Any]]:
|
|
61
|
+
return self._rows
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class _CozoResult:
|
|
65
|
+
"""Normalize old PyCozo dict responses to the dataframe surface we use."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, result: Any) -> None:
|
|
68
|
+
self._result = result
|
|
69
|
+
self.values = _CozoRows(list(result.get("rows", []))) if isinstance(result, dict) else result.values
|
|
70
|
+
|
|
71
|
+
def __len__(self) -> int:
|
|
72
|
+
return len(self.values.tolist())
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class _CozoClientAdapter:
|
|
76
|
+
"""Bridge PyCozo 0.3 embedded bindings and later client conveniences.
|
|
77
|
+
|
|
78
|
+
PyCozo 0.3 is the last client compatible with the published macOS native
|
|
79
|
+
binding. It returns dictionaries and exposes ``import_relations`` rather
|
|
80
|
+
than ``put``; later clients return dataframe-like values and add ``put``.
|
|
81
|
+
SLM only needs relation upserts and row results, so normalize those here.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(self, client: Any) -> None:
|
|
85
|
+
self._client = client
|
|
86
|
+
|
|
87
|
+
def run(self, script: str, params: dict[str, Any] | None = None) -> Any:
|
|
88
|
+
result = self._client.run(script) if params is None else self._client.run(script, params)
|
|
89
|
+
return _CozoResult(result) if isinstance(result, dict) else result
|
|
90
|
+
|
|
91
|
+
def put(self, relation: str, rows: list[dict[str, Any]]) -> None:
|
|
92
|
+
if not rows:
|
|
93
|
+
return
|
|
94
|
+
put = getattr(self._client, "put", None)
|
|
95
|
+
if callable(put):
|
|
96
|
+
put(relation, rows)
|
|
97
|
+
return
|
|
98
|
+
headers = list(rows[0])
|
|
99
|
+
self._client.import_relations({
|
|
100
|
+
relation: {
|
|
101
|
+
"headers": headers,
|
|
102
|
+
"rows": [[row.get(header) for header in headers] for row in rows],
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
def close(self) -> None:
|
|
107
|
+
self._client.close()
|
|
108
|
+
|
|
109
|
+
|
|
54
110
|
# ---------------------------------------------------------------------------
|
|
55
111
|
# CozoDBGraphBackend
|
|
56
112
|
# ---------------------------------------------------------------------------
|
|
@@ -70,7 +126,11 @@ class CozoDBGraphBackend:
|
|
|
70
126
|
path = Path(db_path)
|
|
71
127
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
72
128
|
self._db_path = str(path)
|
|
73
|
-
|
|
129
|
+
client = _CozoClient("rocksdb", self._db_path, dataframe=False) # type: ignore[misc]
|
|
130
|
+
self._db = _CozoClientAdapter(client)
|
|
131
|
+
self._shadow_checks = 0
|
|
132
|
+
self._shadow_mismatches = 0
|
|
133
|
+
self._shadow_errors = 0
|
|
74
134
|
self._ensure_schema()
|
|
75
135
|
|
|
76
136
|
def close(self) -> None:
|
|
@@ -100,8 +160,8 @@ class CozoDBGraphBackend:
|
|
|
100
160
|
try:
|
|
101
161
|
self._db.run("""
|
|
102
162
|
:create edge {
|
|
103
|
-
from_id: String, to_id: String =>
|
|
104
|
-
|
|
163
|
+
from_id: String, to_id: String, edge_type: String =>
|
|
164
|
+
weight: Float default 1.0,
|
|
105
165
|
metadata: String default '{}',
|
|
106
166
|
profile_id: String default 'default',
|
|
107
167
|
created_at: String
|
|
@@ -110,6 +170,21 @@ class CozoDBGraphBackend:
|
|
|
110
170
|
except Exception:
|
|
111
171
|
pass
|
|
112
172
|
|
|
173
|
+
# The entity recall channel resolves a query to canonical entity IDs,
|
|
174
|
+
# while graph_edges links fact IDs. Keeping those relations separate
|
|
175
|
+
# is essential: treating fact IDs as entities produces a healthy but
|
|
176
|
+
# semantically incompatible graph. ``fact_entity`` is the bridge
|
|
177
|
+
# that lets Cozo traverse the same two spaces as the SQLite channel.
|
|
178
|
+
try:
|
|
179
|
+
self._db.run("""
|
|
180
|
+
:create fact_entity {
|
|
181
|
+
fact_id: String, entity_id: String =>
|
|
182
|
+
profile_id: String default 'default'
|
|
183
|
+
}
|
|
184
|
+
""")
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
|
|
113
188
|
# ------------------------------------------------------------------
|
|
114
189
|
# Write Path
|
|
115
190
|
# ------------------------------------------------------------------
|
|
@@ -158,6 +233,60 @@ class CozoDBGraphBackend:
|
|
|
158
233
|
"created_at": now,
|
|
159
234
|
}])
|
|
160
235
|
|
|
236
|
+
def add_fact_entities(
|
|
237
|
+
self,
|
|
238
|
+
fact_id: str,
|
|
239
|
+
entity_ids: list[str],
|
|
240
|
+
profile_id: str = "default",
|
|
241
|
+
) -> None:
|
|
242
|
+
"""Upsert the canonical entities attached to one fact.
|
|
243
|
+
|
|
244
|
+
This is deliberately a separate relation from ``edge``. Fact edges
|
|
245
|
+
and canonical entity IDs are different namespaces in SLM.
|
|
246
|
+
"""
|
|
247
|
+
self._db.put("fact_entity", [
|
|
248
|
+
{"fact_id": fact_id, "entity_id": entity_id, "profile_id": profile_id}
|
|
249
|
+
for entity_id in dict.fromkeys(entity_ids)
|
|
250
|
+
if entity_id
|
|
251
|
+
])
|
|
252
|
+
|
|
253
|
+
def remove_fact(self, fact_id: str) -> None:
|
|
254
|
+
"""Remove a fact's derived graph records using bound query values."""
|
|
255
|
+
# Cozo :rm needs every non-key column in the output relation. Binding
|
|
256
|
+
# ``fact_id`` keeps apostrophes and Datalog syntax in external IDs from
|
|
257
|
+
# becoming executable query text.
|
|
258
|
+
self._db.run("""
|
|
259
|
+
?[fact_id, entity_id, profile_id] :=
|
|
260
|
+
*fact_entity{fact_id, entity_id, profile_id}, fact_id = $fact_id
|
|
261
|
+
:rm fact_entity {fact_id, entity_id => profile_id}
|
|
262
|
+
""", {"fact_id": fact_id})
|
|
263
|
+
|
|
264
|
+
def record_shadow_comparison(
|
|
265
|
+
self,
|
|
266
|
+
*,
|
|
267
|
+
matches: bool,
|
|
268
|
+
projected: list[tuple[str, float]],
|
|
269
|
+
canonical: list[tuple[str, float]],
|
|
270
|
+
) -> None:
|
|
271
|
+
"""Retain aggregate parity telemetry without persisting recalled text."""
|
|
272
|
+
self._shadow_checks += 1
|
|
273
|
+
if not matches:
|
|
274
|
+
self._shadow_mismatches += 1
|
|
275
|
+
logger.warning(
|
|
276
|
+
"Cozo entity recall diverged from canonical SQLite; using SQLite "
|
|
277
|
+
"(projected=%d canonical=%d)", len(projected), len(canonical),
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def record_shadow_error(self, error: str) -> None:
|
|
281
|
+
self._shadow_errors += 1
|
|
282
|
+
logger.warning("Cozo entity recall failed closed to SQLite: %s", error)
|
|
283
|
+
self._db.run("""
|
|
284
|
+
?[from_id, to_id, edge_type, weight, metadata, profile_id, created_at] :=
|
|
285
|
+
*edge{from_id, to_id, edge_type, weight, metadata, profile_id, created_at},
|
|
286
|
+
(from_id = $fact_id or to_id = $fact_id)
|
|
287
|
+
:rm edge {from_id, to_id, edge_type => weight, metadata, profile_id, created_at}
|
|
288
|
+
""", {"fact_id": fact_id})
|
|
289
|
+
|
|
161
290
|
# ------------------------------------------------------------------
|
|
162
291
|
# Bulk Import (SQLite → CozoDB)
|
|
163
292
|
# ------------------------------------------------------------------
|
|
@@ -178,37 +307,57 @@ class CozoDBGraphBackend:
|
|
|
178
307
|
if tier_filter is None:
|
|
179
308
|
tier_filter = ["active", "warm"]
|
|
180
309
|
|
|
181
|
-
# Step 1: Export
|
|
182
|
-
# graph_edges
|
|
183
|
-
#
|
|
310
|
+
# Step 1: Export canonical entity records. Do *not* synthesize
|
|
311
|
+
# entities from graph_edges: those are fact IDs and belong to the
|
|
312
|
+
# separate fact graph relation below.
|
|
184
313
|
entities_sql = """
|
|
185
|
-
SELECT
|
|
186
|
-
|
|
187
|
-
UNION
|
|
188
|
-
SELECT target_id as node_id FROM graph_edges WHERE profile_id = ?
|
|
189
|
-
)
|
|
314
|
+
SELECT entity_id, canonical_name, entity_type, first_seen, last_seen, fact_count
|
|
315
|
+
FROM canonical_entities WHERE profile_id = ?
|
|
190
316
|
"""
|
|
191
|
-
rows = conn.execute(entities_sql, (profile_id,
|
|
317
|
+
rows = conn.execute(entities_sql, (profile_id,)).fetchall()
|
|
192
318
|
|
|
193
319
|
entity_dicts = []
|
|
194
320
|
now = datetime.now().isoformat()
|
|
195
|
-
for
|
|
321
|
+
for entity_id, name, entity_type, first_seen, last_seen, fact_count in rows:
|
|
196
322
|
entity_dicts.append({
|
|
197
|
-
"id":
|
|
198
|
-
"name":
|
|
199
|
-
"entity_type": "
|
|
323
|
+
"id": entity_id,
|
|
324
|
+
"name": name,
|
|
325
|
+
"entity_type": entity_type or "concept",
|
|
200
326
|
"tier": "active",
|
|
201
|
-
"properties": "
|
|
327
|
+
"properties": json.dumps({"fact_count": int(fact_count or 0)}),
|
|
202
328
|
"profile_id": profile_id,
|
|
203
|
-
"created_at": now,
|
|
204
|
-
"updated_at": now,
|
|
329
|
+
"created_at": first_seen or now,
|
|
330
|
+
"updated_at": last_seen or now,
|
|
205
331
|
})
|
|
206
332
|
|
|
207
333
|
if entity_dicts:
|
|
208
334
|
self._db.put("entity", entity_dicts)
|
|
209
335
|
logger.info("CozoDB: imported %d entities", len(entity_dicts))
|
|
210
336
|
|
|
211
|
-
# Step 2: Export
|
|
337
|
+
# Step 2: Export fact-to-canonical-entity mappings. This relation is
|
|
338
|
+
# what allows a canonical query seed to enter the fact graph.
|
|
339
|
+
facts_sql = """
|
|
340
|
+
SELECT fact_id, canonical_entities_json
|
|
341
|
+
FROM atomic_facts WHERE profile_id = ?
|
|
342
|
+
"""
|
|
343
|
+
fact_entity_dicts: list[dict[str, str]] = []
|
|
344
|
+
for fact_id, raw_entities in conn.execute(facts_sql, (profile_id,)).fetchall():
|
|
345
|
+
try:
|
|
346
|
+
entity_ids = json.loads(raw_entities or "[]")
|
|
347
|
+
except (TypeError, ValueError, json.JSONDecodeError):
|
|
348
|
+
entity_ids = []
|
|
349
|
+
for entity_id in dict.fromkeys(entity_ids):
|
|
350
|
+
if entity_id:
|
|
351
|
+
fact_entity_dicts.append({
|
|
352
|
+
"fact_id": fact_id,
|
|
353
|
+
"entity_id": str(entity_id),
|
|
354
|
+
"profile_id": profile_id,
|
|
355
|
+
})
|
|
356
|
+
if fact_entity_dicts:
|
|
357
|
+
self._db.put("fact_entity", fact_entity_dicts)
|
|
358
|
+
|
|
359
|
+
# Step 3: Export fact graph edges directly. Fact graph traversal is
|
|
360
|
+
# intentionally kept in its native fact-ID namespace.
|
|
212
361
|
edges_sql = """
|
|
213
362
|
SELECT source_id, target_id, edge_type, weight
|
|
214
363
|
FROM graph_edges WHERE profile_id = ?
|
|
@@ -234,6 +383,83 @@ class CozoDBGraphBackend:
|
|
|
234
383
|
|
|
235
384
|
return len(edge_dicts)
|
|
236
385
|
|
|
386
|
+
def recall_facts(
|
|
387
|
+
self,
|
|
388
|
+
seed_entity_ids: list[str],
|
|
389
|
+
*,
|
|
390
|
+
profile_id: str = "default",
|
|
391
|
+
depth: int = 4,
|
|
392
|
+
decay: float = 0.7,
|
|
393
|
+
threshold: float = 0.05,
|
|
394
|
+
top_k: int = 50,
|
|
395
|
+
) -> list[tuple[str, float]]:
|
|
396
|
+
"""Mirror SLM's entity-to-fact/fact-graph activation in Cozo storage.
|
|
397
|
+
|
|
398
|
+
Query values never enter Datalog source. Cozo is used as the durable
|
|
399
|
+
projection; activation runs in Python so the algorithm stays aligned
|
|
400
|
+
with the SQLite in-memory channel and can be shadow-compared exactly.
|
|
401
|
+
"""
|
|
402
|
+
if not seed_entity_ids:
|
|
403
|
+
return []
|
|
404
|
+
entity_rows = self._db.run(
|
|
405
|
+
"?[fact_id, entity_id] := *fact_entity{fact_id, entity_id, profile_id}, profile_id = $profile_id",
|
|
406
|
+
{"profile_id": profile_id},
|
|
407
|
+
)
|
|
408
|
+
edge_rows = self._db.run(
|
|
409
|
+
"?[from_id, to_id, weight] := *edge{from_id, to_id, weight, profile_id}, profile_id = $profile_id",
|
|
410
|
+
{"profile_id": profile_id},
|
|
411
|
+
)
|
|
412
|
+
entity_to_facts: dict[str, list[str]] = {}
|
|
413
|
+
fact_to_entities: dict[str, list[str]] = {}
|
|
414
|
+
for fact_id, entity_id in entity_rows.values.tolist() if len(entity_rows) else []:
|
|
415
|
+
entity_to_facts.setdefault(str(entity_id), []).append(str(fact_id))
|
|
416
|
+
fact_to_entities.setdefault(str(fact_id), []).append(str(entity_id))
|
|
417
|
+
adjacency: dict[str, list[tuple[str, float]]] = {}
|
|
418
|
+
for source_id, target_id, weight in edge_rows.values.tolist() if len(edge_rows) else []:
|
|
419
|
+
source, target = str(source_id), str(target_id)
|
|
420
|
+
# Match EntityGraphChannel: graph edges are bidirectional during
|
|
421
|
+
# activation even when stored as directed rows.
|
|
422
|
+
adjacency.setdefault(source, []).append((target, float(weight)))
|
|
423
|
+
adjacency.setdefault(target, []).append((source, float(weight)))
|
|
424
|
+
|
|
425
|
+
activation: dict[str, float] = {}
|
|
426
|
+
visited_entities = set(seed_entity_ids)
|
|
427
|
+
for entity_id in seed_entity_ids:
|
|
428
|
+
for fact_id in entity_to_facts.get(entity_id, ()):
|
|
429
|
+
activation[fact_id] = max(activation.get(fact_id, 0.0), 1.0)
|
|
430
|
+
frontier = set(activation)
|
|
431
|
+
for hop in range(1, depth):
|
|
432
|
+
hop_decay = decay ** hop
|
|
433
|
+
if hop_decay < threshold:
|
|
434
|
+
break
|
|
435
|
+
next_frontier: set[str] = set()
|
|
436
|
+
for fact_id in frontier:
|
|
437
|
+
for neighbor_id, _weight in adjacency.get(fact_id, ()):
|
|
438
|
+
# SQLite intentionally ignores edge weights when graph
|
|
439
|
+
# metrics are unavailable; use that same baseline here.
|
|
440
|
+
score = activation[fact_id] * decay
|
|
441
|
+
if score >= threshold and score > activation.get(neighbor_id, 0.0):
|
|
442
|
+
activation[neighbor_id] = score
|
|
443
|
+
next_frontier.add(neighbor_id)
|
|
444
|
+
for fact_id in frontier:
|
|
445
|
+
for entity_id in fact_to_entities.get(fact_id, ()):
|
|
446
|
+
if entity_id in visited_entities:
|
|
447
|
+
continue
|
|
448
|
+
visited_entities.add(entity_id)
|
|
449
|
+
for related_fact_id in entity_to_facts.get(entity_id, ()):
|
|
450
|
+
if hop_decay > activation.get(related_fact_id, 0.0):
|
|
451
|
+
activation[related_fact_id] = hop_decay
|
|
452
|
+
next_frontier.add(related_fact_id)
|
|
453
|
+
frontier = next_frontier
|
|
454
|
+
if not frontier:
|
|
455
|
+
break
|
|
456
|
+
results = [(fact_id, score) for fact_id, score in activation.items() if score >= threshold]
|
|
457
|
+
if not results:
|
|
458
|
+
return []
|
|
459
|
+
maximum = max(score for _, score in results)
|
|
460
|
+
results = [(fact_id, score / maximum) for fact_id, score in results]
|
|
461
|
+
return sorted(results, key=lambda item: item[1], reverse=True)[:top_k]
|
|
462
|
+
|
|
237
463
|
# ------------------------------------------------------------------
|
|
238
464
|
# Spreading Activation (Python BFS over CozoDB edges)
|
|
239
465
|
# ------------------------------------------------------------------
|
|
@@ -267,10 +493,10 @@ class CozoDBGraphBackend:
|
|
|
267
493
|
for entity_id in current_frontier:
|
|
268
494
|
# Query all outgoing edges from this entity
|
|
269
495
|
try:
|
|
270
|
-
result = self._db.run(
|
|
496
|
+
result = self._db.run("""
|
|
271
497
|
?[to_id, weight] :=
|
|
272
|
-
*edge{
|
|
273
|
-
""")
|
|
498
|
+
*edge{from_id, to_id, weight}, from_id = $entity_id
|
|
499
|
+
""", {"entity_id": entity_id})
|
|
274
500
|
df = result if hasattr(result, "values") else result
|
|
275
501
|
if df is None or len(df) == 0:
|
|
276
502
|
continue
|
|
@@ -497,6 +723,9 @@ class CozoDBGraphBackend:
|
|
|
497
723
|
"status": "active",
|
|
498
724
|
"entities": int(ec),
|
|
499
725
|
"edges": int(edc),
|
|
726
|
+
"shadow_checks": self._shadow_checks,
|
|
727
|
+
"shadow_mismatches": self._shadow_mismatches,
|
|
728
|
+
"shadow_errors": self._shadow_errors,
|
|
500
729
|
"db_path": self._db_path,
|
|
501
730
|
}
|
|
502
731
|
except Exception as exc:
|
|
@@ -522,6 +751,10 @@ class CozoDBGraphBackend:
|
|
|
522
751
|
self._db.run("::remove edge")
|
|
523
752
|
except Exception:
|
|
524
753
|
pass
|
|
754
|
+
try:
|
|
755
|
+
self._db.run("::remove fact_entity")
|
|
756
|
+
except Exception:
|
|
757
|
+
pass
|
|
525
758
|
|
|
526
759
|
self._ensure_schema()
|
|
527
760
|
return self.bulk_import_from_sqlite(conn, profile_id)
|
|
@@ -36,7 +36,6 @@ import time
|
|
|
36
36
|
from pathlib import Path
|
|
37
37
|
from typing import IO, Optional
|
|
38
38
|
|
|
39
|
-
|
|
40
39
|
# ---------------------------------------------------------------------------
|
|
41
40
|
# Budget constants
|
|
42
41
|
# ---------------------------------------------------------------------------
|
|
@@ -63,17 +62,19 @@ PERF_LOG_CHECK_EVERY: int = 256 # check size every N writes, not every write
|
|
|
63
62
|
|
|
64
63
|
|
|
65
64
|
def slm_home() -> Path:
|
|
66
|
-
"""Return
|
|
65
|
+
"""Return the canonical runtime-state root for outcome hooks.
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
The central resolver owns environment alias precedence. Keeping that
|
|
68
|
+
policy out of this hot-path module prevents hooks from silently selecting
|
|
69
|
+
a different database than the daemon or MCP server.
|
|
70
70
|
|
|
71
71
|
SEC-M6 — first-creation chmod's the dir to 0700 so the audit marker
|
|
72
72
|
in ``ram_lock.sem`` (``{pid}:{name}``) and session-state files are
|
|
73
73
|
not world-readable on shared hosts.
|
|
74
74
|
"""
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
from superlocalmemory.infra.data_root import canonical_data_root
|
|
76
|
+
|
|
77
|
+
base = canonical_data_root()
|
|
77
78
|
try:
|
|
78
79
|
if not base.exists():
|
|
79
80
|
base.mkdir(parents=True, exist_ok=True)
|
|
@@ -266,7 +267,7 @@ _PERF_LOG_FD: Optional[IO[str]] = None
|
|
|
266
267
|
_PERF_LOG_PATH: Optional[Path] = None
|
|
267
268
|
# S9-W3 M-PERF-02: RLock (not Lock) so a reentrant acquire during
|
|
268
269
|
# atexit shutdown — e.g. a handler that calls ``log_perf`` while
|
|
269
|
-
# ``
|
|
270
|
+
# ``close_perf_log`` already holds the lock — does not deadlock
|
|
270
271
|
# the interpreter for the 30s graceful-shutdown timeout.
|
|
271
272
|
_PERF_LOG_LOCK = threading.RLock()
|
|
272
273
|
_PERF_LOG_WRITE_COUNT: int = 0 # SEC-M4 — rotation cadence counter
|
|
@@ -340,8 +341,12 @@ def _maybe_rotate_perf_log(path: Path) -> None:
|
|
|
340
341
|
pass
|
|
341
342
|
|
|
342
343
|
|
|
343
|
-
def
|
|
344
|
-
"""Flush the
|
|
344
|
+
def close_perf_log() -> None:
|
|
345
|
+
"""Flush and close the process-owned perf-log stream. Never raises.
|
|
346
|
+
|
|
347
|
+
Long-lived hosts and isolated tests can call this at their lifecycle
|
|
348
|
+
boundary; the atexit registration remains the final safety net.
|
|
349
|
+
"""
|
|
345
350
|
global _PERF_LOG_FD
|
|
346
351
|
with _PERF_LOG_LOCK:
|
|
347
352
|
fd = _PERF_LOG_FD
|
|
@@ -358,7 +363,12 @@ def _perf_log_flush() -> None:
|
|
|
358
363
|
pass
|
|
359
364
|
|
|
360
365
|
|
|
361
|
-
|
|
366
|
+
def _perf_log_flush() -> None:
|
|
367
|
+
"""Backward-compatible private alias for existing daemon shutdown code."""
|
|
368
|
+
close_perf_log()
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
atexit.register(close_perf_log)
|
|
362
372
|
|
|
363
373
|
|
|
364
374
|
#: S9-W3 C8: rotation flag set on hot path, drained on exit / next
|
|
@@ -407,7 +417,7 @@ def log_perf(hook_name: str, duration_ms: float, outcome: str) -> None:
|
|
|
407
417
|
|
|
408
418
|
Best-effort: disk full / unwritable dir → silently skip. Uses a
|
|
409
419
|
module-level append-only fd opened on first use and flushed on
|
|
410
|
-
process exit via :func:`
|
|
420
|
+
process exit via :func:`close_perf_log`.
|
|
411
421
|
|
|
412
422
|
S9-W3 C8: the rotation/rename/reopen workflow has moved OFF the
|
|
413
423
|
hot-path lock. Previously every 256th call held the lock across
|
|
@@ -35,15 +35,11 @@ from superlocalmemory.hooks.adapter_base import (
|
|
|
35
35
|
truncate_to_cap,
|
|
36
36
|
)
|
|
37
37
|
from superlocalmemory.hooks.context_payload import (
|
|
38
|
+
VERSION,
|
|
38
39
|
ContextPayload,
|
|
39
40
|
RecallFn,
|
|
40
|
-
build_payload,
|
|
41
|
-
format_decisions,
|
|
42
|
-
format_entities,
|
|
43
|
-
format_memories,
|
|
44
|
-
format_topics,
|
|
45
|
-
truncate_payload_for_cap,
|
|
46
41
|
)
|
|
42
|
+
from superlocalmemory.hooks.memory_protocol import memory_protocol_markdown
|
|
47
43
|
|
|
48
44
|
logger = logging.getLogger(__name__)
|
|
49
45
|
|
|
@@ -63,30 +59,21 @@ GLOBAL_REL = f".gemini/antigravity/{_SKILLS}/{_GLOBAL_SKILL_NAME}/SKILL.md"
|
|
|
63
59
|
_FRONTMATTER = (
|
|
64
60
|
"---\n"
|
|
65
61
|
"name: slm-memory-adapter\n"
|
|
66
|
-
"description: \"
|
|
67
|
-
"(topics, entities, decisions) at the start of every "
|
|
68
|
-
"Antigravity conversation.\"\n"
|
|
62
|
+
"description: \"SuperLocalMemory runtime MCP memory protocol.\"\n"
|
|
69
63
|
"---\n"
|
|
70
64
|
)
|
|
71
65
|
|
|
72
66
|
_BODY_TEMPLATE = (
|
|
73
|
-
"\n# SLM Memory
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"## Entities\n{entities}\n\n"
|
|
77
|
-
"## Recent decisions\n{decisions}\n\n"
|
|
78
|
-
"## Project memories\n{memories}\n"
|
|
67
|
+
"\n# SLM Runtime Memory Protocol\n\n"
|
|
68
|
+
"_Managed by SuperLocalMemory v{version}. This skill contains no recalled memory._\n\n"
|
|
69
|
+
"{protocol}"
|
|
79
70
|
)
|
|
80
71
|
|
|
81
72
|
|
|
82
|
-
def render_antigravity(payload: ContextPayload) -> bytes:
|
|
73
|
+
def render_antigravity(payload: ContextPayload | None = None) -> bytes:
|
|
83
74
|
body = _BODY_TEMPLATE.format(
|
|
84
|
-
version=payload.version,
|
|
85
|
-
|
|
86
|
-
topics=format_topics(payload),
|
|
87
|
-
entities=format_entities(payload),
|
|
88
|
-
decisions=format_decisions(payload),
|
|
89
|
-
memories=format_memories(payload),
|
|
75
|
+
version=payload.version if payload is not None else VERSION,
|
|
76
|
+
protocol=memory_protocol_markdown(),
|
|
90
77
|
)
|
|
91
78
|
return (_FRONTMATTER + body).encode("utf-8")
|
|
92
79
|
|
|
@@ -144,15 +131,7 @@ class AntigravityAdapter:
|
|
|
144
131
|
self._inactive_until_retry = True
|
|
145
132
|
return False
|
|
146
133
|
|
|
147
|
-
|
|
148
|
-
scope_for_builder = "project" if self._scope == "workspace" else "global"
|
|
149
|
-
payload = build_payload(
|
|
150
|
-
self._profile_id, scope_for_builder, self._base_dir,
|
|
151
|
-
recall_fn=self._recall_fn,
|
|
152
|
-
)
|
|
153
|
-
rendered = truncate_payload_for_cap(
|
|
154
|
-
payload, hard_cap=HARD_BYTES_CAP, render=render_antigravity,
|
|
155
|
-
)
|
|
134
|
+
rendered = render_antigravity()
|
|
156
135
|
rendered = truncate_to_cap(rendered, cap=HARD_BYTES_CAP)
|
|
157
136
|
|
|
158
137
|
result: WriteResult = atomic_write(
|
|
@@ -21,11 +21,11 @@ Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
|
21
21
|
|
|
22
22
|
from __future__ import annotations
|
|
23
23
|
|
|
24
|
-
import json
|
|
25
24
|
import logging
|
|
26
25
|
import math
|
|
27
26
|
|
|
28
27
|
from superlocalmemory.core.config import AutoInvokeConfig
|
|
28
|
+
from superlocalmemory.core.injection import InjectableMemory, render_context
|
|
29
29
|
|
|
30
30
|
logger = logging.getLogger(__name__)
|
|
31
31
|
|
|
@@ -90,16 +90,19 @@ class AutoInvoker:
|
|
|
90
90
|
limit=self._config.max_memories_injected,
|
|
91
91
|
)
|
|
92
92
|
|
|
93
|
-
memory_context = self.format_for_injection(results) if results else ""
|
|
94
|
-
|
|
95
|
-
# V3.3: Inject soft prompts (priority over memory context)
|
|
96
93
|
soft_prompt_text = self._get_soft_prompt_text()
|
|
97
|
-
if soft_prompt_text
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
94
|
+
if soft_prompt_text:
|
|
95
|
+
results = [
|
|
96
|
+
{
|
|
97
|
+
"fact_id": "",
|
|
98
|
+
"content": soft_prompt_text,
|
|
99
|
+
"fact_type": "behavioral-pattern",
|
|
100
|
+
"score": 0.0,
|
|
101
|
+
"contextual_description": "",
|
|
102
|
+
},
|
|
103
|
+
*results,
|
|
104
|
+
]
|
|
105
|
+
return self.format_for_injection(results) if results else ""
|
|
103
106
|
except Exception as exc:
|
|
104
107
|
logger.debug("Auto-invoke failed: %s", exc)
|
|
105
108
|
return ""
|
|
@@ -476,29 +479,24 @@ class AutoInvoker:
|
|
|
476
479
|
# ------------------------------------------------------------------
|
|
477
480
|
|
|
478
481
|
def format_for_injection(self, results: list[dict]) -> str:
|
|
479
|
-
"""Format results
|
|
480
|
-
|
|
481
|
-
Output: Markdown list with content previews and context.
|
|
482
|
-
"""
|
|
482
|
+
"""Format results as bounded, untrusted evidence with provenance."""
|
|
483
483
|
if not results:
|
|
484
484
|
return ""
|
|
485
485
|
|
|
486
|
-
|
|
486
|
+
memories: list[InjectableMemory] = []
|
|
487
487
|
for r in results:
|
|
488
|
-
|
|
488
|
+
content = str(r.get("content", ""))
|
|
489
489
|
ctx = r.get("contextual_description", "")
|
|
490
|
-
|
|
491
|
-
line = f"- [{r['fact_type']}] {content_preview}"
|
|
492
490
|
if ctx:
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
return "
|
|
491
|
+
content += f"\nContext: {ctx}"
|
|
492
|
+
memories.append(InjectableMemory(
|
|
493
|
+
content=content,
|
|
494
|
+
score=float(r.get("score", 0.0) or 0.0),
|
|
495
|
+
fact_id=str(r.get("fact_id", "")),
|
|
496
|
+
source_type=str(r.get("fact_type", "auto-invoke")),
|
|
497
|
+
source_id=f"fok-threshold:{self._config.fok_threshold}",
|
|
498
|
+
))
|
|
499
|
+
return render_context(memories, mode="B", cfg=None, wrap=True)
|
|
502
500
|
|
|
503
501
|
# ------------------------------------------------------------------
|
|
504
502
|
# V3.3: Soft prompt injection
|
|
@@ -9,6 +9,8 @@ from __future__ import annotations
|
|
|
9
9
|
import logging
|
|
10
10
|
from typing import Any, Callable
|
|
11
11
|
|
|
12
|
+
from superlocalmemory.core.injection import InjectableMemory, render_context
|
|
13
|
+
|
|
12
14
|
logger = logging.getLogger(__name__)
|
|
13
15
|
|
|
14
16
|
|
|
@@ -72,12 +74,18 @@ class AutoRecall:
|
|
|
72
74
|
if not relevant:
|
|
73
75
|
return ""
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
memories = [
|
|
78
|
+
InjectableMemory(
|
|
79
|
+
content=r.fact.content,
|
|
80
|
+
score=float(r.score),
|
|
81
|
+
fact_id=str(r.fact.fact_id),
|
|
82
|
+
importance=float(getattr(r.fact, "importance", 0.0) or 0.0),
|
|
83
|
+
access_count=int(getattr(r.fact, "access_count", 0) or 0),
|
|
84
|
+
source_type="recall",
|
|
85
|
+
)
|
|
86
|
+
for r in relevant[:self._max_memories]
|
|
87
|
+
]
|
|
88
|
+
return render_context(memories, mode="B", cfg=None, wrap=True)
|
|
81
89
|
except Exception as exc:
|
|
82
90
|
logger.warning("Auto-recall failed: %s", exc)
|
|
83
91
|
return ""
|
|
@@ -103,6 +111,23 @@ class AutoRecall:
|
|
|
103
111
|
"fact_id": r.fact.fact_id,
|
|
104
112
|
"content": r.fact.content[:300],
|
|
105
113
|
"score": round(r.score, 3),
|
|
114
|
+
"relevance_score": round(
|
|
115
|
+
getattr(r, "relevance_score", r.score) or 0.0, 3
|
|
116
|
+
),
|
|
117
|
+
"ranking_score": getattr(r, "ranking_score", None),
|
|
118
|
+
"confidence": round(
|
|
119
|
+
getattr(
|
|
120
|
+
r, "memory_confidence",
|
|
121
|
+
getattr(r, "confidence", 0.0),
|
|
122
|
+
) or 0.0, 3
|
|
123
|
+
),
|
|
124
|
+
"memory_confidence": round(
|
|
125
|
+
getattr(
|
|
126
|
+
r, "memory_confidence",
|
|
127
|
+
getattr(r, "confidence", 0.0),
|
|
128
|
+
) or 0.0, 3
|
|
129
|
+
),
|
|
130
|
+
"rank_position": int(getattr(r, "rank_position", 0) or 0),
|
|
106
131
|
})
|
|
107
132
|
return results
|
|
108
133
|
except Exception as exc:
|