superlocalmemory 3.7.8 → 3.8.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/.claude-plugin/marketplace.json +1 -1
- package/ATTRIBUTION.md +1 -3
- package/CHANGELOG.md +69 -0
- package/README.md +199 -29
- package/package.json +4 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/CLAUDE.md +8 -8
- package/plugin/agents/slm-governance-advisor.md +80 -0
- package/plugin/agents/slm-loop-runner.md +71 -0
- package/plugin/agents/slm-memory-advisor.md +10 -5
- package/plugin/agents/slm-optimize-advisor.md +9 -3
- package/plugin/commands/slm-loop.md +31 -0
- package/plugin/hooks/hooks.json +79 -0
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/slm-launch +46 -7
- package/plugin/settings.json +9 -0
- package/plugin/skills/slm-cache/SKILL.md +9 -1
- package/plugin/skills/slm-compress/SKILL.md +8 -1
- package/plugin/skills/slm-governance/SKILL.md +248 -0
- package/plugin/skills/slm-graph/SKILL.md +17 -3
- package/plugin/skills/slm-loop/SKILL.md +99 -0
- package/plugin/skills/slm-mesh/SKILL.md +282 -0
- package/plugin/skills/slm-profile/SKILL.md +148 -0
- package/plugin/skills/slm-recall/SKILL.md +46 -10
- package/plugin/skills/slm-remember/SKILL.md +48 -1
- package/plugin/skills/slm-scope/SKILL.md +176 -0
- package/plugin/skills/slm-session/SKILL.md +24 -1
- package/plugin/skills/slm-status/SKILL.md +18 -1
- package/plugin-src/agents/slm-governance-advisor.md +80 -0
- package/plugin-src/agents/slm-loop-runner.md +71 -0
- package/plugin-src/agents/slm-memory-advisor.md +10 -5
- package/plugin-src/agents/slm-optimize-advisor.md +9 -3
- package/plugin-src/commands/slm-loop.md +31 -0
- package/plugin-src/hooks/hooks.json +79 -0
- package/plugin-src/manifest.json +7 -2
- package/plugin-src/requirements.txt +1 -1
- package/plugin-src/rules/AGENTS.md +57 -18
- package/plugin-src/rules/CLAUDE.md.fragment +8 -8
- package/plugin-src/scripts/slm-launch +46 -7
- package/plugin-src/settings.json +9 -0
- package/plugin-src/skills/slm-cache/SKILL.md +9 -1
- package/plugin-src/skills/slm-compress/SKILL.md +8 -1
- package/plugin-src/skills/slm-governance/SKILL.md +248 -0
- package/plugin-src/skills/slm-graph/SKILL.md +17 -3
- package/plugin-src/skills/slm-loop/SKILL.md +99 -0
- package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
- package/plugin-src/skills/slm-profile/SKILL.md +148 -0
- package/plugin-src/skills/slm-recall/SKILL.md +46 -10
- package/plugin-src/skills/slm-remember/SKILL.md +48 -1
- package/plugin-src/skills/slm-scope/SKILL.md +176 -0
- package/plugin-src/skills/slm-session/SKILL.md +24 -1
- package/plugin-src/skills/slm-status/SKILL.md +18 -1
- package/pyproject.toml +1 -1
- package/scripts/postinstall/validation.js +2 -0
- package/scripts/postinstall-interactive.js +74 -2
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/__init__.py +3 -0
- package/src/superlocalmemory/access/rbac.py +477 -0
- package/src/superlocalmemory/cli/commands.py +94 -10
- package/src/superlocalmemory/cli/compress_cmd.py +17 -7
- package/src/superlocalmemory/cli/loop_cmd.py +192 -0
- package/src/superlocalmemory/cli/main.py +39 -4
- package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
- package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
- package/src/superlocalmemory/cli/pending_store.py +49 -13
- package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
- package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
- package/src/superlocalmemory/cli/setup_wizard.py +22 -13
- package/src/superlocalmemory/compliance/audit.py +6 -0
- package/src/superlocalmemory/compliance/gdpr.py +128 -138
- package/src/superlocalmemory/compliance/retention.py +176 -45
- package/src/superlocalmemory/core/backend_orchestrator.py +5 -43
- package/src/superlocalmemory/core/community_summary.py +267 -0
- package/src/superlocalmemory/core/config.py +216 -3
- package/src/superlocalmemory/core/consolidation_engine.py +95 -22
- package/src/superlocalmemory/core/context_cache.py +61 -18
- package/src/superlocalmemory/core/embedding_worker.py +17 -2
- package/src/superlocalmemory/core/embeddings.py +12 -1
- package/src/superlocalmemory/core/engine.py +17 -1
- package/src/superlocalmemory/core/engine_ingestion.py +29 -0
- package/src/superlocalmemory/core/engine_wiring.py +13 -0
- package/src/superlocalmemory/core/entity_community.py +178 -0
- package/src/superlocalmemory/core/graph_analyzer.py +39 -2
- package/src/superlocalmemory/core/graph_pruner.py +13 -8
- package/src/superlocalmemory/core/key_expander.py +138 -0
- package/src/superlocalmemory/core/maintenance.py +23 -0
- package/src/superlocalmemory/core/modes.py +1 -1
- package/src/superlocalmemory/core/mutations.py +2 -2
- package/src/superlocalmemory/core/pii.py +105 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
- package/src/superlocalmemory/core/recall_pipeline.py +2 -0
- package/src/superlocalmemory/core/recall_worker.py +20 -6
- package/src/superlocalmemory/core/scale_engine.py +60 -1
- package/src/superlocalmemory/core/security_primitives.py +40 -2
- package/src/superlocalmemory/core/store_pipeline.py +35 -11
- package/src/superlocalmemory/core/worker_pool.py +21 -6
- package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
- package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
- package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
- package/src/superlocalmemory/evolution/evolution_store.py +122 -45
- package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
- package/src/superlocalmemory/evolution/model_selection.py +160 -0
- package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
- package/src/superlocalmemory/evolution/triggers.py +22 -13
- package/src/superlocalmemory/graph/cozo_backend.py +43 -20
- package/src/superlocalmemory/hooks/adapter_base.py +5 -1
- package/src/superlocalmemory/hooks/auto_recall.py +13 -1
- package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
- package/src/superlocalmemory/hooks/codex_assets.py +64 -5
- package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
- package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
- package/src/superlocalmemory/hooks/portable_kit.py +114 -1
- package/src/superlocalmemory/infra/backup.py +12 -1
- package/src/superlocalmemory/infra/daemon_identity.py +40 -4
- package/src/superlocalmemory/infra/data_root.py +43 -4
- package/src/superlocalmemory/infra/event_bus.py +107 -24
- package/src/superlocalmemory/infra/rate_limiter.py +93 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
- package/src/superlocalmemory/ingestion/credentials.py +1 -1
- package/src/superlocalmemory/learning/cross_project.py +28 -19
- package/src/superlocalmemory/learning/reward_proxy.py +42 -9
- package/src/superlocalmemory/loops/__init__.py +56 -0
- package/src/superlocalmemory/loops/budget.py +58 -0
- package/src/superlocalmemory/loops/engine.py +164 -0
- package/src/superlocalmemory/loops/ledger.py +243 -0
- package/src/superlocalmemory/loops/models.py +152 -0
- package/src/superlocalmemory/loops/rules.py +52 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
- package/src/superlocalmemory/mcp/profiles.py +103 -0
- package/src/superlocalmemory/mcp/server.py +21 -49
- package/src/superlocalmemory/mcp/tools_active.py +4 -7
- package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
- package/src/superlocalmemory/mcp/tools_core.py +8 -1
- package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
- package/src/superlocalmemory/mcp/tools_loops.py +300 -0
- package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
- package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
- package/src/superlocalmemory/mesh/broker.py +237 -129
- package/src/superlocalmemory/mesh/remote_sync.py +50 -8
- package/src/superlocalmemory/optimize/NOTICE +1 -6
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
- package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
- package/src/superlocalmemory/optimize/compress/align.py +32 -26
- package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
- package/src/superlocalmemory/optimize/compress/router.py +105 -22
- package/src/superlocalmemory/optimize/config/defaults.py +1 -1
- package/src/superlocalmemory/optimize/config/schema.py +87 -4
- package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
- package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
- package/src/superlocalmemory/optimize/storage/db.py +38 -9
- package/src/superlocalmemory/optimize/storage/schema.py +10 -0
- package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
- package/src/superlocalmemory/retrieval/engine.py +168 -26
- package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
- package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
- package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
- package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
- package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
- package/src/superlocalmemory/retrieval/time_window.py +181 -0
- package/src/superlocalmemory/server/api.py +4 -4
- package/src/superlocalmemory/server/profile_runtime.py +125 -8
- package/src/superlocalmemory/server/rbac_enforce.py +142 -0
- package/src/superlocalmemory/server/recall_health.py +24 -3
- package/src/superlocalmemory/server/recall_serializer.py +19 -1
- package/src/superlocalmemory/server/routes/abstraction.py +115 -0
- package/src/superlocalmemory/server/routes/agents.py +128 -38
- package/src/superlocalmemory/server/routes/backup.py +34 -10
- package/src/superlocalmemory/server/routes/behavioral.py +13 -12
- package/src/superlocalmemory/server/routes/brain.py +21 -5
- package/src/superlocalmemory/server/routes/chat.py +10 -5
- package/src/superlocalmemory/server/routes/compliance.py +171 -21
- package/src/superlocalmemory/server/routes/config_api.py +436 -0
- package/src/superlocalmemory/server/routes/data_io.py +30 -8
- package/src/superlocalmemory/server/routes/entity.py +9 -4
- package/src/superlocalmemory/server/routes/events.py +24 -8
- package/src/superlocalmemory/server/routes/evolution.py +135 -17
- package/src/superlocalmemory/server/routes/helpers.py +16 -1
- package/src/superlocalmemory/server/routes/ingest.py +7 -4
- package/src/superlocalmemory/server/routes/insights.py +3 -3
- package/src/superlocalmemory/server/routes/learning.py +14 -14
- package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
- package/src/superlocalmemory/server/routes/memories.py +182 -57
- package/src/superlocalmemory/server/routes/mesh.py +95 -15
- package/src/superlocalmemory/server/routes/optimize.py +33 -1
- package/src/superlocalmemory/server/routes/prewarm.py +2 -0
- package/src/superlocalmemory/server/routes/profiles.py +63 -17
- package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
- package/src/superlocalmemory/server/routes/rbac.py +367 -0
- package/src/superlocalmemory/server/routes/stats.py +13 -6
- package/src/superlocalmemory/server/routes/tiers.py +11 -9
- package/src/superlocalmemory/server/routes/v3_api.py +183 -69
- package/src/superlocalmemory/server/routes/ws.py +5 -2
- package/src/superlocalmemory/server/security_middleware.py +12 -5
- package/src/superlocalmemory/server/ui.py +20 -5
- package/src/superlocalmemory/server/unified_daemon.py +384 -56
- package/src/superlocalmemory/server/write_identity.py +38 -8
- package/src/superlocalmemory/storage/database.py +265 -53
- package/src/superlocalmemory/storage/migration_runner.py +53 -0
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/storage/schema.py +87 -0
- package/src/superlocalmemory/storage/schema_v343.py +24 -12
- package/src/superlocalmemory/trust/gate.py +49 -8
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
- package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
- package/src/superlocalmemory/ui/css/design-system.css +621 -0
- package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
- package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
- package/src/superlocalmemory/ui/favicon.svg +35 -4
- package/src/superlocalmemory/ui/index.html +306 -173
- package/src/superlocalmemory/ui/js/brain.js +5 -20
- package/src/superlocalmemory/ui/js/core.js +47 -31
- package/src/superlocalmemory/ui/js/dashboard.js +314 -63
- package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
- package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
- package/src/superlocalmemory/ui/js/math-health.js +1 -1
- package/src/superlocalmemory/ui/js/memories.js +15 -4
- package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
- package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
- package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
- package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
- package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
- package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
- package/src/superlocalmemory/ui/js/od-agents.js +544 -0
- package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
- package/src/superlocalmemory/ui/js/od-backup.js +780 -0
- package/src/superlocalmemory/ui/js/od-brain.js +779 -0
- package/src/superlocalmemory/ui/js/od-entities.js +579 -0
- package/src/superlocalmemory/ui/js/od-graph.js +593 -0
- package/src/superlocalmemory/ui/js/od-health.js +539 -0
- package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
- package/src/superlocalmemory/ui/js/od-memories.js +887 -0
- package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
- package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
- package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
- package/src/superlocalmemory/ui/js/od-shell.js +593 -0
- package/src/superlocalmemory/ui/js/od-skills.js +573 -0
- package/src/superlocalmemory/ui/js/od-team.js +258 -0
- package/src/superlocalmemory/ui/js/profiles.js +159 -46
- package/src/superlocalmemory/ui/js/settings.js +2 -2
- package/src/superlocalmemory/ui/js/timeline.js +34 -5
- package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
- package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Community summaries (Wave Q2) — one synthesized report per entity community.
|
|
6
|
+
|
|
7
|
+
Rides on the entity-community backbone (core.entity_community). For each
|
|
8
|
+
community it gathers the member entities' facts, EXCLUDES superseded facts
|
|
9
|
+
(bi-temporal — market CRIT-3), then produces:
|
|
10
|
+
|
|
11
|
+
- a keyword-dense signal (always, Mode A, zero-LLM), and
|
|
12
|
+
- a summary: Mode B/C LLM synthesis via the shared core.Summarizer
|
|
13
|
+
(which itself falls back to a heuristic), else the Mode A keyword-dense
|
|
14
|
+
line. Fail-open — a summarizer error never breaks generation.
|
|
15
|
+
|
|
16
|
+
Surfacing is on-device-safe: summaries are PRECOMPUTED here in the background
|
|
17
|
+
and later matched to a query as a single thematic-context block (Q2b) — never
|
|
18
|
+
a GraphRAG-style per-query LLM fan-out (market CRIT-1). member_fact_ids gives
|
|
19
|
+
drill-down back to the source atoms (Q3).
|
|
20
|
+
|
|
21
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
22
|
+
License: AGPL-3.0-or-later
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import logging
|
|
29
|
+
from collections import Counter, defaultdict
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from superlocalmemory.core.entity_community import EntityCommunityBuilder
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
_STOPWORDS = frozenset({
|
|
37
|
+
"the", "a", "an", "is", "was", "were", "are", "be", "been", "being",
|
|
38
|
+
"have", "has", "had", "do", "does", "did", "will", "would", "could",
|
|
39
|
+
"should", "may", "might", "shall", "can", "to", "of", "in", "for", "on",
|
|
40
|
+
"with", "at", "by", "from", "as", "into", "through", "and", "but", "or",
|
|
41
|
+
"not", "no", "this", "that", "these", "those", "it", "its", "they",
|
|
42
|
+
"them", "their", "he", "she", "his", "her", "we", "our", "you", "your",
|
|
43
|
+
"i", "my", "me", "his", "was", "who", "what", "when", "where", "how",
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CommunitySummaryBuilder:
|
|
48
|
+
"""Generate + persist one summary per entity community (background)."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
db: Any,
|
|
53
|
+
summarizer: Any = None,
|
|
54
|
+
max_communities: int = 50,
|
|
55
|
+
min_facts: int = 2,
|
|
56
|
+
max_facts_per_community: int = 30,
|
|
57
|
+
max_keywords: int = 8,
|
|
58
|
+
summary_max_chars: int = 512,
|
|
59
|
+
) -> None:
|
|
60
|
+
self._db = db
|
|
61
|
+
self._summarizer = summarizer
|
|
62
|
+
self._max_communities = max(1, int(max_communities))
|
|
63
|
+
self._min_facts = max(1, int(min_facts))
|
|
64
|
+
self._max_facts = max(1, int(max_facts_per_community))
|
|
65
|
+
self._max_keywords = max(1, int(max_keywords))
|
|
66
|
+
self._summary_max_chars = max(64, int(summary_max_chars))
|
|
67
|
+
|
|
68
|
+
# ------------------------------------------------------------------
|
|
69
|
+
# Generation
|
|
70
|
+
# ------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
def compute_and_store(self, profile_id: str) -> dict[str, int]:
|
|
73
|
+
communities = EntityCommunityBuilder(self._db).get_communities(profile_id)
|
|
74
|
+
try:
|
|
75
|
+
self._db.execute(
|
|
76
|
+
"DELETE FROM community_summaries WHERE profile_id = ?",
|
|
77
|
+
(profile_id,),
|
|
78
|
+
)
|
|
79
|
+
except Exception as exc:
|
|
80
|
+
logger.debug("community_summaries clear failed: %s", exc)
|
|
81
|
+
if not communities:
|
|
82
|
+
return {"summaries_written": 0, "communities": 0}
|
|
83
|
+
|
|
84
|
+
entity_to_cid: dict[str, int] = {
|
|
85
|
+
e: cid for cid, ents in communities.items() for e in ents
|
|
86
|
+
}
|
|
87
|
+
cid_facts = self._gather_facts(profile_id, entity_to_cid)
|
|
88
|
+
name_map = self._entity_names(profile_id, communities)
|
|
89
|
+
|
|
90
|
+
written = 0
|
|
91
|
+
ordered = sorted(
|
|
92
|
+
cid_facts.items(), key=lambda kv: len(kv[1]), reverse=True,
|
|
93
|
+
)
|
|
94
|
+
for cid, facts in ordered:
|
|
95
|
+
if written >= self._max_communities:
|
|
96
|
+
break
|
|
97
|
+
seen: set[str] = set()
|
|
98
|
+
vf: list[tuple[str, str]] = []
|
|
99
|
+
for fid, content in facts:
|
|
100
|
+
if fid in seen:
|
|
101
|
+
continue
|
|
102
|
+
seen.add(fid)
|
|
103
|
+
vf.append((fid, content))
|
|
104
|
+
if len(vf) < self._min_facts:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
fact_ids = [fid for fid, _ in vf]
|
|
108
|
+
contents = [c for _, c in vf][: self._max_facts]
|
|
109
|
+
entity_ids = list(communities.get(cid, []))
|
|
110
|
+
entity_names = [name_map.get(e, e) for e in entity_ids]
|
|
111
|
+
keywords = self._keywords(contents)
|
|
112
|
+
summary = self._summary(contents, entity_names, keywords)
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
self._db.execute(
|
|
116
|
+
"INSERT OR REPLACE INTO community_summaries "
|
|
117
|
+
"(profile_id, community_id, summary, keywords, "
|
|
118
|
+
" entity_ids_json, fact_ids_json, fact_count, computed_at) "
|
|
119
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))",
|
|
120
|
+
(
|
|
121
|
+
profile_id, cid, summary, keywords,
|
|
122
|
+
json.dumps(entity_ids), json.dumps(fact_ids),
|
|
123
|
+
len(fact_ids),
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
written += 1
|
|
127
|
+
except Exception as exc:
|
|
128
|
+
logger.debug("community_summaries write failed (%s): %s", cid, exc)
|
|
129
|
+
|
|
130
|
+
return {"summaries_written": written, "communities": len(communities)}
|
|
131
|
+
|
|
132
|
+
# ------------------------------------------------------------------
|
|
133
|
+
# Read API
|
|
134
|
+
# ------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
def get_summaries(self, profile_id: str) -> list[dict]:
|
|
137
|
+
try:
|
|
138
|
+
rows = self._db.execute(
|
|
139
|
+
"SELECT * FROM community_summaries WHERE profile_id = ? "
|
|
140
|
+
"ORDER BY fact_count DESC",
|
|
141
|
+
(profile_id,),
|
|
142
|
+
)
|
|
143
|
+
except Exception as exc:
|
|
144
|
+
logger.debug("get_summaries failed: %s", exc)
|
|
145
|
+
return []
|
|
146
|
+
return [dict(r) for r in rows]
|
|
147
|
+
|
|
148
|
+
def get_summary(self, profile_id: str, community_id: int) -> dict | None:
|
|
149
|
+
try:
|
|
150
|
+
rows = self._db.execute(
|
|
151
|
+
"SELECT * FROM community_summaries "
|
|
152
|
+
"WHERE profile_id = ? AND community_id = ?",
|
|
153
|
+
(profile_id, int(community_id)),
|
|
154
|
+
)
|
|
155
|
+
except Exception as exc:
|
|
156
|
+
logger.debug("get_summary failed: %s", exc)
|
|
157
|
+
return None
|
|
158
|
+
return dict(rows[0]) if rows else None
|
|
159
|
+
|
|
160
|
+
# ------------------------------------------------------------------
|
|
161
|
+
# Internal
|
|
162
|
+
# ------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
def _gather_facts(
|
|
165
|
+
self, profile_id: str, entity_to_cid: dict[str, int],
|
|
166
|
+
) -> dict[int, list[tuple[str, str]]]:
|
|
167
|
+
"""One scan → {community_id -> [(fact_id, content)]}, superseded dropped."""
|
|
168
|
+
try:
|
|
169
|
+
rows = self._db.execute(
|
|
170
|
+
"SELECT fact_id, canonical_entities_json, content "
|
|
171
|
+
"FROM atomic_facts WHERE profile_id = ?",
|
|
172
|
+
(profile_id,),
|
|
173
|
+
)
|
|
174
|
+
except Exception as exc:
|
|
175
|
+
logger.debug("community fact scan failed: %s", exc)
|
|
176
|
+
return {}
|
|
177
|
+
|
|
178
|
+
cid_facts: dict[int, list[tuple[str, str]]] = defaultdict(list)
|
|
179
|
+
for row in rows:
|
|
180
|
+
d = dict(row)
|
|
181
|
+
raw = d.get("canonical_entities_json")
|
|
182
|
+
if not raw:
|
|
183
|
+
continue
|
|
184
|
+
try:
|
|
185
|
+
ents = json.loads(raw)
|
|
186
|
+
except (ValueError, TypeError):
|
|
187
|
+
continue
|
|
188
|
+
if not isinstance(ents, list):
|
|
189
|
+
continue
|
|
190
|
+
cids = {
|
|
191
|
+
entity_to_cid[str(e).strip()]
|
|
192
|
+
for e in ents
|
|
193
|
+
if str(e).strip() in entity_to_cid
|
|
194
|
+
}
|
|
195
|
+
if not cids:
|
|
196
|
+
continue
|
|
197
|
+
fid = str(d["fact_id"])
|
|
198
|
+
content = d.get("content") or ""
|
|
199
|
+
for cid in cids:
|
|
200
|
+
cid_facts[cid].append((fid, content))
|
|
201
|
+
|
|
202
|
+
all_fids = list({fid for lst in cid_facts.values() for fid, _ in lst})
|
|
203
|
+
invalid: set[str] = set()
|
|
204
|
+
if all_fids:
|
|
205
|
+
try:
|
|
206
|
+
invalid = self._db.get_invalidated_fact_ids(all_fids, profile_id)
|
|
207
|
+
except Exception as exc:
|
|
208
|
+
logger.debug("invalidated-fact lookup failed: %s", exc)
|
|
209
|
+
if invalid:
|
|
210
|
+
cid_facts = {
|
|
211
|
+
cid: [(fid, c) for fid, c in lst if fid not in invalid]
|
|
212
|
+
for cid, lst in cid_facts.items()
|
|
213
|
+
}
|
|
214
|
+
return cid_facts
|
|
215
|
+
|
|
216
|
+
def _entity_names(
|
|
217
|
+
self, profile_id: str, communities: dict[int, list[str]],
|
|
218
|
+
) -> dict[str, str]:
|
|
219
|
+
all_eids = list({e for ents in communities.values() for e in ents})
|
|
220
|
+
name_map: dict[str, str] = {}
|
|
221
|
+
chunk = 900
|
|
222
|
+
for start in range(0, len(all_eids), chunk):
|
|
223
|
+
batch = all_eids[start:start + chunk]
|
|
224
|
+
ph = ",".join("?" for _ in batch)
|
|
225
|
+
try:
|
|
226
|
+
rows = self._db.execute(
|
|
227
|
+
"SELECT entity_id, canonical_name FROM canonical_entities "
|
|
228
|
+
f"WHERE profile_id = ? AND entity_id IN ({ph})",
|
|
229
|
+
(profile_id, *batch),
|
|
230
|
+
)
|
|
231
|
+
except Exception as exc:
|
|
232
|
+
logger.debug("entity-name lookup failed: %s", exc)
|
|
233
|
+
continue
|
|
234
|
+
for r in rows:
|
|
235
|
+
d = dict(r)
|
|
236
|
+
name_map[str(d["entity_id"])] = str(d.get("canonical_name") or "")
|
|
237
|
+
return name_map
|
|
238
|
+
|
|
239
|
+
def _keywords(self, contents: list[str]) -> str:
|
|
240
|
+
tokens: list[str] = []
|
|
241
|
+
for text in contents:
|
|
242
|
+
for word in text.lower().split():
|
|
243
|
+
w = word.strip(".,;:!?\"'()[]{}")
|
|
244
|
+
if len(w) > 2 and w not in _STOPWORDS:
|
|
245
|
+
tokens.append(w)
|
|
246
|
+
top = [w for w, _ in Counter(tokens).most_common(self._max_keywords)]
|
|
247
|
+
return ", ".join(top)
|
|
248
|
+
|
|
249
|
+
def _summary(
|
|
250
|
+
self, contents: list[str], entity_names: list[str], keywords: str,
|
|
251
|
+
) -> str:
|
|
252
|
+
if self._summarizer is not None:
|
|
253
|
+
try:
|
|
254
|
+
text = self._summarizer.summarize_cluster(
|
|
255
|
+
[{"content": c} for c in contents],
|
|
256
|
+
)
|
|
257
|
+
if text and text.strip():
|
|
258
|
+
return text.strip()[: self._summary_max_chars]
|
|
259
|
+
except Exception as exc:
|
|
260
|
+
logger.debug("community summarizer failed (fail-open): %s", exc)
|
|
261
|
+
# Mode A keyword-dense fallback.
|
|
262
|
+
names = [n for n in entity_names if n]
|
|
263
|
+
topic = ", ".join(names[:6]) if names else "related memories"
|
|
264
|
+
base = f"Topics: {topic}."
|
|
265
|
+
if keywords:
|
|
266
|
+
base += f" Key terms: {keywords}."
|
|
267
|
+
return base[: self._summary_max_chars]
|
|
@@ -236,6 +236,11 @@ class EncodingConfig:
|
|
|
236
236
|
# Entropy gate
|
|
237
237
|
entropy_threshold: float = 0.95
|
|
238
238
|
|
|
239
|
+
# Entity reflexion (Wave Q1) — Mode B/C self-review of extracted entities.
|
|
240
|
+
# Fail-open; one extra bounded LLM call per chunk. Mode A is unaffected.
|
|
241
|
+
enable_entity_reflexion: bool = True
|
|
242
|
+
reflexion_max_facts: int = 8
|
|
243
|
+
|
|
239
244
|
|
|
240
245
|
# ---------------------------------------------------------------------------
|
|
241
246
|
# Retrieval Config
|
|
@@ -297,6 +302,11 @@ class RetrievalConfig:
|
|
|
297
302
|
recall_per_fact_max_chars: int = 2400 # ~600 tokens; head 70% + tail 30%
|
|
298
303
|
recall_total_max_chars: int = 12000 # ~3K tokens; stubs beyond this
|
|
299
304
|
|
|
305
|
+
# Wave Q2b: attach a precomputed community summary as thematic context when
|
|
306
|
+
# the top results cluster in one community. Read-only lookup, gated, and
|
|
307
|
+
# fail-open — never a per-query LLM call. Kill-switch for tuning.
|
|
308
|
+
enable_community_context: bool = True
|
|
309
|
+
|
|
300
310
|
|
|
301
311
|
# ---------------------------------------------------------------------------
|
|
302
312
|
# Math Config
|
|
@@ -659,6 +669,14 @@ class TemporalValidatorConfig:
|
|
|
659
669
|
# Sheaf contradiction threshold
|
|
660
670
|
contradiction_threshold: float = 0.45 # Mode A threshold (768d)
|
|
661
671
|
|
|
672
|
+
# P5-INT-01: superseded facts are DEMOTED in recall, not hidden. A fact
|
|
673
|
+
# marked system_expired_at keeps its channel evidence but its per-channel
|
|
674
|
+
# score is multiplied by this factor, so current facts rank above it while
|
|
675
|
+
# nothing valid silently vanishes (Mem0-2026 non-destructive design;
|
|
676
|
+
# retrieval-time recency resolves conflicts). 0.0 restores the old hide
|
|
677
|
+
# behaviour; 1.0 disables demotion.
|
|
678
|
+
superseded_demotion_factor: float = 0.25
|
|
679
|
+
|
|
662
680
|
# LLM pre-filter threshold (lower to catch more candidates)
|
|
663
681
|
llm_prefilter_threshold: float = 0.30
|
|
664
682
|
|
|
@@ -677,18 +695,31 @@ class EvolutionConfig:
|
|
|
677
695
|
"""Configuration for Skill Evolution Engine (v3.4.10).
|
|
678
696
|
|
|
679
697
|
OFF by default — opt in via `slm setup` (interactive) or
|
|
680
|
-
`slm config set evolution.enabled true` (CLI).
|
|
698
|
+
`slm config set evolution.enabled true` (CLI). Enabling makes
|
|
699
|
+
background LLM calls; the enable flow surfaces a cost advisory.
|
|
681
700
|
|
|
682
701
|
Backend auto-detection priority:
|
|
683
702
|
1. `claude` CLI available → spawn `claude --model haiku` (ECC pattern, free)
|
|
684
703
|
2. Ollama running → use Ollama (free, local)
|
|
685
704
|
3. API key set → use Anthropic/OpenAI API (paid)
|
|
686
705
|
4. Nothing → dashboard-only (show candidates, manual evolution)
|
|
706
|
+
|
|
707
|
+
Model selection (v3.7.9): each pipeline step is configurable and
|
|
708
|
+
defaults to the lowest-cost capable model for the active backend
|
|
709
|
+
(Claude→haiku, Ollama→local). Leave a field empty ("") for "auto".
|
|
710
|
+
The blind verifier is kept on a *different* model from the generator
|
|
711
|
+
so it can't grade its own homework; see
|
|
712
|
+
``evolution.model_selection.resolve_evolution_models``.
|
|
687
713
|
"""
|
|
688
714
|
|
|
689
715
|
enabled: bool = False # OFF by default, opt-in
|
|
690
716
|
backend: str = "auto" # auto, claude, ollama, anthropic, openai
|
|
691
717
|
max_evolutions_per_cycle: int = 3 # Budget cap per consolidation
|
|
718
|
+
# Empty string == "auto" (resolve cheapest capable model at runtime).
|
|
719
|
+
# Accepts short aliases ("haiku"/"sonnet") or allow-listed model ids.
|
|
720
|
+
mutation_model: str = "" # generator (quality-sensitive)
|
|
721
|
+
verify_model: str = "" # blind verifier (must differ from generator)
|
|
722
|
+
confirm_model: str = "" # cheap yes/no gate
|
|
692
723
|
|
|
693
724
|
|
|
694
725
|
@dataclass(frozen=True)
|
|
@@ -743,6 +774,152 @@ class AutoInvokeConfig:
|
|
|
743
774
|
relevance_threshold: float = 0.3 # Legacy compat with AutoRecall
|
|
744
775
|
|
|
745
776
|
|
|
777
|
+
# ---------------------------------------------------------------------------
|
|
778
|
+
# Deployment Config (v3.8.0)
|
|
779
|
+
# ---------------------------------------------------------------------------
|
|
780
|
+
|
|
781
|
+
_VALID_DEPLOYMENT_MODES = ("personal", "enterprise")
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
@dataclass(frozen=True)
|
|
785
|
+
class DeploymentConfig:
|
|
786
|
+
"""Deployment mode configuration.
|
|
787
|
+
|
|
788
|
+
Personal (default, safe defaults):
|
|
789
|
+
Single-user install. No login required, no PII redaction, no
|
|
790
|
+
retention scheduler. Behaviour is identical to pre-3.8.0 installs.
|
|
791
|
+
|
|
792
|
+
Enterprise:
|
|
793
|
+
Multi-user / team / company install. Login required, PII redacted,
|
|
794
|
+
retention scheduler active, audit enabled.
|
|
795
|
+
|
|
796
|
+
SAFE DEFAULTS = Personal — an existing config.toml with no [deployment]
|
|
797
|
+
section continues to behave exactly as before. Adding a [deployment]
|
|
798
|
+
section is opt-in; removing it reverts to Personal automatically.
|
|
799
|
+
"""
|
|
800
|
+
|
|
801
|
+
mode: str = "personal" # "personal" | "enterprise"
|
|
802
|
+
require_login: bool = False
|
|
803
|
+
pii_redaction: bool = False
|
|
804
|
+
retention_enabled: bool = False
|
|
805
|
+
audit: bool = True
|
|
806
|
+
|
|
807
|
+
def __post_init__(self) -> None:
|
|
808
|
+
if self.mode not in _VALID_DEPLOYMENT_MODES:
|
|
809
|
+
raise ValueError(
|
|
810
|
+
f"DeploymentConfig.mode must be one of {_VALID_DEPLOYMENT_MODES!r}, "
|
|
811
|
+
f"got {self.mode!r}"
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
@property
|
|
815
|
+
def is_personal(self) -> bool:
|
|
816
|
+
"""True when operating in personal (single-user) mode."""
|
|
817
|
+
return self.mode == "personal"
|
|
818
|
+
|
|
819
|
+
@property
|
|
820
|
+
def is_enterprise(self) -> bool:
|
|
821
|
+
"""True when operating in enterprise (team/company) mode."""
|
|
822
|
+
return self.mode == "enterprise"
|
|
823
|
+
|
|
824
|
+
def as_dict(self) -> dict:
|
|
825
|
+
return {
|
|
826
|
+
"mode": self.mode,
|
|
827
|
+
"require_login": self.require_login,
|
|
828
|
+
"pii_redaction": self.pii_redaction,
|
|
829
|
+
"retention_enabled": self.retention_enabled,
|
|
830
|
+
"audit": self.audit,
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
#: Canonical Personal preset — all permissive defaults.
|
|
835
|
+
DEPLOYMENT_PERSONAL = DeploymentConfig(
|
|
836
|
+
mode="personal",
|
|
837
|
+
require_login=False,
|
|
838
|
+
pii_redaction=False,
|
|
839
|
+
retention_enabled=False,
|
|
840
|
+
audit=True,
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
#: Canonical Enterprise preset — all enforcement defaults.
|
|
844
|
+
DEPLOYMENT_ENTERPRISE = DeploymentConfig(
|
|
845
|
+
mode="enterprise",
|
|
846
|
+
require_login=True,
|
|
847
|
+
pii_redaction=True,
|
|
848
|
+
retention_enabled=True,
|
|
849
|
+
audit=True,
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
def load_deployment_config(
|
|
854
|
+
config_toml_path: Path | None = None,
|
|
855
|
+
) -> DeploymentConfig:
|
|
856
|
+
"""Parse [deployment] from config.toml; return Personal defaults if absent.
|
|
857
|
+
|
|
858
|
+
config.toml is the installer-written performance config (separate from the
|
|
859
|
+
daemon's config.json managed by SLMConfig). This function reads ONLY the
|
|
860
|
+
[deployment] section. All other sections are ignored.
|
|
861
|
+
|
|
862
|
+
Args:
|
|
863
|
+
config_toml_path: Explicit path to config.toml. When None, resolved to
|
|
864
|
+
``~/.superlocalmemory/config.toml`` via the canonical data root.
|
|
865
|
+
|
|
866
|
+
Returns:
|
|
867
|
+
DeploymentConfig — Personal preset when the file is absent, the section
|
|
868
|
+
is missing, or any parse error occurs (fail-open, non-destructive).
|
|
869
|
+
"""
|
|
870
|
+
if config_toml_path is None:
|
|
871
|
+
try:
|
|
872
|
+
config_toml_path = _runtime_base_dir() / "config.toml"
|
|
873
|
+
except Exception:
|
|
874
|
+
return DEPLOYMENT_PERSONAL
|
|
875
|
+
|
|
876
|
+
if not config_toml_path.exists():
|
|
877
|
+
return DEPLOYMENT_PERSONAL
|
|
878
|
+
|
|
879
|
+
try:
|
|
880
|
+
import tomllib as _tomllib
|
|
881
|
+
raw = config_toml_path.read_text(encoding="utf-8")
|
|
882
|
+
data = _tomllib.loads(raw)
|
|
883
|
+
except Exception as exc:
|
|
884
|
+
logger.warning(
|
|
885
|
+
"load_deployment_config: failed to parse %s: %s", config_toml_path, exc
|
|
886
|
+
)
|
|
887
|
+
return DEPLOYMENT_PERSONAL
|
|
888
|
+
|
|
889
|
+
dep = data.get("deployment", {})
|
|
890
|
+
if not dep:
|
|
891
|
+
# No [deployment] section — personal defaults, no behaviour change.
|
|
892
|
+
return DEPLOYMENT_PERSONAL
|
|
893
|
+
|
|
894
|
+
raw_mode = str(dep.get("mode", "personal")).lower()
|
|
895
|
+
if raw_mode not in _VALID_DEPLOYMENT_MODES:
|
|
896
|
+
logger.warning(
|
|
897
|
+
"load_deployment_config: unknown mode %r in %s — defaulting to personal",
|
|
898
|
+
raw_mode, config_toml_path,
|
|
899
|
+
)
|
|
900
|
+
raw_mode = "personal"
|
|
901
|
+
|
|
902
|
+
# Use the preset for the mode as the base so omitted keys inherit
|
|
903
|
+
# sensible values (enterprise → require_login=True etc.).
|
|
904
|
+
base = DEPLOYMENT_ENTERPRISE if raw_mode == "enterprise" else DEPLOYMENT_PERSONAL
|
|
905
|
+
|
|
906
|
+
try:
|
|
907
|
+
return DeploymentConfig(
|
|
908
|
+
mode=raw_mode,
|
|
909
|
+
require_login=bool(dep.get("require_login", base.require_login)),
|
|
910
|
+
pii_redaction=bool(dep.get("pii_redaction", base.pii_redaction)),
|
|
911
|
+
retention_enabled=bool(dep.get("retention_enabled", base.retention_enabled)),
|
|
912
|
+
audit=bool(dep.get("audit", base.audit)),
|
|
913
|
+
)
|
|
914
|
+
except (ValueError, TypeError) as exc:
|
|
915
|
+
logger.warning(
|
|
916
|
+
"load_deployment_config: invalid values in [deployment] in %s: %s — "
|
|
917
|
+
"falling back to personal",
|
|
918
|
+
config_toml_path, exc,
|
|
919
|
+
)
|
|
920
|
+
return DEPLOYMENT_PERSONAL
|
|
921
|
+
|
|
922
|
+
|
|
746
923
|
# ---------------------------------------------------------------------------
|
|
747
924
|
# Health Config (v3.6.9 BUG-A)
|
|
748
925
|
# ---------------------------------------------------------------------------
|
|
@@ -849,6 +1026,35 @@ class SLMConfig:
|
|
|
849
1026
|
path = config_path or (_runtime_base / "config.json")
|
|
850
1027
|
if not path.exists():
|
|
851
1028
|
return cls.for_mode(Mode.A, base_dir=_runtime_base)
|
|
1029
|
+
|
|
1030
|
+
# DASH-V4 (3.7.9): the ``current_mode`` file is the single source of
|
|
1031
|
+
# truth for the ACTIVE mode. If config.json drifted from it (a writer
|
|
1032
|
+
# bypassed switch_mode and reset the active config), load the
|
|
1033
|
+
# authoritative per-mode file so daemon/CLI/dashboard all run the user's
|
|
1034
|
+
# chosen mode — with its settings — instead of a stale one. Scoped to
|
|
1035
|
+
# config.json so explicit per-mode loads (switch_mode) are untouched.
|
|
1036
|
+
# Fail-open: any error falls through to the normal load below.
|
|
1037
|
+
if path.name == "config.json":
|
|
1038
|
+
try:
|
|
1039
|
+
import json as _json
|
|
1040
|
+
_disk_mode = str(
|
|
1041
|
+
_json.loads(path.read_text()).get("mode", "")
|
|
1042
|
+
).lower()
|
|
1043
|
+
_active_mode = cls.read_current_mode(path.parent)
|
|
1044
|
+
if _disk_mode and _active_mode and _disk_mode != _active_mode:
|
|
1045
|
+
_mode_path = cls._mode_config_path(
|
|
1046
|
+
path.parent, Mode(_active_mode)
|
|
1047
|
+
)
|
|
1048
|
+
if _mode_path.exists():
|
|
1049
|
+
logger.warning(
|
|
1050
|
+
"config.json mode=%s disagrees with current_mode=%s;"
|
|
1051
|
+
" loading authoritative %s",
|
|
1052
|
+
_disk_mode, _active_mode, _mode_path.name,
|
|
1053
|
+
)
|
|
1054
|
+
return cls.load(_mode_path)
|
|
1055
|
+
except Exception:
|
|
1056
|
+
pass # fail-open — fall through to the normal load below
|
|
1057
|
+
|
|
852
1058
|
import json
|
|
853
1059
|
try:
|
|
854
1060
|
data = json.loads(path.read_text())
|
|
@@ -1070,6 +1276,9 @@ class SLMConfig:
|
|
|
1070
1276
|
"enabled": self.evolution.enabled,
|
|
1071
1277
|
"backend": self.evolution.backend,
|
|
1072
1278
|
"max_evolutions_per_cycle": self.evolution.max_evolutions_per_cycle,
|
|
1279
|
+
"mutation_model": self.evolution.mutation_model,
|
|
1280
|
+
"verify_model": self.evolution.verify_model,
|
|
1281
|
+
"confirm_model": self.evolution.confirm_model,
|
|
1073
1282
|
}
|
|
1074
1283
|
|
|
1075
1284
|
# V3.4.65: Persist injection config
|
|
@@ -1264,6 +1473,7 @@ class SLMConfig:
|
|
|
1264
1473
|
_c_emb = EmbeddingConfig(
|
|
1265
1474
|
model_name=embedding_model_name or "text-embedding-3-large",
|
|
1266
1475
|
dimension=embedding_dimension or 3072,
|
|
1476
|
+
provider=_c_emb_provider,
|
|
1267
1477
|
api_endpoint=embedding_endpoint,
|
|
1268
1478
|
api_key=embedding_key,
|
|
1269
1479
|
deployment_name=embedding_deployment,
|
|
@@ -1275,8 +1485,11 @@ class SLMConfig:
|
|
|
1275
1485
|
# 768-dim Ollama embedder, which made ingestion fail at vector
|
|
1276
1486
|
# materialization. Cloud embeddings remain an explicit opt-in.
|
|
1277
1487
|
_c_emb = EmbeddingConfig(
|
|
1278
|
-
|
|
1279
|
-
|
|
1488
|
+
# Honour an on-disk embedding model when one was configured (the
|
|
1489
|
+
# load() path passes it through); default to the local nomic
|
|
1490
|
+
# model so Mode C never silently requires a paid cloud embedder.
|
|
1491
|
+
model_name=embedding_model_name or "nomic-ai/nomic-embed-text-v1.5",
|
|
1492
|
+
dimension=embedding_dimension or 768,
|
|
1280
1493
|
)
|
|
1281
1494
|
return cls(
|
|
1282
1495
|
mode=mode,
|