@simbimbo/memory-ocmemog 0.1.11 → 0.1.12

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 (102) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +83 -18
  3. package/brain/runtime/__init__.py +2 -12
  4. package/brain/runtime/config.py +1 -24
  5. package/brain/runtime/inference.py +1 -151
  6. package/brain/runtime/instrumentation.py +1 -15
  7. package/brain/runtime/memory/__init__.py +3 -13
  8. package/brain/runtime/memory/api.py +1 -1219
  9. package/brain/runtime/memory/candidate.py +1 -185
  10. package/brain/runtime/memory/conversation_state.py +1 -1823
  11. package/brain/runtime/memory/distill.py +1 -344
  12. package/brain/runtime/memory/embedding_engine.py +1 -92
  13. package/brain/runtime/memory/freshness.py +1 -112
  14. package/brain/runtime/memory/health.py +1 -40
  15. package/brain/runtime/memory/integrity.py +1 -186
  16. package/brain/runtime/memory/memory_consolidation.py +1 -58
  17. package/brain/runtime/memory/memory_links.py +1 -107
  18. package/brain/runtime/memory/memory_salience.py +1 -233
  19. package/brain/runtime/memory/memory_synthesis.py +1 -31
  20. package/brain/runtime/memory/memory_taxonomy.py +1 -33
  21. package/brain/runtime/memory/pondering_engine.py +1 -654
  22. package/brain/runtime/memory/promote.py +1 -277
  23. package/brain/runtime/memory/provenance.py +1 -406
  24. package/brain/runtime/memory/reinforcement.py +1 -71
  25. package/brain/runtime/memory/retrieval.py +1 -210
  26. package/brain/runtime/memory/semantic_search.py +1 -64
  27. package/brain/runtime/memory/store.py +1 -429
  28. package/brain/runtime/memory/unresolved_state.py +1 -91
  29. package/brain/runtime/memory/vector_index.py +1 -323
  30. package/brain/runtime/model_roles.py +1 -9
  31. package/brain/runtime/model_router.py +1 -22
  32. package/brain/runtime/providers.py +1 -66
  33. package/brain/runtime/security/redaction.py +1 -12
  34. package/brain/runtime/state_store.py +1 -23
  35. package/brain/runtime/storage_paths.py +1 -39
  36. package/docs/architecture/memory.md +20 -24
  37. package/docs/release-checklist.md +19 -6
  38. package/docs/usage.md +33 -17
  39. package/index.ts +8 -1
  40. package/ocmemog/__init__.py +11 -0
  41. package/ocmemog/doctor.py +1255 -0
  42. package/ocmemog/runtime/__init__.py +18 -0
  43. package/ocmemog/runtime/_compat_bridge.py +28 -0
  44. package/ocmemog/runtime/config.py +35 -0
  45. package/ocmemog/runtime/identity.py +115 -0
  46. package/ocmemog/runtime/inference.py +164 -0
  47. package/ocmemog/runtime/instrumentation.py +20 -0
  48. package/ocmemog/runtime/memory/__init__.py +91 -0
  49. package/ocmemog/runtime/memory/api.py +1431 -0
  50. package/ocmemog/runtime/memory/candidate.py +192 -0
  51. package/ocmemog/runtime/memory/conversation_state.py +1831 -0
  52. package/ocmemog/runtime/memory/distill.py +282 -0
  53. package/ocmemog/runtime/memory/embedding_engine.py +151 -0
  54. package/ocmemog/runtime/memory/freshness.py +114 -0
  55. package/ocmemog/runtime/memory/health.py +57 -0
  56. package/ocmemog/runtime/memory/integrity.py +208 -0
  57. package/ocmemog/runtime/memory/memory_consolidation.py +60 -0
  58. package/ocmemog/runtime/memory/memory_links.py +109 -0
  59. package/ocmemog/runtime/memory/memory_salience.py +235 -0
  60. package/ocmemog/runtime/memory/memory_synthesis.py +33 -0
  61. package/ocmemog/runtime/memory/memory_taxonomy.py +35 -0
  62. package/ocmemog/runtime/memory/pondering_engine.py +681 -0
  63. package/ocmemog/runtime/memory/promote.py +279 -0
  64. package/ocmemog/runtime/memory/provenance.py +408 -0
  65. package/ocmemog/runtime/memory/reinforcement.py +73 -0
  66. package/ocmemog/runtime/memory/retrieval.py +224 -0
  67. package/ocmemog/runtime/memory/semantic_search.py +66 -0
  68. package/ocmemog/runtime/memory/store.py +433 -0
  69. package/ocmemog/runtime/memory/unresolved_state.py +93 -0
  70. package/ocmemog/runtime/memory/vector_index.py +411 -0
  71. package/ocmemog/runtime/model_roles.py +16 -0
  72. package/ocmemog/runtime/model_router.py +29 -0
  73. package/ocmemog/runtime/providers.py +79 -0
  74. package/ocmemog/runtime/roles.py +92 -0
  75. package/ocmemog/runtime/security/__init__.py +8 -0
  76. package/ocmemog/runtime/security/redaction.py +17 -0
  77. package/ocmemog/runtime/state_store.py +34 -0
  78. package/ocmemog/runtime/storage_paths.py +70 -0
  79. package/ocmemog/sidecar/app.py +310 -23
  80. package/ocmemog/sidecar/compat.py +50 -13
  81. package/ocmemog/sidecar/transcript_watcher.py +318 -240
  82. package/openclaw.plugin.json +4 -0
  83. package/package.json +1 -1
  84. package/scripts/ocmemog-backfill-vectors.py +5 -3
  85. package/scripts/ocmemog-continuity-benchmark.py +1 -1
  86. package/scripts/ocmemog-demo.py +1 -1
  87. package/scripts/ocmemog-doctor.py +15 -0
  88. package/scripts/ocmemog-install.sh +29 -7
  89. package/scripts/ocmemog-integrated-proof.py +373 -0
  90. package/scripts/ocmemog-reindex-vectors.py +5 -3
  91. package/scripts/ocmemog-release-check.sh +330 -0
  92. package/scripts/ocmemog-sidecar.sh +4 -2
  93. package/scripts/ocmemog-test-rig.py +5 -3
  94. package/brain/runtime/memory/artifacts.py +0 -33
  95. package/brain/runtime/memory/context_builder.py +0 -112
  96. package/brain/runtime/memory/interaction_memory.py +0 -57
  97. package/brain/runtime/memory/memory_gate.py +0 -38
  98. package/brain/runtime/memory/memory_graph.py +0 -54
  99. package/brain/runtime/memory/person_identity.py +0 -83
  100. package/brain/runtime/memory/person_memory.py +0 -138
  101. package/brain/runtime/memory/sentiment_memory.py +0 -67
  102. package/brain/runtime/memory/tool_catalog.py +0 -68
