@simbimbo/memory-ocmemog 0.1.15 → 0.1.17
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 +79 -0
- package/README.md +36 -3
- package/docs/architecture/memory.md +89 -10
- package/docs/release-checklist.md +5 -0
- package/docs/usage.md +71 -1
- package/index.ts +90 -6
- package/ocmemog/__init__.py +1 -1
- package/ocmemog/doctor.py +23 -1
- package/ocmemog/runtime/memory/api.py +103 -19
- package/ocmemog/runtime/memory/embedding_engine.py +24 -0
- package/ocmemog/runtime/memory/promote.py +183 -10
- package/ocmemog/runtime/memory/retrieval.py +185 -16
- package/ocmemog/runtime/memory/vector_index.py +79 -1
- package/ocmemog/sidecar/app.py +339 -6
- package/ocmemog/sidecar/compat.py +160 -2
- package/package.json +1 -1
|
@@ -4,9 +4,10 @@ import importlib
|
|
|
4
4
|
import importlib.util
|
|
5
5
|
import os
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
|
-
from ocmemog.runtime import identity
|
|
10
|
+
from ocmemog.runtime import config, identity, state_store
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@dataclass(frozen=True)
|
|
@@ -17,6 +18,7 @@ class RuntimeStatus:
|
|
|
17
18
|
warnings: list[str]
|
|
18
19
|
identity: dict[str, Any]
|
|
19
20
|
capabilities: list[dict[str, Any]]
|
|
21
|
+
runtime_summary: dict[str, Any]
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
TODO_ITEMS = [
|
|
@@ -36,6 +38,121 @@ _EMBEDDING_PROVIDER_BACKEND_HINTS = {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
|
|
41
|
+
def _parse_agent_id_list(raw: str | None) -> list[str]:
|
|
42
|
+
return [item.strip() for item in str(raw or "").split(",") if item.strip()]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _queue_runtime_summary() -> dict[str, Any]:
|
|
46
|
+
queue_path = state_store.data_dir() / "ingest_queue.jsonl"
|
|
47
|
+
stats_path = state_store.data_dir() / "queue_stats.json"
|
|
48
|
+
depth = 0
|
|
49
|
+
invalid_lines = 0
|
|
50
|
+
retrying_lines = 0
|
|
51
|
+
max_retry_seen = 0
|
|
52
|
+
try:
|
|
53
|
+
if queue_path.exists():
|
|
54
|
+
import json
|
|
55
|
+
|
|
56
|
+
with queue_path.open("r", encoding="utf-8", errors="ignore") as handle:
|
|
57
|
+
for raw_line in handle:
|
|
58
|
+
line = raw_line.strip()
|
|
59
|
+
if not line:
|
|
60
|
+
continue
|
|
61
|
+
depth += 1
|
|
62
|
+
try:
|
|
63
|
+
payload = json.loads(line)
|
|
64
|
+
if isinstance(payload, dict):
|
|
65
|
+
retry_count = int(payload.get("_ocmemog_retry_count", 0) or 0)
|
|
66
|
+
if retry_count > 0:
|
|
67
|
+
retrying_lines += 1
|
|
68
|
+
max_retry_seen = max(max_retry_seen, retry_count)
|
|
69
|
+
except Exception:
|
|
70
|
+
invalid_lines += 1
|
|
71
|
+
except Exception:
|
|
72
|
+
depth = 0
|
|
73
|
+
invalid_lines = 0
|
|
74
|
+
retrying_lines = 0
|
|
75
|
+
max_retry_seen = 0
|
|
76
|
+
|
|
77
|
+
stats: dict[str, Any] = {}
|
|
78
|
+
try:
|
|
79
|
+
if stats_path.exists():
|
|
80
|
+
import json
|
|
81
|
+
|
|
82
|
+
parsed = json.loads(stats_path.read_text(encoding="utf-8"))
|
|
83
|
+
if isinstance(parsed, dict):
|
|
84
|
+
stats = parsed
|
|
85
|
+
except Exception:
|
|
86
|
+
stats = {}
|
|
87
|
+
|
|
88
|
+
worker_enabled = str(os.environ.get("OCMEMOG_INGEST_ASYNC_WORKER", "false")).strip().lower() in {"1", "true", "yes"}
|
|
89
|
+
error_count = int(stats.get("errors") or 0)
|
|
90
|
+
config_issues: list[str] = []
|
|
91
|
+
try:
|
|
92
|
+
worker_poll_seconds = float(os.environ.get("OCMEMOG_INGEST_ASYNC_POLL_SECONDS", "5"))
|
|
93
|
+
if worker_poll_seconds < 0:
|
|
94
|
+
config_issues.append("OCMEMOG_INGEST_ASYNC_POLL_SECONDS must be >= 0")
|
|
95
|
+
except Exception:
|
|
96
|
+
config_issues.append("OCMEMOG_INGEST_ASYNC_POLL_SECONDS")
|
|
97
|
+
try:
|
|
98
|
+
worker_batch_max = int(os.environ.get("OCMEMOG_INGEST_ASYNC_BATCH_MAX", "25"))
|
|
99
|
+
if worker_batch_max < 1:
|
|
100
|
+
config_issues.append("OCMEMOG_INGEST_ASYNC_BATCH_MAX must be >= 1")
|
|
101
|
+
except Exception:
|
|
102
|
+
config_issues.append("OCMEMOG_INGEST_ASYNC_BATCH_MAX")
|
|
103
|
+
hints: list[str] = []
|
|
104
|
+
severity = "ok"
|
|
105
|
+
backlog_severity = "low"
|
|
106
|
+
if config_issues:
|
|
107
|
+
severity = "warn"
|
|
108
|
+
hints.append("queue worker config has invalid values")
|
|
109
|
+
if depth > 0 and not worker_enabled:
|
|
110
|
+
severity = "warn"
|
|
111
|
+
hints.append("queue has backlog but async worker is disabled")
|
|
112
|
+
if error_count > 0:
|
|
113
|
+
severity = "warn"
|
|
114
|
+
hints.append("queue has recorded ingest/parse errors")
|
|
115
|
+
if invalid_lines > 0:
|
|
116
|
+
severity = "warn"
|
|
117
|
+
hints.append("queue contains invalid lines")
|
|
118
|
+
if retrying_lines > 0:
|
|
119
|
+
severity = "warn"
|
|
120
|
+
hints.append("queue contains retrying payloads")
|
|
121
|
+
if depth > 1000:
|
|
122
|
+
backlog_severity = "critical"
|
|
123
|
+
elif depth > 100:
|
|
124
|
+
backlog_severity = "high"
|
|
125
|
+
elif depth > 25:
|
|
126
|
+
backlog_severity = "medium"
|
|
127
|
+
if depth > 100:
|
|
128
|
+
severity = "high"
|
|
129
|
+
hints.append("queue backlog is high")
|
|
130
|
+
elif depth > 25 and severity == "ok":
|
|
131
|
+
severity = "warn"
|
|
132
|
+
hints.append("queue backlog is elevated")
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
"enabled": worker_enabled,
|
|
136
|
+
"status": severity,
|
|
137
|
+
"issues": config_issues,
|
|
138
|
+
"depth": int(depth),
|
|
139
|
+
"queue_depth": int(depth),
|
|
140
|
+
"queue_backlog_severity": backlog_severity,
|
|
141
|
+
"last_run": stats.get("last_run"),
|
|
142
|
+
"last_batch": int(stats.get("last_batch") or 0),
|
|
143
|
+
"processed_total": int(stats.get("processed") or 0),
|
|
144
|
+
"error_count": error_count,
|
|
145
|
+
"last_error": stats.get("last_error"),
|
|
146
|
+
"invalid_lines": int(invalid_lines),
|
|
147
|
+
"retrying_lines": int(retrying_lines),
|
|
148
|
+
"max_retry_seen": int(max_retry_seen),
|
|
149
|
+
"worker_enabled": worker_enabled,
|
|
150
|
+
"config_issues": config_issues,
|
|
151
|
+
"severity": severity,
|
|
152
|
+
"hints": hints,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
39
156
|
def probe_runtime() -> RuntimeStatus:
|
|
40
157
|
runtime_identity = identity.get_runtime_identity()
|
|
41
158
|
capabilities = runtime_identity.get("capabilities", [])
|
|
@@ -59,7 +176,16 @@ def probe_runtime() -> RuntimeStatus:
|
|
|
59
176
|
or os.environ.get("OCMEMOG_EMBED_PROVIDER", "")
|
|
60
177
|
or os.environ.get("BRAIN_EMBED_MODEL_PROVIDER", "")
|
|
61
178
|
).strip().lower()
|
|
62
|
-
|
|
179
|
+
local_model = str(
|
|
180
|
+
getattr(config, "OCMEMOG_EMBED_MODEL_LOCAL", "")
|
|
181
|
+
or getattr(config, "BRAIN_EMBED_MODEL_LOCAL", getattr(config, "OCMEMOG_EMBED_LOCAL_MODEL", "simple"))
|
|
182
|
+
or ""
|
|
183
|
+
).strip().lower()
|
|
184
|
+
sentence_transformers_ready = importlib.util.find_spec("sentence_transformers") is not None
|
|
185
|
+
local_simple_only = local_model in {"", "simple", "hash"}
|
|
186
|
+
provider_configured = provider in _EMBEDDING_PROVIDER_BACKEND_HINTS
|
|
187
|
+
using_hash_embeddings = bool(not provider_configured and local_model in {"", "simple", "hash"} and not sentence_transformers_ready)
|
|
188
|
+
if not sentence_transformers_ready and provider not in _EMBEDDING_PROVIDER_BACKEND_HINTS:
|
|
63
189
|
warnings.append("Optional dependency missing: sentence-transformers; using local hash embeddings.")
|
|
64
190
|
|
|
65
191
|
try:
|
|
@@ -81,6 +207,37 @@ def probe_runtime() -> RuntimeStatus:
|
|
|
81
207
|
|
|
82
208
|
if missing_deps:
|
|
83
209
|
mode = "degraded"
|
|
210
|
+
|
|
211
|
+
hydration_allow_agents = _parse_agent_id_list(os.environ.get("OCMEMOG_AUTO_HYDRATION_ALLOW_AGENT_IDS"))
|
|
212
|
+
hydration_deny_agents = _parse_agent_id_list(os.environ.get("OCMEMOG_AUTO_HYDRATION_DENY_AGENT_IDS"))
|
|
213
|
+
runtime_summary = {
|
|
214
|
+
"mode": mode,
|
|
215
|
+
"embedding_provider": provider or "local-simple",
|
|
216
|
+
"embedding_local_model": local_model or "simple",
|
|
217
|
+
"embedding_path_summary": {
|
|
218
|
+
"enabled": True,
|
|
219
|
+
"status": "provider" if provider_configured else ("local-simple" if using_hash_embeddings else "local"),
|
|
220
|
+
"issues": ["sentence-transformers missing"] if (not sentence_transformers_ready and not provider_configured) else [],
|
|
221
|
+
"provider_configured": provider_configured,
|
|
222
|
+
"provider_backend_hint": provider if provider else None,
|
|
223
|
+
"local_model": local_model or "simple",
|
|
224
|
+
"local_simple_only": local_simple_only,
|
|
225
|
+
"sentence_transformers_ready": sentence_transformers_ready,
|
|
226
|
+
},
|
|
227
|
+
"using_hash_embeddings": using_hash_embeddings,
|
|
228
|
+
"shim_surface_count": shim_count,
|
|
229
|
+
"missing_dep_count": len(missing_deps),
|
|
230
|
+
"warning_count": len(warnings),
|
|
231
|
+
"queue": _queue_runtime_summary(),
|
|
232
|
+
"auto_hydration": {
|
|
233
|
+
"enabled": str(os.environ.get("OCMEMOG_AUTO_HYDRATION", "false")).strip().lower() in {"1", "true", "yes"},
|
|
234
|
+
"status": "enabled" if str(os.environ.get("OCMEMOG_AUTO_HYDRATION", "false")).strip().lower() in {"1", "true", "yes"} else "disabled",
|
|
235
|
+
"issues": [],
|
|
236
|
+
"allow_agent_ids": hydration_allow_agents,
|
|
237
|
+
"deny_agent_ids": hydration_deny_agents,
|
|
238
|
+
"scoped_by_agent": bool(hydration_allow_agents or hydration_deny_agents),
|
|
239
|
+
},
|
|
240
|
+
}
|
|
84
241
|
return RuntimeStatus(
|
|
85
242
|
mode=mode,
|
|
86
243
|
missing_deps=missing_deps,
|
|
@@ -88,6 +245,7 @@ def probe_runtime() -> RuntimeStatus:
|
|
|
88
245
|
warnings=warnings,
|
|
89
246
|
identity=runtime_identity,
|
|
90
247
|
capabilities=capabilities,
|
|
248
|
+
runtime_summary=runtime_summary,
|
|
91
249
|
)
|
|
92
250
|
|
|
93
251
|
|
package/package.json
CHANGED