loop-memory 0.4.0__py3-none-any.whl

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.
Files changed (84) hide show
  1. loop_memory/__init__.py +62 -0
  2. loop_memory/backends/__init__.py +13 -0
  3. loop_memory/backends/embedding.py +82 -0
  4. loop_memory/backends/sentence_embedder.py +30 -0
  5. loop_memory/backends/vector_store.py +139 -0
  6. loop_memory/cli/__init__.py +0 -0
  7. loop_memory/cli/_common.py +68 -0
  8. loop_memory/cli/commands/__init__.py +13 -0
  9. loop_memory/cli/commands/cognitive.py +205 -0
  10. loop_memory/cli/commands/diag.py +346 -0
  11. loop_memory/cli/commands/graph.py +21 -0
  12. loop_memory/cli/commands/hooks.py +212 -0
  13. loop_memory/cli/commands/read.py +362 -0
  14. loop_memory/cli/commands/serve.py +147 -0
  15. loop_memory/cli/commands/write.py +138 -0
  16. loop_memory/cli/main.py +115 -0
  17. loop_memory/engine/__init__.py +0 -0
  18. loop_memory/engine/loop.py +247 -0
  19. loop_memory/engine/reflect.py +89 -0
  20. loop_memory/examples/__init__.py +0 -0
  21. loop_memory/examples/demo.py +39 -0
  22. loop_memory/export/__init__.py +39 -0
  23. loop_memory/export/memory_md.py +629 -0
  24. loop_memory/graph/__init__.py +0 -0
  25. loop_memory/graph/build.py +259 -0
  26. loop_memory/graph/extract.py +197 -0
  27. loop_memory/ingest/__init__.py +0 -0
  28. loop_memory/ingest/loader.py +782 -0
  29. loop_memory/ingest/pipeline.py +458 -0
  30. loop_memory/jobs/__init__.py +0 -0
  31. loop_memory/jobs/cognitive.py +353 -0
  32. loop_memory/jobs/compact.py +371 -0
  33. loop_memory/jobs/consolidate.py +95 -0
  34. loop_memory/jobs/contradiction.py +281 -0
  35. loop_memory/jobs/evolution.py +2021 -0
  36. loop_memory/jobs/graph.py +395 -0
  37. loop_memory/jobs/llm_compact_pass.py +24 -0
  38. loop_memory/jobs/llm_consolidate.py +980 -0
  39. loop_memory/jobs/scheduler.py +495 -0
  40. loop_memory/llm/__init__.py +0 -0
  41. loop_memory/llm/base.py +80 -0
  42. loop_memory/llm/openai_adapter.py +31 -0
  43. loop_memory/llm/providers.py +517 -0
  44. loop_memory/mcp/__init__.py +804 -0
  45. loop_memory/memory/__init__.py +0 -0
  46. loop_memory/memory/types.py +199 -0
  47. loop_memory/privacy/__init__.py +22 -0
  48. loop_memory/privacy/private.py +46 -0
  49. loop_memory/privacy/redact.py +188 -0
  50. loop_memory/py.typed +0 -0
  51. loop_memory/sdk.py +875 -0
  52. loop_memory/sdk_extensions.py +384 -0
  53. loop_memory/security/__init__.py +20 -0
  54. loop_memory/security/secrets.py +464 -0
  55. loop_memory/serve/__init__.py +0 -0
  56. loop_memory/serve/app.py +506 -0
  57. loop_memory/serve/handlers.py +316 -0
  58. loop_memory/serve/routes/_shared.py +59 -0
  59. loop_memory/serve/routes/admin.py +970 -0
  60. loop_memory/serve/routes/cognitive.py +64 -0
  61. loop_memory/serve/routes/export.py +65 -0
  62. loop_memory/serve/routes/graph.py +101 -0
  63. loop_memory/serve/routes/insights.py +702 -0
  64. loop_memory/serve/routes/memories.py +435 -0
  65. loop_memory/serve/routes/sessions.py +75 -0
  66. loop_memory/serve/routes/system.py +493 -0
  67. loop_memory/serve/routes/wiki.py +812 -0
  68. loop_memory/serve/static/__init__.py +0 -0
  69. loop_memory/serve/static/index.html +15 -0
  70. loop_memory/serve/watcher.py +451 -0
  71. loop_memory/storage/__init__.py +5 -0
  72. loop_memory/storage/retrieval.py +365 -0
  73. loop_memory/storage/sqlite_store.py +3627 -0
  74. loop_memory/wiki/__init__.py +41 -0
  75. loop_memory/wiki/backfill.py +143 -0
  76. loop_memory/wiki/classifier.py +238 -0
  77. loop_memory/wiki/prompts.py +295 -0
  78. loop_memory/wiki/scope.py +227 -0
  79. loop_memory-0.4.0.dist-info/METADATA +627 -0
  80. loop_memory-0.4.0.dist-info/RECORD +84 -0
  81. loop_memory-0.4.0.dist-info/WHEEL +5 -0
  82. loop_memory-0.4.0.dist-info/entry_points.txt +2 -0
  83. loop_memory-0.4.0.dist-info/licenses/LICENSE +21 -0
  84. loop_memory-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,64 @@