@@ -1,346 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
- import os
5
- import re
6
- from typing import Dict, Any, List
7
-
8
- from brain.runtime.instrumentation import emit_event
9
- from brain.runtime import state_store
10
- from brain.runtime.memory import candidate, provenance, store
11
- from brain.runtime.security import redaction
12
- from brain.runtime import inference
13
- from brain.runtime import model_roles
14
-
15
-
16
- def _normalize(text: str) -> str:
17
- return re.sub(r"\s+", " ", text.lower()).strip()
18
-
19
-
20
- def _heuristic_summary(text: str) -> str:
21
- lines = [line.strip() for line in text.splitlines() if line.strip()]
22
- if not lines:
23
- return ""
24
- return lines[0][:240]
25
-
26
-
27
- def _should_skip_local_distill(text: str) -> bool:
28
- cleaned = _normalize(text)
29
- if not cleaned or len(cleaned) < 24:
30
- return True
31
- if cleaned in {"ok", "okay", "done", "fixed", "working", "success", "positive feedback"}:
32
- return True
33
- return False
34
-
35
-
36
- def _local_distill_summary(text: str) -> str:
37
- if _should_skip_local_distill(text):
38
- return ""
39
- prompt = (
40
- "Distill this experience into one concise operational summary. "
41
- "Prefer concrete cause/effect, decision, or reusable takeaway. "
42
- "Keep it under 220 characters. Return NONE if there is no meaningful takeaway.\n\n"
43
- f"Experience:\n{text}\n\n"
44
- "Summary:"
45
- )
46
- model = os.environ.get("OCMEMOG_PONDER_MODEL", "local-openai:qwen2.5-7b-instruct")
47
- try:
48
- result = inference.infer(prompt, provider_name=model)
49
- except Exception:
50
- return ""
51
- if result.get("status") != "ok":
52
- return ""
53
- output = str(result.get("output", "")).strip()
54
- output = re.sub(r"^(Summary|Sentence|Lesson):\s*", "", output, flags=re.IGNORECASE).strip()
55
- if not output or output.upper().startswith("NONE"):
56
- return ""
57
- return output[:240]
58
-
59
-
60
- def _frontier_distill_summary(text: str) -> str:
61
- try:
62
- model = model_roles.get_model_for_role("memory")
63
- result = inference.infer(
64
- f"Distill this experience into a concise summary:\n\n{text}".strip(),
65
- provider_name=model,
66
- )
67
- if result.get("status") == "ok":
68
- return str(result.get("output", "")).strip()[:240]
69
- except Exception:
70
- return ""
71
- return ""
72
-
73
-
74
- def _needs_frontier_refine(summary: str, source: str) -> bool:
75
- if not summary:
76
- return True
77
- lowered = summary.lower().strip()
78
- if lowered.startswith(("be ", "always ", "remember ", "good job", "be careful")):
79
- return True
80
- if len(summary) < 24:
81
- return True
82
- if len(summary) > len(source):
83
- return True
84
- if _normalize(summary) == _normalize(_heuristic_summary(source)):
85
- return True
86
- return False
87
-
88
-
89
- def _reject_distilled_summary(summary: str, source: str) -> bool:
90
- lowered = _normalize(summary)
91
- if not lowered:
92
- return True
93
- if lowered in {"ok", "okay", "done", "fixed", "working", "positive feedback", "success", "passed"}:
94
- return True
95
- if len(lowered) < 16:
96
- return True
97
- if lowered.startswith(("good job", "be proactive", "be thorough", "always check", "always remember")):
98
- return True
99
- if source and lowered == _normalize(source):
100
- # In no-model environments the best available summary can be the
101
- # original one-line experience. Keep rejecting verbose/source-equal
102
- # fallbacks, but allow concise operational statements through.
103
- compact_source = re.sub(r"\s+", " ", str(source or "")).strip()
104
- if "\n" in compact_source or len(compact_source) > 120:
105
- return True
106
- return False
107
-
108
-
109
- def _verification_points(text: str) -> List[str]:
110
- points = []
111
- if "verify" in text.lower():
112
- points.append("Verify referenced assumptions")
113
- if "risk" in text.lower():
114
- points.append("Validate risk and mitigation")
115
- if not points:
116
- points.append("Confirm key facts before promotion")
117
- return points[:3]
118
-
119
-
120
- def _candidate_score(summary: str, source: str) -> float:
121
- if not source:
122
- return 0.0
123
- ratio = len(summary) / max(1, len(source))
124
- score = 1.0 - min(1.0, ratio * 0.5)
125
- return round(max(0.1, min(1.0, score)), 3)
126
-
127
-
128
- def _row_value(row: Any, key: str, fallback_index: int | None = None) -> Any:
129
- if isinstance(row, dict):
130
- return row.get(key)
131
- try:
132
- return row[key]
133
- except Exception:
134
- if fallback_index is None:
135
- return None
136
- try:
137
- return row[fallback_index]
138
- except Exception:
139
- return None
140
-
141
-
142
-
143
- def distill_experiences(limit: int = 10) -> List[Dict[str, Any]]:
144
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_distill_start", status="ok")
145
- conn = store.connect()
146
- rows = conn.execute(
147
- "SELECT id, task_id, outcome, source_module, metadata_json FROM experiences ORDER BY id DESC LIMIT ?",
148
- (limit,),
149
- ).fetchall()
150
- conn.close()
151
-
152
- distilled: List[Dict[str, Any]] = []
153
- seen = set()
154
-
155
- for row in rows:
156
- source_id = _row_value(row, "id", 0)
157
- task_id = _row_value(row, "task_id", 1)
158
- content = _row_value(row, "outcome", 2) or ""
159
- source_module = _row_value(row, "source_module", 3)
160
- raw_metadata = _row_value(row, "metadata_json", 4) or "{}"
161
- try:
162
- experience_metadata = json.loads(raw_metadata) if isinstance(raw_metadata, str) else dict(raw_metadata or {})
163
- except Exception:
164
- experience_metadata = {}
165
- content, _ = redaction.redact_text(content)
166
-
167
- heuristic_summary = _heuristic_summary(content)
168
- summary = _local_distill_summary(content)
169
- if _needs_frontier_refine(summary, content):
170
- refined = _frontier_distill_summary(content)
171
- if refined:
172
- summary = refined
173
-
174
- if not summary or len(summary) > len(content):
175
- summary = heuristic_summary
176
-
177
- summary, _ = redaction.redact_text(summary)
178
- norm = _normalize(summary)
179
- if _reject_distilled_summary(summary, content):
180
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_distill_rejected", status="ok")
181
- continue
182
- if not norm or norm in seen:
183
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_distill_rejected", status="ok")
184
- continue
185
-
186
- seen.add(norm)
187
- verification = _verification_points(content)
188
- score = _candidate_score(summary, content)
189
- ratio = len(summary) / max(1, len(content))
190
-
191
- if score <= 0.1:
192
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_distill_rejected", status="ok")
193
- continue
194
-
195
- candidate_metadata = provenance.normalize_metadata(
196
- {
197
- **experience_metadata,
198
- "compression_ratio": round(ratio, 3),
199
- "task_id": task_id,
200
- "source_event_id": source_id,
201
- "experience_reference": f"experiences:{source_id}",
202
- "derived_via": "distill",
203
- "kind": "distilled_candidate",
204
- "source_labels": [*(experience_metadata.get("source_labels") or []), *( [source_module] if source_module else [])],
205
- },
206
- source=source_module,
207
- )
208
- candidate_result = candidate.create_candidate(
209
- source_event_id=source_id,
210
- distilled_summary=summary,
211
- verification_points=verification,
212
- confidence_score=score,
213
- metadata=candidate_metadata,
214
- )
215
-
216
- distilled.append({
217
- "source_event_id": source_id,
218
- "distilled_summary": summary,
219
- "verification_points": verification,
220
- "confidence_score": score,
221
- "compression_ratio": round(ratio, 3),
222
- "candidate_id": candidate_result.get("candidate_id"),
223
- "duplicate": candidate_result.get("duplicate"),
224
- "provenance": provenance.preview_from_metadata(candidate_metadata),
225
- })
226
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_distill_success", status="ok")
227
-
228
- return distilled
229
-
230
-
231
- def distill_artifact(artifact: Dict[str, Any]) -> List[Dict[str, Any]]:
232
- text = artifact.get("content_text", "")
233
- if not isinstance(text, str) or not text.strip():
234
- return []
235
-
236
- text, _ = redaction.redact_text(text)
237
- summary = _local_distill_summary(text)
238
- if _needs_frontier_refine(summary, text):
239
- refined = _frontier_distill_summary(text)
240
- if refined:
241
- summary = refined
242
- if not summary or len(summary) > len(text):
243
- summary = _heuristic_summary(text)
244
- summary, _ = redaction.redact_text(summary)
245
- if _reject_distilled_summary(summary, text):
246
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_distill_rejected", status="ok")
247
- return []
248
- norm = _normalize(summary)
249
- if not norm:
250
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_distill_rejected", status="ok")
251
- return []
252
-
253
- verification = _verification_points(text)
254
- score = _candidate_score(summary, text)
255
- ratio = len(summary) / max(1, len(text))
256
-
257
- if score <= 0.1:
258
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_distill_rejected", status="ok")
259
- return []
260
-
261
- candidate_metadata = provenance.normalize_metadata(
262
- {
263
- "compression_ratio": round(ratio, 3),
264
- "artifact_id": artifact.get("artifact_id"),
265
- "derived_via": "artifact_distill",
266
- "kind": "distilled_candidate",
267
- "source_labels": ["artifact"],
268
- }
269
- )
270
- candidate_result = candidate.create_candidate(
271
- source_event_id=0,
272
- distilled_summary=summary,
273
- verification_points=verification,
274
- confidence_score=score,
275
- metadata=candidate_metadata,
276
- )
277
-
278
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_distill_success", status="ok")
279
- return [{
280
- "source_event_id": 0,
281
- "distilled_summary": summary,
282
- "verification_points": verification,
283
- "confidence_score": score,
284
- "compression_ratio": round(ratio, 3),
285
- "candidate_id": candidate_result.get("candidate_id"),
286
- "duplicate": candidate_result.get("duplicate"),
287
- "provenance": provenance.preview_from_metadata(candidate_metadata),
288
- }]
289
-
290
- candidate_metadata = provenance.normalize_metadata(
291
- {
292
- "compression_ratio": round(ratio, 3),
293
- "artifact_id": artifact.get("artifact_id"),
294
- "derived_via": "artifact_distill",
295
- "kind": "distilled_candidate",
296
- "source_labels": ["artifact"],
297
- }
298
- )
299
- candidate_result = candidate.create_candidate(
300
- source_event_id=0,
301
- distilled_summary=summary,
302
- verification_points=verification,
303
- confidence_score=score,
304
- metadata=candidate_metadata,
305
- )
306
-
307
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_distill_success", status="ok")
308
- return [{
309
- "source_event_id": 0,
310
- "distilled_summary": summary,
311
- "verification_points": verification,
312
- "confidence_score": score,
313
- "compression_ratio": round(ratio, 3),
314
- "candidate_id": candidate_result.get("candidate_id"),
315
- "duplicate": candidate_result.get("duplicate"),
316
- "provenance": provenance.preview_from_metadata(candidate_metadata),
317
- }]
318
-
319
- candidate_metadata = provenance.normalize_metadata(
320
- {
321
- "compression_ratio": round(ratio, 3),
322
- "artifact_id": artifact.get("artifact_id"),
323
- "derived_via": "artifact_distill",
324
- "kind": "distilled_candidate",
325
- "source_labels": ["artifact"],
326
- }
327
- )
328
- candidate_result = candidate.create_candidate(
329
- source_event_id=0,
330
- distilled_summary=summary,
331
- verification_points=verification,
332
- confidence_score=score,
333
- metadata=candidate_metadata,
334
- )
335
-
336
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_distill_success", status="ok")
337
- return [{
338
- "source_event_id": 0,
339
- "distilled_summary": summary,
340
- "verification_points": verification,
341
- "confidence_score": score,
342
- "compression_ratio": round(ratio, 3),
343
- "candidate_id": candidate_result.get("candidate_id"),
344
- "duplicate": candidate_result.get("duplicate"),
345
- "provenance": provenance.preview_from_metadata(candidate_metadata),
346
- }]
3
+ from ocmemog.runtime.memory.distill import * # noqa: F401,F403
@@ -1,94 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- import hashlib
4
- from typing import List, Any
5
-
6
- from brain.runtime import config, state_store, model_router
7
- from brain.runtime.instrumentation import emit_event
8
- from brain.runtime.providers import provider_execute
9
-
10
- LOGFILE = state_store.reports_dir() / "brain_memory.log.jsonl"
11
- _MODEL_CACHE: dict[str, Any] = {}
12
-
13
-
14
- def _simple_embedding(text: str, dims: int = 8) -> List[float]:
15
- digest = hashlib.sha256(text.encode("utf-8")).digest()
16
- values = [digest[i] / 255.0 for i in range(dims)]
17
- return values
18
-
19
-
20
- def _load_sentence_transformer(model_name: str) -> Any | None:
21
- if model_name in _MODEL_CACHE:
22
- return _MODEL_CACHE[model_name]
23
- try:
24
- from sentence_transformers import SentenceTransformer
25
- except Exception:
26
- return None
27
- model = SentenceTransformer(model_name)
28
- _MODEL_CACHE[model_name] = model
29
- return model
30
-
31
-
32
- def _provider_embedding(text: str, model_name: str) -> tuple[List[float] | None, dict[str, str]]:
33
- selection = model_router.get_provider_for_role("embedding")
34
- if not selection.provider_id:
35
- return None, {}
36
- response = provider_execute.execute_embedding_call(selection, text)
37
- embedding = response.get("embedding") if isinstance(response, dict) else None
38
- meta = {
39
- "provider_id": str(getattr(selection, "provider_id", "") or ""),
40
- "model": str(model_name or getattr(selection, "model", "") or ""),
41
- }
42
- if isinstance(embedding, list):
43
- return [float(x) for x in embedding], meta
44
- return None, meta
45
-
46
-
47
- def generate_embedding(text: str) -> List[float] | None:
48
- emit_event(LOGFILE, "brain_embedding_start", status="ok")
49
- if not isinstance(text, str) or not text.strip():
50
- emit_event(LOGFILE, "brain_embedding_failed", status="error", reason="empty_text")
51
- return None
52
- local_model = getattr(config, "BRAIN_EMBED_MODEL_LOCAL", "simple")
53
- provider_model = getattr(config, "BRAIN_EMBED_MODEL_PROVIDER", "")
54
-
55
- if provider_model:
56
- try:
57
- embedding, provider_meta = _provider_embedding(text, provider_model)
58
- except Exception:
59
- embedding, provider_meta = None, {}
60
- if embedding:
61
- emit_event(
62
- LOGFILE,
63
- "brain_embedding_complete",
64
- status="ok",
65
- provider="provider",
66
- provider_id=provider_meta.get("provider_id", ""),
67
- model=provider_meta.get("model", ""),
68
- )
69
- emit_event(
70
- LOGFILE,
71
- "brain_embedding_generated",
72
- status="ok",
73
- provider="provider",
74
- dimensions=len(embedding),
75
- provider_id=provider_meta.get("provider_id", ""),
76
- model=provider_meta.get("model", ""),
77
- )
78
- return embedding
79
-
80
- if local_model:
81
- if local_model in {"simple", "hash"}:
82
- embedding = _simple_embedding(text)
83
- emit_event(LOGFILE, "brain_embedding_complete", status="ok", provider="local_simple")
84
- emit_event(LOGFILE, "brain_embedding_generated", status="ok", provider="local_simple", dimensions=len(embedding))
85
- return embedding
86
- model = _load_sentence_transformer(local_model)
87
- if model is not None:
88
- embedding = model.encode([text])[0]
89
- emit_event(LOGFILE, "brain_embedding_complete", status="ok", provider="local_model")
90
- vector = [float(x) for x in embedding]
91
- emit_event(LOGFILE, "brain_embedding_generated", status="ok", provider="local_model", dimensions=len(vector))
92
- return vector
93
- emit_event(LOGFILE, "brain_embedding_failed", status="error", reason="no_embedding")
94
- return None
3
+ from ocmemog.runtime.memory.embedding_engine import * # noqa: F401,F403
@@ -1,114 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- import time
4
- from typing import Any, Dict, List
5
-
6
- from brain.runtime import state_store
7
- from brain.runtime.instrumentation import emit_event
8
- from brain.runtime.memory import store
9
-
10
- DEFAULT_STALE_DAYS = 30
11
- DEFAULT_CONFIDENCE_THRESHOLD = 0.6
12
- DEFAULT_LIMIT = 25
13
-
14
-
15
- _BAD_SUMMARY_PREFIXES = (
16
- "promoted",
17
- "candidate_promoted",
18
- "no local memory summary available",
19
- "summary",
20
- )
21
-
22
-
23
- def _summary_from_content(content: Any, limit: int = 120) -> str:
24
- text = str(content or "").strip()
25
- if not text:
26
- return "(empty memory content)"
27
- lines = [line.strip() for line in text.splitlines() if line.strip()]
28
- for line in lines:
29
- lowered = line.lower()
30
- if lowered in _BAD_SUMMARY_PREFIXES:
31
- continue
32
- if any(lowered.startswith(prefix + ":") for prefix in _BAD_SUMMARY_PREFIXES):
33
- continue
34
- return line[:limit]
35
- return "(needs summary cleanup)"
36
-
37
-
38
- def scan_freshness(
39
- stale_days: int = DEFAULT_STALE_DAYS,
40
- confidence_threshold: float = DEFAULT_CONFIDENCE_THRESHOLD,
41
- limit: int = DEFAULT_LIMIT,
42
- ) -> Dict[str, Any]:
43
- emit_event(
44
- state_store.reports_dir() / "brain_memory.log.jsonl",
45
- "brain_memory_freshness_scan_start",
46
- status="ok",
47
- stale_days=stale_days,
48
- confidence_threshold=confidence_threshold,
49
- )
50
- conn = store.connect()
51
- stale_rows = conn.execute(
52
- """
53
- SELECT 'knowledge' AS memory_type, id, timestamp, confidence, content
54
- FROM knowledge
55
- WHERE timestamp <= datetime('now', ?)
56
- ORDER BY timestamp ASC
57
- LIMIT ?
58
- """,
59
- (f"-{max(1, stale_days)} days", limit),
60
- ).fetchall()
61
- low_conf_rows = conn.execute(
62
- """
63
- SELECT 'knowledge' AS memory_type, id, timestamp, confidence, content
64
- FROM knowledge
65
- WHERE confidence < ?
66
- ORDER BY confidence ASC, timestamp ASC
67
- LIMIT ?
68
- """,
69
- (confidence_threshold, limit),
70
- ).fetchall()
71
- conn.close()
72
- advisories: List[Dict[str, Any]] = []
73
- refresh_candidates: List[Dict[str, Any]] = []
74
- now_ts = time.time()
75
- for category, rows in (("stale", stale_rows), ("low_confidence", low_conf_rows)):
76
- for row in rows:
77
- age_seconds = 0.0
78
- if row["timestamp"]:
79
- try:
80
- age_seconds = max(0.0, now_ts - time.mktime(time.strptime(row["timestamp"], "%Y-%m-%d %H:%M:%S")))
81
- except Exception:
82
- age_seconds = 0.0
83
- confidence = float(row["confidence"] or 0.0)
84
- freshness_score = max(0.0, 1.0 - min(age_seconds / (stale_days * 86400), 1.0)) * (0.5 + confidence / 2.0)
85
- refresh_recommended = freshness_score < 0.4 or category == "stale"
86
- entry = {
87
- "category": category,
88
- "memory_type": row["memory_type"],
89
- "memory_id": row["id"],
90
- "timestamp": row["timestamp"],
91
- "confidence": confidence,
92
- "summary": _summary_from_content(row["content"]),
93
- "freshness_score": round(freshness_score, 3),
94
- "refresh_recommended": refresh_recommended,
95
- }
96
- advisories.append(entry)
97
- refresh_candidates.append(entry)
98
- emit_event(
99
- state_store.reports_dir() / "brain_memory.log.jsonl",
100
- "brain_memory_freshness_scan_complete",
101
- status="ok",
102
- advisory_count=len(advisories),
103
- refresh_candidates=len(refresh_candidates),
104
- )
105
- return {
106
- "ok": True,
107
- "advisory_only": True,
108
- "advisories": advisories,
109
- "refresh_candidates": refresh_candidates,
110
- }
111
-
112
-
113
- def freshness_weight(score: float) -> float:
114
- return max(0.0, min(1.0, float(score)))
3
+ from ocmemog.runtime.memory.freshness import * # noqa: F401,F403
@@ -1,42 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Dict, Any
4
-
5
- from brain.runtime.memory import store, integrity
6
-
7
-
8
- EMBED_TABLES = tuple(store.MEMORY_TABLES)
9
-
10
-
11
- def get_memory_health() -> Dict[str, Any]:
12
- conn = store.connect()
13
- counts: Dict[str, int] = {}
14
- for table in ["experiences", "candidates", "promotions", "memory_index", *store.MEMORY_TABLES, "vector_embeddings"]:
15
- try:
16
- counts[table] = conn.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0]
17
- except Exception:
18
- counts[table] = 0
19
-
20
- vector_count = 0
21
- try:
22
- vector_count = conn.execute(
23
- "SELECT COUNT(*) FROM vector_embeddings WHERE source_type IN ('knowledge','preferences','identity','reflections','directives','tasks','runbooks','lessons')"
24
- ).fetchone()[0]
25
- except Exception:
26
- vector_count = 0
27
-
28
- total_embed_sources = sum(counts.get(table, 0) for table in EMBED_TABLES)
29
- conn.close()
30
- integrity_result = integrity.run_integrity_check()
31
-
32
- coverage = 0.0
33
- if total_embed_sources:
34
- coverage = round(vector_count / total_embed_sources, 3)
35
-
36
- return {
37
- "counts": counts,
38
- "vector_index_count": vector_count,
39
- "vector_index_coverage": coverage,
40
- "vector_index_integrity_status": integrity_result.get("ok"),
41
- "integrity": integrity_result,
42
- }
3
+ from ocmemog.runtime.memory.health import * # noqa: F401,F403