superlocalmemory 3.8.0 → 3.8.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/CHANGELOG.md +112 -0
- package/README.md +32 -120
- package/package.json +9 -2
- package/plugin/.claude-plugin/plugin.json +1 -2
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +2 -2
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +3 -5
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +2 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +3 -5
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +2 -1
- package/scripts/postinstall.js +7 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +494 -9
- package/src/superlocalmemory/cli/daemon.py +7 -0
- package/src/superlocalmemory/cli/loop_cmd.py +2 -7
- package/src/superlocalmemory/cli/main.py +72 -7
- package/src/superlocalmemory/cli/setup_wizard.py +142 -16
- package/src/superlocalmemory/cli/version_banner.py +17 -3
- package/src/superlocalmemory/core/backend_orchestrator.py +18 -16
- package/src/superlocalmemory/core/component_healer.py +144 -0
- package/src/superlocalmemory/core/component_registry.py +487 -0
- package/src/superlocalmemory/core/config.py +21 -0
- package/src/superlocalmemory/core/embedding_worker.py +4 -5
- package/src/superlocalmemory/core/embeddings.py +132 -45
- package/src/superlocalmemory/core/engine.py +29 -22
- package/src/superlocalmemory/core/engine_ingestion.py +332 -45
- package/src/superlocalmemory/core/ingestion_command.py +154 -25
- package/src/superlocalmemory/core/injection.py +12 -7
- package/src/superlocalmemory/core/maintenance.py +43 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +44 -6
- package/src/superlocalmemory/core/recall_pipeline.py +42 -4
- package/src/superlocalmemory/core/store_pipeline.py +195 -20
- package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
- package/src/superlocalmemory/hooks/portable_kit.py +34 -2
- package/src/superlocalmemory/learning/model_rollback.py +3 -0
- package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
- package/src/superlocalmemory/learning/reward.py +50 -0
- package/src/superlocalmemory/learning/source_quality.py +523 -1
- package/src/superlocalmemory/loops/ledger.py +25 -5
- package/src/superlocalmemory/mcp/_daemon_proxy.py +6 -2
- package/src/superlocalmemory/mcp/_pool_adapter.py +4 -1
- package/src/superlocalmemory/mcp/server.py +11 -30
- package/src/superlocalmemory/mcp/tools_active.py +1 -1
- package/src/superlocalmemory/mcp/tools_core.py +21 -5
- package/src/superlocalmemory/mcp/tools_learning.py +2 -2
- package/src/superlocalmemory/retrieval/bridge_discovery.py +14 -0
- package/src/superlocalmemory/retrieval/engine.py +53 -21
- package/src/superlocalmemory/retrieval/reranker.py +3 -4
- package/src/superlocalmemory/retrieval/spreading_activation.py +68 -38
- package/src/superlocalmemory/server/config_file.py +90 -0
- package/src/superlocalmemory/server/origin.py +50 -0
- package/src/superlocalmemory/server/routes/backup.py +293 -70
- package/src/superlocalmemory/server/routes/behavioral.py +342 -61
- package/src/superlocalmemory/server/routes/brain.py +57 -16
- package/src/superlocalmemory/server/routes/config_api.py +84 -82
- package/src/superlocalmemory/server/routes/entity.py +100 -23
- package/src/superlocalmemory/server/routes/evolution.py +103 -100
- package/src/superlocalmemory/server/routes/learning.py +286 -105
- package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
- package/src/superlocalmemory/server/routes/memories.py +8 -3
- package/src/superlocalmemory/server/routes/mesh.py +121 -32
- package/src/superlocalmemory/server/routes/ratelimit.py +33 -25
- package/src/superlocalmemory/server/routes/stats.py +93 -155
- package/src/superlocalmemory/server/routes/token.py +3 -13
- package/src/superlocalmemory/server/routes/v3_api.py +184 -20
- package/src/superlocalmemory/server/unified_daemon.py +732 -41
- package/src/superlocalmemory/storage/embedding_migrator.py +235 -0
- package/src/superlocalmemory/storage/migration_runner.py +79 -1
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
- package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
- package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
- package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
- package/src/superlocalmemory/storage/schema.py +49 -1
- package/src/superlocalmemory/storage/schema_v32.py +2 -0
- package/src/superlocalmemory/storage/schema_v347.py +4 -0
- package/src/superlocalmemory/ui/index.html +6 -8
- package/src/superlocalmemory/ui/js/core.js +52 -9
- package/src/superlocalmemory/ui/js/dashboard.js +169 -82
- package/src/superlocalmemory/ui/js/od-backup.js +156 -65
- package/src/superlocalmemory/ui/js/od-brain.js +88 -51
- package/src/superlocalmemory/ui/js/od-components.js +147 -0
- package/src/superlocalmemory/ui/js/od-entities.js +65 -22
- package/src/superlocalmemory/ui/js/od-graph.js +46 -4
- package/src/superlocalmemory/ui/js/od-health.js +18 -0
- package/src/superlocalmemory/ui/js/od-memories.js +84 -5
- package/src/superlocalmemory/ui/js/od-mesh.js +23 -9
- package/src/superlocalmemory/ui/js/od-operations.js +36 -0
- package/src/superlocalmemory/ui/js/od-settings.js +186 -63
- package/src/superlocalmemory/ui/js/od-shell.js +249 -33
- package/src/superlocalmemory/ui/js/od-skills.js +44 -17
- package/src/superlocalmemory/ui/js/settings.js +15 -1
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-governance-advisor.md +0 -80
- package/plugin-src/agents/slm-loop-runner.md +0 -71
- package/plugin-src/agents/slm-memory-advisor.md +0 -49
- package/plugin-src/agents/slm-optimize-advisor.md +0 -44
- package/plugin-src/commands/slm-loop.md +0 -31
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -102
- package/plugin-src/manifest.json +0 -30
- 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 -62
- package/plugin-src/scripts/slm-launch.bat +0 -23
- package/plugin-src/settings.json +0 -25
- package/plugin-src/skills/slm-governance/SKILL.md +0 -248
- package/plugin-src/skills/slm-loop/SKILL.md +0 -99
- package/plugin-src/skills/slm-mesh/SKILL.md +0 -282
- package/plugin-src/skills/slm-profile/SKILL.md +0 -148
- package/plugin-src/skills/slm-scope/SKILL.md +0 -176
|
@@ -16,8 +16,6 @@ import os
|
|
|
16
16
|
import uuid
|
|
17
17
|
from typing import TYPE_CHECKING
|
|
18
18
|
|
|
19
|
-
logger = logging.getLogger(__name__)
|
|
20
|
-
|
|
21
19
|
from superlocalmemory.core.ingestion_command import (
|
|
22
20
|
IngestionCommand,
|
|
23
21
|
IngestionOperation,
|
|
@@ -31,7 +29,11 @@ if TYPE_CHECKING:
|
|
|
31
29
|
from superlocalmemory.storage.models import AtomicFact
|
|
32
30
|
|
|
33
31
|
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
34
35
|
_PREBUILT_FACT_KEY = "_slm_prebuilt_fact_v1"
|
|
36
|
+
_DERIVATION_VERSION = "v3.7-ingestion-1"
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
def _pii_redaction_enabled(engine: "MemoryEngine") -> bool:
|
|
@@ -141,7 +143,14 @@ def canonical_store(
|
|
|
141
143
|
require_complete: bool = True,
|
|
142
144
|
return_receipt: bool = False,
|
|
143
145
|
) -> list[str] | IngestionOperation:
|
|
144
|
-
"""
|
|
146
|
+
"""Submit canonical evidence, optionally waiting for enrichment completion.
|
|
147
|
+
|
|
148
|
+
``require_complete=False`` is the interactive/CQRS path: it commits a
|
|
149
|
+
profile-scoped memory and FTS-queryable fact through M018, then returns the
|
|
150
|
+
durable receipt without invoking any LLM, embedding, or graph work. The
|
|
151
|
+
daemon materializer owns that expensive, retryable enrichment. Explicit
|
|
152
|
+
complete callers retain the historical synchronous contract.
|
|
153
|
+
"""
|
|
145
154
|
import time
|
|
146
155
|
|
|
147
156
|
from superlocalmemory.core.ingestion_command import IngestionRequest, IngestionState
|
|
@@ -185,21 +194,16 @@ def canonical_store(
|
|
|
185
194
|
speaker=speaker,
|
|
186
195
|
role=role,
|
|
187
196
|
))
|
|
197
|
+
if not require_complete:
|
|
198
|
+
record_operation(
|
|
199
|
+
"remember",
|
|
200
|
+
client=trusted_actor_id,
|
|
201
|
+
duration_ms=(time.monotonic() - started) * 1000.0,
|
|
202
|
+
)
|
|
203
|
+
return receipt if return_receipt else list(receipt.fact_ids)
|
|
204
|
+
|
|
188
205
|
result = command.materialize(receipt.operation_id)
|
|
189
206
|
if result.state is not IngestionState.COMPLETE:
|
|
190
|
-
if not require_complete and receipt.fact_ids:
|
|
191
|
-
import logging
|
|
192
|
-
logging.getLogger(__name__).warning(
|
|
193
|
-
"Canonical operation %s remains %s and will retry: %s",
|
|
194
|
-
result.operation_id,
|
|
195
|
-
result.state.value,
|
|
196
|
-
result.last_error,
|
|
197
|
-
)
|
|
198
|
-
record_operation(
|
|
199
|
-
"remember", client=trusted_actor_id,
|
|
200
|
-
duration_ms=(time.monotonic() - started) * 1000.0,
|
|
201
|
-
)
|
|
202
|
-
return list(receipt.fact_ids)
|
|
203
207
|
raise RuntimeError(result.last_error or "canonical materialization failed")
|
|
204
208
|
except Exception as exc:
|
|
205
209
|
record_operation(
|
|
@@ -345,29 +349,138 @@ def build_engine_ingestion_command(engine: MemoryEngine) -> IngestionCommand:
|
|
|
345
349
|
index_external=False,
|
|
346
350
|
)
|
|
347
351
|
|
|
348
|
-
def
|
|
352
|
+
def resume_checkpoint(operation: IngestionOperation) -> MaterializationResult:
|
|
353
|
+
"""Repair only stages whose writes have an idempotent natural key."""
|
|
354
|
+
state = dict(operation.derivation_state)
|
|
355
|
+
if not state.get("pipeline", False):
|
|
356
|
+
return MaterializationResult(
|
|
357
|
+
operation.final_fact_ids, state, operation.last_error,
|
|
358
|
+
)
|
|
349
359
|
facts = engine._db.get_facts_by_ids(
|
|
350
|
-
list(operation.
|
|
351
|
-
|
|
360
|
+
list(operation.final_fact_ids), operation.profile_id,
|
|
361
|
+
)
|
|
362
|
+
if len(facts) != len(operation.final_fact_ids):
|
|
363
|
+
state["relational"] = False
|
|
364
|
+
return MaterializationResult(
|
|
365
|
+
operation.final_fact_ids,
|
|
366
|
+
state,
|
|
367
|
+
"checkpointed relational facts are missing",
|
|
368
|
+
)
|
|
369
|
+
if state.get("provenance") is False and engine._provenance is not None:
|
|
370
|
+
provenance_complete = True
|
|
371
|
+
for fact in facts:
|
|
372
|
+
existing = engine._db.execute(
|
|
373
|
+
"SELECT 1 FROM provenance WHERE fact_id=? AND profile_id=? "
|
|
374
|
+
"AND source_type=? AND source_id=? AND created_by=? LIMIT 1",
|
|
375
|
+
(
|
|
376
|
+
fact.fact_id,
|
|
377
|
+
operation.profile_id,
|
|
378
|
+
operation.source_type,
|
|
379
|
+
operation.operation_id,
|
|
380
|
+
operation.trusted_actor_id,
|
|
381
|
+
),
|
|
382
|
+
)
|
|
383
|
+
if existing:
|
|
384
|
+
continue
|
|
385
|
+
try:
|
|
386
|
+
engine._provenance.record(
|
|
387
|
+
fact_id=fact.fact_id,
|
|
388
|
+
profile_id=operation.profile_id,
|
|
389
|
+
source_type=operation.source_type,
|
|
390
|
+
source_id=operation.operation_id,
|
|
391
|
+
created_by=operation.trusted_actor_id,
|
|
392
|
+
)
|
|
393
|
+
except Exception:
|
|
394
|
+
provenance_complete = False
|
|
395
|
+
state["provenance"] = provenance_complete
|
|
396
|
+
if state.get("post_hooks") is False:
|
|
397
|
+
try:
|
|
398
|
+
engine._hooks.run_post("store", {
|
|
399
|
+
"operation": "store",
|
|
400
|
+
"agent_id": operation.trusted_actor_id,
|
|
401
|
+
"profile_id": operation.profile_id,
|
|
402
|
+
"content_preview": operation.raw_content[:100],
|
|
403
|
+
"ingestion_operation_id": operation.operation_id,
|
|
404
|
+
"fact_ids": list(operation.final_fact_ids),
|
|
405
|
+
"fact_count": len(operation.final_fact_ids),
|
|
406
|
+
})
|
|
407
|
+
except Exception as exc:
|
|
408
|
+
return MaterializationResult(
|
|
409
|
+
operation.final_fact_ids, state, str(exc),
|
|
410
|
+
)
|
|
411
|
+
state["post_hooks"] = True
|
|
412
|
+
incomplete = [name for name, complete in state.items() if not complete]
|
|
413
|
+
return MaterializationResult(
|
|
414
|
+
operation.final_fact_ids,
|
|
415
|
+
state,
|
|
416
|
+
"" if not incomplete else operation.last_error,
|
|
352
417
|
)
|
|
353
|
-
if len(facts) != len(operation.queryable_fact_ids):
|
|
354
|
-
raise ValueError("queryable fact profile mismatch or missing fact")
|
|
355
|
-
memory_ids = {fact.memory_id for fact in facts}
|
|
356
|
-
if len(memory_ids) != 1:
|
|
357
|
-
raise ValueError("queryable facts do not share one source memory")
|
|
358
|
-
memory_id = next(iter(memory_ids))
|
|
359
418
|
|
|
419
|
+
def resume_partial_relational(
|
|
420
|
+
operation: IngestionOperation,
|
|
421
|
+
memory_id: str,
|
|
422
|
+
) -> MaterializationResult:
|
|
423
|
+
"""Finish idempotent relational effects from facts committed before a crash.
|
|
424
|
+
|
|
425
|
+
Extraction and consolidation mutate evidence and access counters, so a
|
|
426
|
+
relational-start checkpoint is an at-most-once boundary for those
|
|
427
|
+
stages. The facts already committed to this operation's dedicated
|
|
428
|
+
memory can safely be promoted again: they use stable fact IDs, graph
|
|
429
|
+
edge logical keys, temporal event IDs, and fact/entity association
|
|
430
|
+
keys. Deliberately omit the other best-effort enrichers here; they
|
|
431
|
+
have no operation-scoped idempotency contract.
|
|
432
|
+
"""
|
|
360
433
|
from superlocalmemory.core.store_pipeline import run_store
|
|
361
434
|
|
|
362
|
-
|
|
435
|
+
# Consolidation may replace a submitted projection with an existing
|
|
436
|
+
# canonical fact from another memory. The operation checkpoint is the
|
|
437
|
+
# authoritative recovery set in that case; falling back to the source
|
|
438
|
+
# memory is only for a fault before the checkpoint captured final IDs.
|
|
439
|
+
if operation.final_fact_ids:
|
|
440
|
+
facts = engine._db.get_facts_by_ids(
|
|
441
|
+
list(operation.final_fact_ids), operation.profile_id,
|
|
442
|
+
)
|
|
443
|
+
if len(facts) != len(operation.final_fact_ids):
|
|
444
|
+
return MaterializationResult(
|
|
445
|
+
operation.final_fact_ids,
|
|
446
|
+
dict(operation.derivation_state),
|
|
447
|
+
"checkpointed relational facts are missing",
|
|
448
|
+
)
|
|
449
|
+
else:
|
|
450
|
+
facts = engine._db.get_facts_by_memory_id(
|
|
451
|
+
memory_id, operation.profile_id,
|
|
452
|
+
)
|
|
453
|
+
fact_ids = tuple(fact.fact_id for fact in facts)
|
|
454
|
+
if not fact_ids:
|
|
455
|
+
return MaterializationResult(
|
|
456
|
+
(),
|
|
457
|
+
dict(operation.derivation_state),
|
|
458
|
+
"no committed facts available for relational recovery",
|
|
459
|
+
)
|
|
363
460
|
|
|
364
|
-
class
|
|
461
|
+
class _CommittedFactsExtractor:
|
|
365
462
|
@staticmethod
|
|
366
463
|
def extract_facts(**_kwargs):
|
|
367
464
|
return []
|
|
368
465
|
|
|
369
466
|
pipeline_state: dict[str, bool] = {}
|
|
370
|
-
|
|
467
|
+
progress: dict[str, object] = {}
|
|
468
|
+
|
|
469
|
+
def checkpoint_materialization(
|
|
470
|
+
_phase: str,
|
|
471
|
+
checkpoint_fact_ids: tuple[str, ...],
|
|
472
|
+
state: dict[str, bool],
|
|
473
|
+
) -> None:
|
|
474
|
+
repository.checkpoint_enriching(
|
|
475
|
+
operation.operation_id,
|
|
476
|
+
final_fact_ids=checkpoint_fact_ids,
|
|
477
|
+
derivation_version=_DERIVATION_VERSION,
|
|
478
|
+
derivation_state=state,
|
|
479
|
+
lease_owner=operation.lease_owner,
|
|
480
|
+
lease_seconds=900.0,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
recovered_ids = run_store(
|
|
371
484
|
operation.raw_content,
|
|
372
485
|
operation.profile_id,
|
|
373
486
|
session_id=operation.session_id,
|
|
@@ -380,42 +493,211 @@ def build_engine_ingestion_command(engine: MemoryEngine) -> IngestionCommand:
|
|
|
380
493
|
config=engine._config,
|
|
381
494
|
db=engine._db,
|
|
382
495
|
embedder=engine._embedder,
|
|
383
|
-
fact_extractor=(
|
|
384
|
-
_QueryableProjectionExtractor() if is_prebuilt else engine._fact_extractor
|
|
385
|
-
),
|
|
496
|
+
fact_extractor=_CommittedFactsExtractor(),
|
|
386
497
|
entity_resolver=engine._entity_resolver,
|
|
387
498
|
temporal_parser=engine._temporal_parser,
|
|
388
|
-
type_router=None
|
|
499
|
+
type_router=None,
|
|
389
500
|
graph_builder=engine._graph_builder,
|
|
390
|
-
consolidator=None
|
|
391
|
-
observation_builder=
|
|
392
|
-
scene_builder=
|
|
501
|
+
consolidator=None,
|
|
502
|
+
observation_builder=None,
|
|
503
|
+
scene_builder=None,
|
|
393
504
|
entropy_gate=engine._entropy_gate,
|
|
394
|
-
# External indexes are projected only after the relational unit of
|
|
395
|
-
# work commits; sqlite-vec uses a separate connection.
|
|
396
505
|
ann_index=None,
|
|
397
|
-
sheaf_checker=
|
|
506
|
+
sheaf_checker=None,
|
|
398
507
|
retrieval_engine=None,
|
|
399
508
|
provenance=engine._provenance,
|
|
400
509
|
hooks=engine._hooks,
|
|
401
510
|
vector_store=None,
|
|
402
|
-
context_generator=
|
|
511
|
+
context_generator=None,
|
|
403
512
|
temporal_validator=engine._temporal_validator,
|
|
404
|
-
auto_linker=
|
|
405
|
-
consolidation_engine=
|
|
513
|
+
auto_linker=None,
|
|
514
|
+
consolidation_engine=None,
|
|
406
515
|
existing_memory_id=memory_id,
|
|
407
|
-
queryable_fact_ids=
|
|
516
|
+
queryable_fact_ids=fact_ids,
|
|
408
517
|
trusted_actor_id=operation.trusted_actor_id,
|
|
409
518
|
pre_authorized=True,
|
|
410
519
|
ingestion_source_type=operation.source_type,
|
|
411
520
|
ingestion_operation_id=operation.operation_id,
|
|
412
521
|
derivation_report=pipeline_state,
|
|
522
|
+
precompleted_derivation_stages=frozenset({
|
|
523
|
+
"extraction", "consolidation",
|
|
524
|
+
}),
|
|
525
|
+
materialization_progress=progress,
|
|
526
|
+
materialization_checkpoint=checkpoint_materialization,
|
|
527
|
+
)
|
|
528
|
+
state = {
|
|
529
|
+
"pipeline_started": True,
|
|
530
|
+
"relational_started": True,
|
|
531
|
+
"pipeline": True,
|
|
532
|
+
"post_hooks": True,
|
|
533
|
+
"relational": True,
|
|
534
|
+
**pipeline_state,
|
|
535
|
+
}
|
|
536
|
+
return MaterializationResult(tuple(recovered_ids), state)
|
|
537
|
+
|
|
538
|
+
def materialize(operation: IngestionOperation) -> MaterializationResult:
|
|
539
|
+
# A checkpointed relational result is an at-most-once boundary. The
|
|
540
|
+
# extraction/consolidation pipeline mutates evidence/access counters,
|
|
541
|
+
# so replaying it is not a valid repair strategy. Idempotent external
|
|
542
|
+
# projections are resumed by IngestionCommand after every relational
|
|
543
|
+
# stage is complete; otherwise the durable failure remains inspectable.
|
|
544
|
+
if operation.final_fact_ids and operation.derivation_state.get("pipeline", False):
|
|
545
|
+
return resume_checkpoint(operation)
|
|
546
|
+
|
|
547
|
+
facts = engine._db.get_facts_by_ids(
|
|
548
|
+
list(operation.queryable_fact_ids),
|
|
549
|
+
operation.profile_id,
|
|
550
|
+
)
|
|
551
|
+
if len(facts) != len(operation.queryable_fact_ids):
|
|
552
|
+
raise ValueError("queryable fact profile mismatch or missing fact")
|
|
553
|
+
memory_ids = {fact.memory_id for fact in facts}
|
|
554
|
+
if len(memory_ids) != 1:
|
|
555
|
+
raise ValueError("queryable facts do not share one source memory")
|
|
556
|
+
memory_id = next(iter(memory_ids))
|
|
557
|
+
|
|
558
|
+
from superlocalmemory.core.store_pipeline import run_store
|
|
559
|
+
|
|
560
|
+
is_prebuilt = isinstance(operation.metadata.get(_PREBUILT_FACT_KEY), dict)
|
|
561
|
+
if (
|
|
562
|
+
operation.derivation_state.get("pipeline_started", False)
|
|
563
|
+
and not operation.derivation_state.get("pipeline", False)
|
|
564
|
+
and operation.derivation_state.get("relational_started", False)
|
|
565
|
+
):
|
|
566
|
+
return resume_partial_relational(operation, memory_id)
|
|
567
|
+
repository.checkpoint_enriching(
|
|
568
|
+
operation.operation_id,
|
|
569
|
+
final_fact_ids=(),
|
|
570
|
+
derivation_version=_DERIVATION_VERSION,
|
|
571
|
+
derivation_state={
|
|
572
|
+
"pipeline_started": True,
|
|
573
|
+
"pipeline": False,
|
|
574
|
+
},
|
|
575
|
+
lease_owner=operation.lease_owner,
|
|
576
|
+
lease_seconds=900.0,
|
|
413
577
|
)
|
|
578
|
+
|
|
579
|
+
class _QueryableProjectionExtractor:
|
|
580
|
+
@staticmethod
|
|
581
|
+
def extract_facts(**_kwargs):
|
|
582
|
+
return []
|
|
583
|
+
|
|
584
|
+
pipeline_state: dict[str, bool] = {}
|
|
585
|
+
progress: dict[str, object] = {}
|
|
586
|
+
|
|
587
|
+
def checkpoint_materialization(
|
|
588
|
+
_phase: str,
|
|
589
|
+
fact_ids: tuple[str, ...],
|
|
590
|
+
state: dict[str, bool],
|
|
591
|
+
) -> None:
|
|
592
|
+
repository.checkpoint_enriching(
|
|
593
|
+
operation.operation_id,
|
|
594
|
+
final_fact_ids=fact_ids,
|
|
595
|
+
derivation_version=_DERIVATION_VERSION,
|
|
596
|
+
derivation_state=state,
|
|
597
|
+
lease_owner=operation.lease_owner,
|
|
598
|
+
lease_seconds=900.0,
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
try:
|
|
602
|
+
fact_ids = run_store(
|
|
603
|
+
operation.raw_content,
|
|
604
|
+
operation.profile_id,
|
|
605
|
+
session_id=operation.session_id,
|
|
606
|
+
session_date=operation.session_date or None,
|
|
607
|
+
speaker=operation.speaker,
|
|
608
|
+
role=operation.role,
|
|
609
|
+
metadata=dict(operation.metadata),
|
|
610
|
+
scope=operation.scope,
|
|
611
|
+
shared_with=list(operation.shared_with) or None,
|
|
612
|
+
config=engine._config,
|
|
613
|
+
db=engine._db,
|
|
614
|
+
embedder=engine._embedder,
|
|
615
|
+
fact_extractor=(
|
|
616
|
+
_QueryableProjectionExtractor()
|
|
617
|
+
if is_prebuilt else engine._fact_extractor
|
|
618
|
+
),
|
|
619
|
+
entity_resolver=engine._entity_resolver,
|
|
620
|
+
temporal_parser=engine._temporal_parser,
|
|
621
|
+
type_router=None if is_prebuilt else engine._type_router,
|
|
622
|
+
graph_builder=engine._graph_builder,
|
|
623
|
+
consolidator=None if is_prebuilt else engine._consolidator,
|
|
624
|
+
observation_builder=engine._observation_builder,
|
|
625
|
+
scene_builder=engine._scene_builder,
|
|
626
|
+
entropy_gate=engine._entropy_gate,
|
|
627
|
+
# External indexes are projected only after the relational unit
|
|
628
|
+
# of work commits; sqlite-vec uses a separate connection.
|
|
629
|
+
ann_index=None,
|
|
630
|
+
sheaf_checker=engine._sheaf_checker,
|
|
631
|
+
retrieval_engine=None,
|
|
632
|
+
provenance=engine._provenance,
|
|
633
|
+
hooks=engine._hooks,
|
|
634
|
+
vector_store=None,
|
|
635
|
+
context_generator=engine._context_generator,
|
|
636
|
+
temporal_validator=engine._temporal_validator,
|
|
637
|
+
auto_linker=engine._auto_linker,
|
|
638
|
+
consolidation_engine=engine._consolidation_engine,
|
|
639
|
+
existing_memory_id=memory_id,
|
|
640
|
+
queryable_fact_ids=operation.queryable_fact_ids,
|
|
641
|
+
trusted_actor_id=operation.trusted_actor_id,
|
|
642
|
+
pre_authorized=True,
|
|
643
|
+
ingestion_source_type=operation.source_type,
|
|
644
|
+
ingestion_operation_id=operation.operation_id,
|
|
645
|
+
derivation_report=pipeline_state,
|
|
646
|
+
precompleted_derivation_stages=(
|
|
647
|
+
frozenset({"extraction", "consolidation"})
|
|
648
|
+
if is_prebuilt else frozenset()
|
|
649
|
+
),
|
|
650
|
+
materialization_progress=progress,
|
|
651
|
+
materialization_checkpoint=checkpoint_materialization,
|
|
652
|
+
)
|
|
653
|
+
except Exception as exc:
|
|
654
|
+
# ``run_store`` checkpoints the completed relational pipeline
|
|
655
|
+
# immediately before post-hooks run. Prefer that durable ledger
|
|
656
|
+
# over rebuilding state from local variables: a one-time hook
|
|
657
|
+
# failure must not make committed extraction/consolidation look
|
|
658
|
+
# incomplete and trigger a destructive pipeline replay on retry.
|
|
659
|
+
checkpoint = repository.get(operation.operation_id)
|
|
660
|
+
partial_ids = tuple(checkpoint.final_fact_ids)
|
|
661
|
+
failed_state = dict(checkpoint.derivation_state)
|
|
662
|
+
if not partial_ids:
|
|
663
|
+
partial_ids = tuple(progress.get("fact_ids") or ())
|
|
664
|
+
if not partial_ids:
|
|
665
|
+
partial_ids = tuple(
|
|
666
|
+
fact.fact_id
|
|
667
|
+
for fact in engine._db.get_facts_by_memory_id(
|
|
668
|
+
memory_id, operation.profile_id
|
|
669
|
+
)
|
|
670
|
+
)
|
|
671
|
+
if not failed_state:
|
|
672
|
+
relational_complete = bool(
|
|
673
|
+
progress.get("relational_complete", False)
|
|
674
|
+
)
|
|
675
|
+
failed_state = {
|
|
676
|
+
**{
|
|
677
|
+
name: bool(pipeline_state.get(name, False))
|
|
678
|
+
for name in (
|
|
679
|
+
"extraction",
|
|
680
|
+
"canonicalization",
|
|
681
|
+
"consolidation",
|
|
682
|
+
"graph",
|
|
683
|
+
"temporal",
|
|
684
|
+
"provenance",
|
|
685
|
+
)
|
|
686
|
+
},
|
|
687
|
+
"pipeline_started": True,
|
|
688
|
+
"relational_started": bool(
|
|
689
|
+
progress.get("relational_started", False)
|
|
690
|
+
),
|
|
691
|
+
"pipeline": relational_complete,
|
|
692
|
+
"post_hooks": False,
|
|
693
|
+
}
|
|
694
|
+
# Reaching this exception handler means hooks did not complete.
|
|
695
|
+
# Keep this false even if a future hook implementation mutates the
|
|
696
|
+
# in-memory progress map before raising.
|
|
697
|
+
failed_state["post_hooks"] = False
|
|
698
|
+
return MaterializationResult(partial_ids, failed_state, str(exc))
|
|
414
699
|
if not fact_ids:
|
|
415
700
|
return MaterializationResult((), {"relational": False})
|
|
416
|
-
if is_prebuilt:
|
|
417
|
-
pipeline_state["extraction"] = True
|
|
418
|
-
pipeline_state["consolidation"] = True
|
|
419
701
|
|
|
420
702
|
placeholders = ",".join("?" for _ in fact_ids)
|
|
421
703
|
relational_count = engine._db.execute(
|
|
@@ -462,6 +744,10 @@ def build_engine_ingestion_command(engine: MemoryEngine) -> IngestionCommand:
|
|
|
462
744
|
)
|
|
463
745
|
|
|
464
746
|
derivation_state = {
|
|
747
|
+
"pipeline_started": True,
|
|
748
|
+
"relational_started": True,
|
|
749
|
+
"pipeline": True,
|
|
750
|
+
"post_hooks": True,
|
|
465
751
|
"relational": int(dict(relational_count[0])["count"]) == len(fact_ids),
|
|
466
752
|
"fts": int(dict(fts_count[0])["count"]) == len(fact_ids),
|
|
467
753
|
"extraction": pipeline_state.get("extraction", False),
|
|
@@ -531,6 +817,7 @@ def build_engine_ingestion_command(engine: MemoryEngine) -> IngestionCommand:
|
|
|
531
817
|
write_queryable=write_queryable,
|
|
532
818
|
materialize=materialize,
|
|
533
819
|
project=project,
|
|
820
|
+
derivation_version=_DERIVATION_VERSION,
|
|
534
821
|
)
|
|
535
822
|
|
|
536
823
|
|