1
+ """Route group: cognitive.
2
+
3
+ v1/cognitive sleep / audit / revert.
4
+
5
+ All routes were extracted from ``serve/app.py`` as part of the O1
6
+ refactor to keep the central ``create_app`` small. Each block lives
7
+ inside ``register(app, store, scheduler=None)`` so closures over the
8
+ three captured variables work unchanged from the original layout.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from typing import Any, Optional
13
+
14
+ import re
15
+ from fastapi import FastAPI, HTTPException
16
+ from fastapi.responses import JSONResponse
17
+
18
+ from ...storage.sqlite_store import MemoryStore
19
+ from ._shared import _memory_to_dict, _export_safe_segment
20
+
21
+
22
+ def register(app: FastAPI, store: MemoryStore, scheduler: Optional[Any] = None) -> None:
23
+ """Mount every route in this bucket onto ``app``.
24
+
25
+ ``store`` and ``scheduler`` are captured in the route closures so
26
+ the function bodies stay byte-identical to the pre-split layout.
27
+ """
28
+ @app.post("/api/v1/cognitive/sleep")
29
+ def v1_cognitive_sleep(body: dict):
30
+ from ...jobs.cognitive import cognitive_sleep
31
+ rpt = cognitive_sleep(
32
+ store,
33
+ apply=bool(body.get("apply", False)),
34
+ stale_days=int(body.get("stale_days", 90)),
35
+ min_score=float(body.get("min_score", 0.2)),
36
+ min_importance=float(body.get("min_importance", 0.3)),
37
+ low_value=float(body.get("low_value", 0.3)),
38
+ merge_threshold=float(body.get("merge_threshold", 0.92)),
39
+ limit=int(body.get("limit", 1000)),
40
+ record_audit=bool(body.get("record_audit", True)),
41
+ )
42
+ return rpt.to_dict()
43
+
44
+
45
+ @app.get("/api/v1/cognitive/audit")
46
+ def v1_cognitive_audit(kind: str | None = None, action: str | None = None,
47
+ limit: int = 200):
48
+ rows = store.list_audit(kind=kind, action=action, limit=limit)
49
+ return {"rows": rows, "count": len(rows)}
50
+
51
+
52
+ @app.post("/api/v1/cognitive/audit/revert")
53
+ def v1_cognitive_audit_revert(body: dict):
54
+ aid = (body.get("id") or "").strip()
55
+ if not aid:
56
+ raise HTTPException(400, "id is required")
57
+ store.record_audit(
58
+ kind="revert", action="reverted",
59
+ target_kind="memory", target_id=aid,
60
+ reason="user marked audit row as reverted",
61
+ )
62
+ return {"ok": True}
63
+
64
+
@@ -0,0 +1,65 @@
1
+ """Route group: export.
2
+
3
+ v1 export + v1 import.
4
+
5
+ All routes were extracted from ``serve/app.py`` as part of the O1
6
+ refactor to keep the central ``create_app`` small. Each block lives
7
+ inside ``register(app, store, scheduler=None)`` so closures over the
8
+ three captured variables work unchanged from the original layout.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from typing import Any, Optional
13
+
14
+ from fastapi import FastAPI, HTTPException
15
+ from fastapi.responses import JSONResponse
16
+
17
+ from ...storage.sqlite_store import MemoryStore
18
+ from ._shared import _memory_to_dict, _export_safe_segment
19
+
20
+
21
+ def register(app: FastAPI, store: MemoryStore, scheduler: Optional[Any] = None) -> None:
22
+ """Mount every route in this bucket onto ``app``.
23
+
24
+ ``store`` and ``scheduler`` are captured in the route closures so
25
+ the function bodies stay byte-identical to the pre-split layout.
26
+ """
27
+ @app.post("/api/v1/export")
28
+ def v1_export(body: dict):
29
+ from ...export import export_bundle
30
+ out_dir = (body.get("out_dir") or "").strip()
31
+ if not out_dir:
32
+ raise HTTPException(400, "out_dir is required")
33
+ try:
34
+ r = export_bundle(
35
+ store, out_dir,
36
+ agent_id=body.get("agent_id") or None,
37
+ user_id=body.get("user_id") or None,
38
+ scope=body.get("scope") or "global",
39
+ min_importance=float(body.get("min_importance") or 0.0),
40
+ )
41
+ except Exception as e:
42
+ raise HTTPException(500, f"export failed: {e}")
43
+ return r.to_dict()
44
+
45
+
46
+ @app.post("/api/v1/import")
47
+ def v1_import(body: dict):
48
+ from ...export import import_bundle
49
+ in_dir = (body.get("in_dir") or "").strip()
50
+ if not in_dir:
51
+ raise HTTPException(400, "in_dir is required")
52
+ try:
53
+ r = import_bundle(
54
+ store, in_dir,
55
+ agent_id=body.get("agent_id") or None,
56
+ user_id=body.get("user_id") or None,
57
+ dry_run=bool(body.get("dry_run", False)),
58
+ )
59
+ except FileNotFoundError as e:
60
+ raise HTTPException(404, str(e))
61
+ except Exception as e:
62
+ raise HTTPException(500, f"import failed: {e}")
63
+ return r.to_dict()
64
+
65
+
@@ -0,0 +1,101 @@
1
+ """Route group: graph.
2
+
3
+ Knowledge graph + v1/graph + v1/fork.
4
+
5
+ All routes were extracted from ``serve/app.py`` as part of the O1
6
+ refactor to keep the central ``create_app`` small. Each block lives
7
+ inside ``register(app, store, scheduler=None)`` so closures over the
8
+ three captured variables work unchanged from the original layout.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from typing import Any, Optional
13
+
14
+ import re
15
+ from fastapi import FastAPI, HTTPException
16
+ from fastapi.responses import JSONResponse
17
+
18
+ from ...storage.sqlite_store import MemoryStore
19
+ from ._shared import _memory_to_dict, _export_safe_segment
20
+
21
+
22
+ def register(app: FastAPI, store: MemoryStore, scheduler: Optional[Any] = None) -> None:
23
+ """Mount every route in this bucket onto ``app``.
24
+
25
+ ``store`` and ``scheduler`` are captured in the route closures so
26
+ the function bodies stay byte-identical to the pre-split layout.
27
+ """
28
+ @app.post("/api/v1/graph/edges")
29
+ def v1_graph_edges(body: dict):
30
+ """Upsert a high-signal semantic relation."""
31
+ from ...jobs.graph import upsert_semantic_edge
32
+ src = (body.get("src") or "").strip()
33
+ dst = (body.get("dst") or "").strip()
34
+ if not src or not dst or src == dst:
35
+ raise HTTPException(400, "src and dst must be distinct non-empty names")
36
+ try:
37
+ weight = float(body.get("weight", 0.5))
38
+ except (TypeError, ValueError):
39
+ raise HTTPException(400, f"weight must be a number, got {body.get('weight')!r}")
40
+ info = upsert_semantic_edge(
41
+ store, src, dst,
42
+ kind=(body.get("kind") or "relates_to").strip() or "relates_to",
43
+ weight=max(0.0, min(1.5, weight)),
44
+ evidence_id=body.get("evidence_id"),
45
+ )
46
+ return info
47
+
48
+
49
+ @app.get("/api/v1/graph/subgraph")
50
+ def v1_graph_subgraph(q: str, max_nodes: int = 32, max_edges: int = 64):
51
+ from ...jobs.graph import subgraph_for
52
+ sg = subgraph_for(store, q, max_nodes=max_nodes, max_edges=max_edges)
53
+ return sg.to_dict()
54
+
55
+
56
+ @app.post("/api/v1/graph/rebuild")
57
+ def v1_graph_rebuild():
58
+ from ...graph.build import KnowledgeGraph
59
+ KnowledgeGraph(store).rebuild(clear=True)
60
+ n = store.rebuild_entity_mentions()
61
+ return {"entity_mentions": n}
62
+
63
+
64
+ @app.post("/api/v1/fork")
65
+ def v1_fork(body: dict):
66
+ from ...export import fork_snapshot
67
+ return fork_snapshot(store, branch_tag=(body.get("branch_tag") or None))
68
+
69
+
70
+ @app.get("/api/graph")
71
+ def graph(limit_entities: int = 200, limit_relations: int = 1000):
72
+ ents = store.list_entities(limit=limit_entities)
73
+ rels = store.list_relations(limit=limit_relations)
74
+ return {
75
+ "entities": [
76
+ {
77
+ "id": e.id, "name": e.name, "kind": e.kind,
78
+ "mention_count": e.mention_count, "weight": round(e.weight, 3),
79
+ }
80
+ for e in ents
81
+ ],
82
+ "node_kinds": sorted({e.kind for e in ents}),
83
+ "relations": [
84
+ {
85
+ "id": r.id, "src": r.src, "dst": r.dst,
86
+ "kind": r.kind, "weight": round(r.weight, 3),
87
+ "evidence": r.evidence_ids[:5],
88
+ }
89
+ for r in rels
90
+ ],
91
+ "stats": store.graph_stats(),
92
+ }
93
+
94
+
95
+ @app.get("/api/graph/entity/{name}/memories")
96
+ def graph_entity_memories(name: str, limit: int = 30):
97
+ # match by LIKE on the entity name appearing in memory text
98
+ rows = store.list_memories(query=name, limit=limit)
99
+ return [_memory_to_dict(m) for m in rows]
100
+
101
+