@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
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, Any
4
+
5
+ from ocmemog.runtime.instrumentation import emit_event
6
+ from ocmemog.runtime import state_store
7
+ from ocmemog.runtime.memory import store
8
+
9
+
10
+ def log_experience(
11
+ task_id: str,
12
+ outcome: str,
13
+ confidence: float,
14
+ reward_score: float,
15
+ memory_reference: str,
16
+ experience_type: str,
17
+ source_module: str,
18
+ ) -> Dict[str, Any]:
19
+ conn = store.connect()
20
+ row = conn.execute(
21
+ "SELECT id FROM experiences WHERE task_id=? AND memory_reference=? AND outcome=?",
22
+ (task_id, memory_reference, outcome),
23
+ ).fetchone()
24
+ if row:
25
+ conn.close()
26
+ emit_event(state_store.report_log_path(), "brain_memory_experience_duplicate", status="ok")
27
+ return {"experience_id": row[0], "duplicate": True}
28
+
29
+ cur = conn.execute(
30
+ "INSERT INTO experiences (task_id, outcome, reward_score, confidence, memory_reference, experience_type, source_module, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
31
+ (task_id, outcome, reward_score, confidence, memory_reference, experience_type, source_module, store.SCHEMA_VERSION),
32
+ )
33
+ conn.commit()
34
+ conn.close()
35
+ emit_event(state_store.report_log_path(), "brain_memory_experience_logged", status="ok")
36
+ return {"experience_id": cur.lastrowid, "duplicate": False, "experience_type": experience_type, "source_module": source_module}
37
+
38
+
39
+ def log_task_execution(
40
+ *,
41
+ task_id: str,
42
+ task_type: str,
43
+ agent_id: str,
44
+ tool_used: str,
45
+ success: bool,
46
+ duration_ms: int,
47
+ ) -> Dict[str, Any]:
48
+ outcome_payload = {
49
+ "task_type": task_type,
50
+ "agent_id": agent_id,
51
+ "tool_used": tool_used,
52
+ "success": bool(success),
53
+ "duration_ms": duration_ms,
54
+ }
55
+ return log_experience(
56
+ task_id=task_id,
57
+ outcome=str(outcome_payload),
58
+ confidence=1.0,
59
+ reward_score=1.0 if success else 0.0,
60
+ memory_reference=f"tool:{tool_used}",
61
+ experience_type="task_execution",
62
+ source_module="task_engine",
63
+ )
64
+
65
+
66
+ def list_recent_experiences(limit: int = 20) -> Dict[str, int]:
67
+ conn = store.connect()
68
+ rows = conn.execute(
69
+ "SELECT experience_type, COUNT(*) as count FROM experiences GROUP BY experience_type ORDER BY count DESC LIMIT ?",
70
+ (limit,),
71
+ ).fetchall()
72
+ conn.close()
73
+ return {row[0]: int(row[1]) for row in rows}
@@ -0,0 +1,224 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import Dict, List, Any, Iterable, Tuple
5
+
6
+ import json
7
+
8
+ from ocmemog.runtime import state_store
9
+ from ocmemog.runtime.instrumentation import emit_event
10
+ from . import memory_links, provenance, store, vector_index
11
+
12
+
13
+ def _tokenize(text: str) -> List[str]:
14
+ return [token for token in "".join(ch.lower() if ch.isalnum() else " " for ch in (text or "")).split() if token]
15
+
16
+
17
+ def _match_score(text: str, query: str) -> float:
18
+ if not text or not query:
19
+ return 0.0
20
+ text_l = text.lower()
21
+ query_l = query.lower()
22
+ if query_l in text_l:
23
+ return 1.0
24
+ query_tokens = set(_tokenize(query_l))
25
+ if not query_tokens:
26
+ return 0.0
27
+ text_tokens = set(_tokenize(text_l))
28
+ if not text_tokens:
29
+ return 0.0
30
+ overlap = len(query_tokens & text_tokens) / max(1, len(query_tokens))
31
+ return round(min(0.95, overlap * 0.85), 3)
32
+
33
+
34
+ def _recency_score(timestamp: str | None) -> float:
35
+ if not timestamp:
36
+ return 0.0
37
+ parsed = None
38
+ for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S.%f"):
39
+ try:
40
+ parsed = datetime.strptime(timestamp, fmt).replace(tzinfo=timezone.utc)
41
+ break
42
+ except ValueError:
43
+ continue
44
+ if parsed is None:
45
+ return 0.0
46
+ age_days = max(0.0, (datetime.now(timezone.utc) - parsed).total_seconds() / 86400.0)
47
+ if age_days <= 1:
48
+ return 0.2
49
+ if age_days <= 7:
50
+ return 0.15
51
+ if age_days <= 30:
52
+ return 0.08
53
+ if age_days <= 180:
54
+ return 0.03
55
+ return 0.0
56
+
57
+
58
+ MEMORY_BUCKETS: Tuple[str, ...] = tuple(store.MEMORY_TABLES)
59
+
60
+
61
+ def _empty_results() -> Dict[str, List[Dict[str, Any]]]:
62
+ return {bucket: [] for bucket in MEMORY_BUCKETS}
63
+
64
+
65
+ def _parse_metadata(raw: Any) -> Dict[str, Any]:
66
+ if isinstance(raw, dict):
67
+ return raw
68
+ try:
69
+ return json.loads(raw or "{}")
70
+ except Exception:
71
+ return {}
72
+
73
+
74
+ def _governance_state(metadata: Dict[str, Any]) -> tuple[str, Dict[str, Any]]:
75
+ preview = provenance.preview_from_metadata(metadata)
76
+ prov = metadata.get("provenance") if isinstance(metadata.get("provenance"), dict) else {}
77
+ state = {
78
+ "memory_status": prov.get("memory_status") or metadata.get("memory_status") or "active",
79
+ "superseded_by": prov.get("superseded_by") or metadata.get("superseded_by"),
80
+ "supersedes": prov.get("supersedes") or metadata.get("supersedes"),
81
+ "duplicate_of": prov.get("duplicate_of") or metadata.get("duplicate_of"),
82
+ "contradicts": prov.get("contradicts") or metadata.get("contradicts") or [],
83
+ "contradiction_status": prov.get("contradiction_status") or metadata.get("contradiction_status"),
84
+ "canonical_reference": prov.get("canonical_reference") or metadata.get("canonical_reference"),
85
+ "provenance_preview": preview,
86
+ }
87
+ return str(state["memory_status"] or "active"), state
88
+
89
+
90
+ def retrieve(
91
+ prompt: str,
92
+ limit: int = 5,
93
+ categories: Iterable[str] | None = None,
94
+ *,
95
+ skip_vector_provider: bool = False,
96
+ ) -> Dict[str, List[Dict[str, Any]]]:
97
+ emit_event(state_store.report_log_path(), "brain_memory_retrieval_start", status="ok")
98
+ emit_event(state_store.report_log_path(), "brain_memory_retrieval_rank_start", status="ok")
99
+
100
+ conn = store.connect()
101
+ results = _empty_results()
102
+ selected_categories = tuple(dict.fromkeys(category for category in (categories or MEMORY_BUCKETS) if category in MEMORY_BUCKETS))
103
+
104
+ reinf_rows = conn.execute("SELECT memory_reference, reward_score, confidence FROM experiences").fetchall()
105
+ reinforcement: Dict[str, Dict[str, float]] = {}
106
+ for row in reinf_rows:
107
+ reference = str(row[0] or "")
108
+ if not reference:
109
+ continue
110
+ current = reinforcement.setdefault(reference, {"reward_score": 0.0, "confidence": 0.0, "count": 0.0})
111
+ current["reward_score"] += float(row[1] or 0.0)
112
+ current["confidence"] += float(row[2] or 0.0)
113
+ current["count"] += 1.0
114
+ for current in reinforcement.values():
115
+ count = max(1.0, float(current.get("count") or 1.0))
116
+ current["reward_score"] = float(current.get("reward_score") or 0.0) / count
117
+ current["confidence"] = float(current.get("confidence") or 0.0) / count
118
+
119
+ semantic_scores: Dict[str, float] = {}
120
+ if prompt.strip():
121
+ for item in vector_index.search_memory(
122
+ prompt,
123
+ limit=max(limit * 6, 20),
124
+ skip_provider=skip_vector_provider,
125
+ source_types=selected_categories,
126
+ ):
127
+ source_type = item.get("source_type") or "knowledge"
128
+ source_id = str(item.get("source_id") or "")
129
+ if source_type in selected_categories and source_id:
130
+ semantic_scores[f"{source_type}:{source_id}"] = float(item.get("score") or 0.0)
131
+
132
+ def score_record(*, content: str, memory_ref: str, promo_conf: float, timestamp: str | None) -> tuple[float, Dict[str, float]]:
133
+ keyword = _match_score(content, prompt)
134
+ semantic = float(semantic_scores.get(memory_ref, 0.0))
135
+ reinf = reinforcement.get(memory_ref, {})
136
+ reinf_score = float(reinf.get("reward_score", 0.0)) * 0.35
137
+ promo_score = float(promo_conf) * 0.2
138
+ recency = _recency_score(timestamp)
139
+ score = round((keyword * 0.45) + (semantic * 0.35) + reinf_score + promo_score + recency, 3)
140
+ return score, {
141
+ "keyword": round(keyword, 3),
142
+ "semantic": round(semantic, 3),
143
+ "reinforcement": round(reinf_score, 3),
144
+ "promotion": round(promo_score, 3),
145
+ "recency": round(recency, 3),
146
+ }
147
+
148
+ for table in selected_categories:
149
+ candidates: Dict[str, Dict[str, Any]] = {}
150
+ try:
151
+ rows = conn.execute(
152
+ f"SELECT id, timestamp, content, confidence, metadata_json FROM {table} ORDER BY id DESC LIMIT ?",
153
+ (max(limit * 20, 50),),
154
+ ).fetchall()
155
+ except Exception:
156
+ continue
157
+ for row in rows:
158
+ content = row["content"] if isinstance(row, dict) else row[2]
159
+ mem_ref = f"{table}:{row[0]}"
160
+ keyword = _match_score(content, prompt)
161
+ semantic = float(semantic_scores.get(mem_ref, 0.0))
162
+ if prompt.strip() and keyword <= 0.0 and semantic <= 0.0:
163
+ continue
164
+ promo_conf = row["confidence"] if isinstance(row, dict) else row[3]
165
+ timestamp = row["timestamp"] if isinstance(row, dict) else row[1]
166
+ raw_metadata = row["metadata_json"] if isinstance(row, dict) else row[4]
167
+ metadata_payload = _parse_metadata(raw_metadata)
168
+ memory_status, governance = _governance_state(metadata_payload)
169
+ if memory_status in {"superseded", "duplicate"}:
170
+ continue
171
+ metadata = provenance.fetch_reference(mem_ref)
172
+ score, signals = score_record(content=content, memory_ref=mem_ref, promo_conf=promo_conf, timestamp=timestamp)
173
+ if memory_status == "contested":
174
+ score = round(max(0.0, score - 0.15), 3)
175
+ signals["contradiction_penalty"] = 0.15
176
+ selected_because = max(signals, key=signals.get) if signals else "keyword"
177
+ candidates[mem_ref] = {
178
+ "content": content,
179
+ "score": score,
180
+ "memory_reference": mem_ref,
181
+ "links": memory_links.get_memory_links(mem_ref),
182
+ "provenance_preview": (metadata or {}).get("provenance_preview") or governance.get("provenance_preview") or provenance.preview_from_metadata((metadata or {}).get("metadata")),
183
+ "retrieval_signals": signals,
184
+ "selected_because": selected_because,
185
+ "timestamp": timestamp,
186
+ "memory_status": memory_status,
187
+ "governance": governance,
188
+ }
189
+ results[table] = sorted(candidates.values(), key=lambda x: x["score"], reverse=True)[:limit]
190
+
191
+ conn.close()
192
+ emit_event(state_store.report_log_path(), "brain_memory_retrieval_rank_complete", status="ok")
193
+ emit_event(state_store.report_log_path(), "brain_memory_retrieval_complete", status="ok")
194
+ return results
195
+
196
+
197
+ def retrieve_for_queries(
198
+ queries: Iterable[str],
199
+ *,
200
+ limit: int = 5,
201
+ categories: Iterable[str] | None = None,
202
+ skip_vector_provider: bool = False,
203
+ ) -> Dict[str, List[Dict[str, Any]]]:
204
+ merged = _empty_results()
205
+ seen_refs = {bucket: set() for bucket in MEMORY_BUCKETS}
206
+ selected_categories = tuple(dict.fromkeys(category for category in (categories or MEMORY_BUCKETS) if category in MEMORY_BUCKETS))
207
+ normalized_queries = [query.strip() for query in queries if isinstance(query, str) and query.strip()]
208
+
209
+ if not normalized_queries:
210
+ return retrieve("", limit=limit, categories=selected_categories)
211
+
212
+ for query in normalized_queries:
213
+ partial = retrieve(query, limit=limit, categories=selected_categories, skip_vector_provider=skip_vector_provider)
214
+ for bucket in selected_categories:
215
+ for item in partial.get(bucket, []):
216
+ ref = item.get("memory_reference")
217
+ if ref in seen_refs[bucket]:
218
+ continue
219
+ seen_refs[bucket].add(ref)
220
+ merged[bucket].append(item)
221
+
222
+ for bucket in selected_categories:
223
+ merged[bucket] = sorted(merged[bucket], key=lambda x: x["score"], reverse=True)[:limit]
224
+ return merged
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, List
4
+
5
+ from ocmemog.runtime import state_store
6
+ from ocmemog.runtime.instrumentation import emit_event
7
+ from ocmemog.runtime.memory import embedding_engine, store, retrieval, freshness
8
+
9
+ LOGFILE = state_store.report_log_path()
10
+
11
+
12
+ def _cosine_similarity(a: List[float], b: List[float]) -> float:
13
+ if not a or not b:
14
+ return 0.0
15
+ size = min(len(a), len(b))
16
+ if size == 0:
17
+ return 0.0
18
+ dot = sum(x * y for x, y in zip(a[:size], b[:size]))
19
+ mag_a = sum(x * x for x in a[:size]) ** 0.5
20
+ mag_b = sum(x * x for x in b[:size]) ** 0.5
21
+ if mag_a == 0 or mag_b == 0:
22
+ return 0.0
23
+ return dot / (mag_a * mag_b)
24
+
25
+
26
+ def semantic_search(query: str, limit: int = 5) -> List[Dict[str, Any]]:
27
+ emit_event(LOGFILE, "brain_semantic_search_start", status="ok")
28
+ query_embedding = embedding_engine.generate_embedding(query)
29
+ conn = store.connect()
30
+ rows = conn.execute(
31
+ "SELECT id, source_type, source_id, embedding FROM vector_embeddings"
32
+ ).fetchall()
33
+ conn.close()
34
+
35
+ reinforcement = retrieval.retrieve(query, limit=limit * 2)
36
+ freshness_info = {item["memory_id"]: item for item in freshness.scan_freshness(limit=limit).get("advisories", [])}
37
+
38
+ results: List[Dict[str, Any]] = []
39
+ for row in rows:
40
+ try:
41
+ embedding = [float(x) for x in __import__("json").loads(row["embedding"])]
42
+ except Exception:
43
+ continue
44
+ similarity = _cosine_similarity(query_embedding or [], embedding)
45
+ memory_ref = f"{row['source_type']}:{row['source_id']}"
46
+ reinforcement_weight = 0.0
47
+ for bucket in reinforcement.values():
48
+ for item in bucket:
49
+ if item.get("memory_reference") == memory_ref:
50
+ reinforcement_weight = item.get("score", 0.0)
51
+ freshness_score = freshness_info.get(int(row["source_id"],), {}).get("freshness_score", 0.0) if str(row["source_id"]).isdigit() else 0.0
52
+ combined = similarity + reinforcement_weight + freshness_score
53
+ results.append(
54
+ {
55
+ "memory_reference": memory_ref,
56
+ "score": round(combined, 6),
57
+ "similarity": round(similarity, 6),
58
+ "freshness": freshness_score,
59
+ "reinforcement_weight": reinforcement_weight,
60
+ "promotion_confidence": 0.0,
61
+ }
62
+ )
63
+
64
+ results.sort(key=lambda item: item["score"], reverse=True)
65
+ emit_event(LOGFILE, "brain_semantic_search_complete", status="ok", result_count=len(results[:limit]))
66
+ return results[:limit]