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.
- loop_memory/__init__.py +62 -0
- loop_memory/backends/__init__.py +13 -0
- loop_memory/backends/embedding.py +82 -0
- loop_memory/backends/sentence_embedder.py +30 -0
- loop_memory/backends/vector_store.py +139 -0
- loop_memory/cli/__init__.py +0 -0
- loop_memory/cli/_common.py +68 -0
- loop_memory/cli/commands/__init__.py +13 -0
- loop_memory/cli/commands/cognitive.py +205 -0
- loop_memory/cli/commands/diag.py +346 -0
- loop_memory/cli/commands/graph.py +21 -0
- loop_memory/cli/commands/hooks.py +212 -0
- loop_memory/cli/commands/read.py +362 -0
- loop_memory/cli/commands/serve.py +147 -0
- loop_memory/cli/commands/write.py +138 -0
- loop_memory/cli/main.py +115 -0
- loop_memory/engine/__init__.py +0 -0
- loop_memory/engine/loop.py +247 -0
- loop_memory/engine/reflect.py +89 -0
- loop_memory/examples/__init__.py +0 -0
- loop_memory/examples/demo.py +39 -0
- loop_memory/export/__init__.py +39 -0
- loop_memory/export/memory_md.py +629 -0
- loop_memory/graph/__init__.py +0 -0
- loop_memory/graph/build.py +259 -0
- loop_memory/graph/extract.py +197 -0
- loop_memory/ingest/__init__.py +0 -0
- loop_memory/ingest/loader.py +782 -0
- loop_memory/ingest/pipeline.py +458 -0
- loop_memory/jobs/__init__.py +0 -0
- loop_memory/jobs/cognitive.py +353 -0
- loop_memory/jobs/compact.py +371 -0
- loop_memory/jobs/consolidate.py +95 -0
- loop_memory/jobs/contradiction.py +281 -0
- loop_memory/jobs/evolution.py +2021 -0
- loop_memory/jobs/graph.py +395 -0
- loop_memory/jobs/llm_compact_pass.py +24 -0
- loop_memory/jobs/llm_consolidate.py +980 -0
- loop_memory/jobs/scheduler.py +495 -0
- loop_memory/llm/__init__.py +0 -0
- loop_memory/llm/base.py +80 -0
- loop_memory/llm/openai_adapter.py +31 -0
- loop_memory/llm/providers.py +517 -0
- loop_memory/mcp/__init__.py +804 -0
- loop_memory/memory/__init__.py +0 -0
- loop_memory/memory/types.py +199 -0
- loop_memory/privacy/__init__.py +22 -0
- loop_memory/privacy/private.py +46 -0
- loop_memory/privacy/redact.py +188 -0
- loop_memory/py.typed +0 -0
- loop_memory/sdk.py +875 -0
- loop_memory/sdk_extensions.py +384 -0
- loop_memory/security/__init__.py +20 -0
- loop_memory/security/secrets.py +464 -0
- loop_memory/serve/__init__.py +0 -0
- loop_memory/serve/app.py +506 -0
- loop_memory/serve/handlers.py +316 -0
- loop_memory/serve/routes/_shared.py +59 -0
- loop_memory/serve/routes/admin.py +970 -0
- loop_memory/serve/routes/cognitive.py +64 -0
- loop_memory/serve/routes/export.py +65 -0
- loop_memory/serve/routes/graph.py +101 -0
- loop_memory/serve/routes/insights.py +702 -0
- loop_memory/serve/routes/memories.py +435 -0
- loop_memory/serve/routes/sessions.py +75 -0
- loop_memory/serve/routes/system.py +493 -0
- loop_memory/serve/routes/wiki.py +812 -0
- loop_memory/serve/static/__init__.py +0 -0
- loop_memory/serve/static/index.html +15 -0
- loop_memory/serve/watcher.py +451 -0
- loop_memory/storage/__init__.py +5 -0
- loop_memory/storage/retrieval.py +365 -0
- loop_memory/storage/sqlite_store.py +3627 -0
- loop_memory/wiki/__init__.py +41 -0
- loop_memory/wiki/backfill.py +143 -0
- loop_memory/wiki/classifier.py +238 -0
- loop_memory/wiki/prompts.py +295 -0
- loop_memory/wiki/scope.py +227 -0
- loop_memory-0.4.0.dist-info/METADATA +627 -0
- loop_memory-0.4.0.dist-info/RECORD +84 -0
- loop_memory-0.4.0.dist-info/WHEEL +5 -0
- loop_memory-0.4.0.dist-info/entry_points.txt +2 -0
- loop_memory-0.4.0.dist-info/licenses/LICENSE +21 -0
- 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
|
+
|