@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,73 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Dict, Any
4
-
5
- from brain.runtime.instrumentation import emit_event
6
- from brain.runtime import state_store
7
- from brain.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.reports_dir() / "brain_memory.log.jsonl", "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.reports_dir() / "brain_memory.log.jsonl", "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}
3
+ from ocmemog.runtime.memory.reinforcement import * # noqa: F401,F403
@@ -1,212 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- from datetime import datetime, timezone
4
- from typing import Dict, List, Any, Iterable, Tuple
5
-
6
- import json
7
-
8
- from brain.runtime.instrumentation import emit_event
9
- from brain.runtime import state_store
10
- from brain.runtime.memory 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(prompt: str, limit: int = 5, categories: Iterable[str] | None = None) -> Dict[str, List[Dict[str, Any]]]:
91
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_retrieval_start", status="ok")
92
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_retrieval_rank_start", status="ok")
93
-
94
- conn = store.connect()
95
- results = _empty_results()
96
- selected_categories = tuple(dict.fromkeys(category for category in (categories or MEMORY_BUCKETS) if category in MEMORY_BUCKETS))
97
-
98
- reinf_rows = conn.execute("SELECT memory_reference, reward_score, confidence FROM experiences").fetchall()
99
- reinforcement: Dict[str, Dict[str, float]] = {}
100
- for row in reinf_rows:
101
- reference = str(row[0] or "")
102
- if not reference:
103
- continue
104
- current = reinforcement.setdefault(reference, {"reward_score": 0.0, "confidence": 0.0, "count": 0.0})
105
- current["reward_score"] += float(row[1] or 0.0)
106
- current["confidence"] += float(row[2] or 0.0)
107
- current["count"] += 1.0
108
- for current in reinforcement.values():
109
- count = max(1.0, float(current.get("count") or 1.0))
110
- current["reward_score"] = float(current.get("reward_score") or 0.0) / count
111
- current["confidence"] = float(current.get("confidence") or 0.0) / count
112
-
113
- semantic_scores: Dict[str, float] = {}
114
- if prompt.strip():
115
- for item in vector_index.search_memory(prompt, limit=max(limit * 6, 20)):
116
- source_type = item.get("source_type") or "knowledge"
117
- source_id = str(item.get("source_id") or "")
118
- if source_type in selected_categories and source_id:
119
- semantic_scores[f"{source_type}:{source_id}"] = float(item.get("score") or 0.0)
120
-
121
- def score_record(*, content: str, memory_ref: str, promo_conf: float, timestamp: str | None) -> tuple[float, Dict[str, float]]:
122
- keyword = _match_score(content, prompt)
123
- semantic = float(semantic_scores.get(memory_ref, 0.0))
124
- reinf = reinforcement.get(memory_ref, {})
125
- reinf_score = float(reinf.get("reward_score", 0.0)) * 0.35
126
- promo_score = float(promo_conf) * 0.2
127
- recency = _recency_score(timestamp)
128
- score = round((keyword * 0.45) + (semantic * 0.35) + reinf_score + promo_score + recency, 3)
129
- return score, {
130
- "keyword": round(keyword, 3),
131
- "semantic": round(semantic, 3),
132
- "reinforcement": round(reinf_score, 3),
133
- "promotion": round(promo_score, 3),
134
- "recency": round(recency, 3),
135
- }
136
-
137
- for table in selected_categories:
138
- candidates: Dict[str, Dict[str, Any]] = {}
139
- try:
140
- rows = conn.execute(
141
- f"SELECT id, timestamp, content, confidence, metadata_json FROM {table} ORDER BY id DESC LIMIT ?",
142
- (max(limit * 20, 50),),
143
- ).fetchall()
144
- except Exception:
145
- continue
146
- for row in rows:
147
- content = row["content"] if isinstance(row, dict) else row[2]
148
- mem_ref = f"{table}:{row[0]}"
149
- keyword = _match_score(content, prompt)
150
- semantic = float(semantic_scores.get(mem_ref, 0.0))
151
- if prompt.strip() and keyword <= 0.0 and semantic <= 0.0:
152
- continue
153
- promo_conf = row["confidence"] if isinstance(row, dict) else row[3]
154
- timestamp = row["timestamp"] if isinstance(row, dict) else row[1]
155
- raw_metadata = row["metadata_json"] if isinstance(row, dict) else row[4]
156
- metadata_payload = _parse_metadata(raw_metadata)
157
- memory_status, governance = _governance_state(metadata_payload)
158
- if memory_status in {"superseded", "duplicate"}:
159
- continue
160
- metadata = provenance.fetch_reference(mem_ref)
161
- score, signals = score_record(content=content, memory_ref=mem_ref, promo_conf=promo_conf, timestamp=timestamp)
162
- if memory_status == "contested":
163
- score = round(max(0.0, score - 0.15), 3)
164
- signals["contradiction_penalty"] = 0.15
165
- selected_because = max(signals, key=signals.get) if signals else "keyword"
166
- candidates[mem_ref] = {
167
- "content": content,
168
- "score": score,
169
- "memory_reference": mem_ref,
170
- "links": memory_links.get_memory_links(mem_ref),
171
- "provenance_preview": (metadata or {}).get("provenance_preview") or governance.get("provenance_preview") or provenance.preview_from_metadata((metadata or {}).get("metadata")),
172
- "retrieval_signals": signals,
173
- "selected_because": selected_because,
174
- "timestamp": timestamp,
175
- "memory_status": memory_status,
176
- "governance": governance,
177
- }
178
- results[table] = sorted(candidates.values(), key=lambda x: x["score"], reverse=True)[:limit]
179
-
180
- conn.close()
181
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_retrieval_rank_complete", status="ok")
182
- emit_event(state_store.reports_dir() / "brain_memory.log.jsonl", "brain_memory_retrieval_complete", status="ok")
183
- return results
184
-
185
-
186
- def retrieve_for_queries(
187
- queries: Iterable[str],
188
- *,
189
- limit: int = 5,
190
- categories: Iterable[str] | None = None,
191
- ) -> Dict[str, List[Dict[str, Any]]]:
192
- merged = _empty_results()
193
- seen_refs = {bucket: set() for bucket in MEMORY_BUCKETS}
194
- selected_categories = tuple(dict.fromkeys(category for category in (categories or MEMORY_BUCKETS) if category in MEMORY_BUCKETS))
195
- normalized_queries = [query.strip() for query in queries if isinstance(query, str) and query.strip()]
196
-
197
- if not normalized_queries:
198
- return retrieve("", limit=limit, categories=selected_categories)
199
-
200
- for query in normalized_queries:
201
- partial = retrieve(query, limit=limit, categories=selected_categories)
202
- for bucket in selected_categories:
203
- for item in partial.get(bucket, []):
204
- ref = item.get("memory_reference")
205
- if ref in seen_refs[bucket]:
206
- continue
207
- seen_refs[bucket].add(ref)
208
- merged[bucket].append(item)
209
-
210
- for bucket in selected_categories:
211
- merged[bucket] = sorted(merged[bucket], key=lambda x: x["score"], reverse=True)[:limit]
212
- return merged
3
+ from ocmemog.runtime.memory.retrieval import * # noqa: F401,F403
@@ -1,66 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, Dict, List
4
-
5
- from brain.runtime import state_store
6
- from brain.runtime.instrumentation import emit_event
7
- from brain.runtime.memory import embedding_engine, store, retrieval, freshness
8
-
9
- LOGFILE = state_store.reports_dir() / "brain_memory.log.jsonl"
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]
3
+ from ocmemog.runtime.memory.semantic_search import * # noqa: F401,F403