@simbimbo/memory-ocmemog 0.1.10 → 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 +30 -0
  2. package/README.md +85 -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 +311 -23
  80. package/ocmemog/sidecar/compat.py +50 -13
  81. package/ocmemog/sidecar/transcript_watcher.py +391 -190
  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,330 @@
1
+ #!/usr/bin/env bash
2
+ set -uo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ cd "$ROOT_DIR"
6
+
7
+ PYTHON_BIN="${OCMEMOG_PYTHON_BIN:-}"
8
+ if [[ -z "${PYTHON_BIN}" ]]; then
9
+ if [[ -x ".venv/bin/python3" ]]; then
10
+ PYTHON_BIN=".venv/bin/python3"
11
+ else
12
+ PYTHON_BIN="$(command -v python3)"
13
+ fi
14
+ fi
15
+ if [[ -z "${PYTHON_BIN}" ]] || ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
16
+ echo "A python executable could not be found. Set OCMEMOG_PYTHON_BIN to a valid interpreter path."
17
+ exit 1
18
+ fi
19
+
20
+ BROAD_TEST_FILES=(
21
+ tests/test_sidecar_routes.py
22
+ tests/test_regressions.py
23
+ tests/test_pondering_engine.py
24
+ tests/test_doctor.py
25
+ tests/test_governance_queue.py
26
+ tests/test_promotion_governance_integration.py
27
+ tests/test_hybrid_retrieval.py
28
+ )
29
+ LIVE_CHECK_URL="${OCMEMOG_RELEASE_LIVE_ENDPOINT:-http://127.0.0.1:17891}"
30
+ PROOF_REPORT_DIR="${OCMEMOG_RELEASE_PROOF_DIR:-${ROOT_DIR}/reports}"
31
+ PROOF_REPORT_FILE="${PROOF_REPORT_DIR}/release-gate-proof.json"
32
+ PROOF_LEGACY_ENDPOINT="${OCMEMOG_RELEASE_LEGACY_ENDPOINT:-}"
33
+ LIVE_STATE_DIR="$(mktemp -d -t ocmemog-release-live-XXXXXX)"
34
+ DOCTOR_STATE_DIR="$(mktemp -d -t ocmemog-release-doctor-XXXXXX)"
35
+ mkdir -p "$PROOF_REPORT_DIR"
36
+ trap 'rm -rf "$DOCTOR_STATE_DIR" "$LIVE_STATE_DIR"' EXIT
37
+
38
+ STATUS=0
39
+
40
+ run_step() {
41
+ local label="$1"
42
+ shift
43
+ echo
44
+ echo "[ocmemog-release-check] ${label}"
45
+ if "$@"; then
46
+ echo "[PASS] ${label}"
47
+ else
48
+ local rc=$?
49
+ STATUS=1
50
+ echo "[FAIL] ${label} (status=${rc})"
51
+ return $rc
52
+ fi
53
+ }
54
+
55
+ run_optional_step() {
56
+ local label="$1"
57
+ shift
58
+ echo
59
+ echo "[ocmemog-release-check] ${label}"
60
+ if "$@"; then
61
+ :
62
+ else
63
+ echo "[WARN] ${label}: command reported a non-blocking warning"
64
+ fi
65
+ }
66
+
67
+ run_step "Verifying shell script syntax" \
68
+ bash -n scripts/install-ocmemog.sh \
69
+ && bash -n scripts/ocmemog-install.sh
70
+
71
+ run_step "Checking installer command surfaces" \
72
+ ./scripts/install-ocmemog.sh --help \
73
+ && ./scripts/install-ocmemog.sh --dry-run
74
+
75
+ run_step "Running strict doctor checks against temporary state" \
76
+ "$PYTHON_BIN" scripts/ocmemog-doctor.py \
77
+ --json \
78
+ --strict \
79
+ --state-dir "$DOCTOR_STATE_DIR" \
80
+ --check runtime/imports \
81
+ --check state/path-writable \
82
+ --check sqlite/schema-access \
83
+ --check queue/health \
84
+ --check sidecar/env-toggles \
85
+ --check sidecar/app-import
86
+
87
+ run_optional_step "Running transcript-root diagnostics (non-blocking)" \
88
+ bash -c '
89
+ if "$0" scripts/ocmemog-doctor.py \
90
+ --json \
91
+ --state-dir "$1" \
92
+ --check sidecar/transcript-roots; then
93
+ :
94
+ else
95
+ rc=$?
96
+ if [[ "$rc" -eq 1 ]]; then
97
+ echo "transcript-root diagnostics reported warning."
98
+ elif [[ "$rc" -eq 2 ]]; then
99
+ echo "transcript-root diagnostics reported failure-level issue."
100
+ else
101
+ echo "transcript-root diagnostics exited with unexpected status ${rc}."
102
+ fi
103
+ fi
104
+ ' "${PYTHON_BIN}" "$DOCTOR_STATE_DIR"
105
+
106
+ run_optional_step "Running optional runtime probe (non-blocking warning)" \
107
+ bash -c '
108
+ if "$0" scripts/ocmemog-doctor.py --json --state-dir "$1" --check vector/runtime-probe; then
109
+ :
110
+ else
111
+ rc=$?
112
+ if [[ "$rc" -eq 1 ]]; then
113
+ echo "runtime probe reports warning-level status (sidecar may be unavailable in this environment)."
114
+ elif [[ "$rc" -eq 2 ]]; then
115
+ echo "runtime probe reports fail-level status (likely unavailable in this environment)."
116
+ else
117
+ echo "runtime probe exited with unexpected status ${rc}."
118
+ fi
119
+ fi
120
+ ' "${PYTHON_BIN}" "$DOCTOR_STATE_DIR"
121
+
122
+ run_step "Checking test dependencies for route tests" \
123
+ "$PYTHON_BIN" - <<'PY'
124
+ import importlib.util
125
+
126
+ missing = [
127
+ name
128
+ for name in ("pytest", "httpx")
129
+ if importlib.util.find_spec(name) is None
130
+ ]
131
+ if missing:
132
+ raise SystemExit("missing test dependencies: " + ", ".join(missing))
133
+ PY
134
+
135
+ run_step "Running broad regression subset" \
136
+ "$PYTHON_BIN" -m pytest -q "${BROAD_TEST_FILES[@]}"
137
+
138
+ run_step "Running contract-facing sidecar route tests" \
139
+ "$PYTHON_BIN" -m pytest -q tests/test_sidecar_routes.py
140
+
141
+ LIVE_CHECK_ENDPOINT="$LIVE_CHECK_URL"
142
+ export LIVE_CHECK_ENDPOINT
143
+ run_step "Running live /healthz, /memory/ingest and /memory/search smoke checks" \
144
+ "$PYTHON_BIN" - <<PY
145
+ import os
146
+ import json
147
+ import time
148
+ import uuid
149
+ import urllib.error
150
+ from urllib import request
151
+
152
+ endpoint = os.environ["LIVE_CHECK_ENDPOINT"].rstrip("/")
153
+
154
+ def post(path, payload, timeout=10):
155
+ req = request.Request(
156
+ f"{endpoint}{path}",
157
+ data=json.dumps(payload).encode("utf-8"),
158
+ method="POST",
159
+ )
160
+ req.add_header("Content-Type", "application/json")
161
+ with request.urlopen(req, timeout=timeout) as response:
162
+ payload = response.read().decode("utf-8")
163
+ return json.loads(payload)
164
+
165
+ def get(path, timeout=10):
166
+ req = request.Request(f"{endpoint}{path}")
167
+ with request.urlopen(req, timeout=timeout) as response:
168
+ payload = response.read().decode("utf-8")
169
+ return json.loads(payload)
170
+
171
+ def wait_ready(timeout=20):
172
+ deadline = time.time() + timeout
173
+ health_payload = {}
174
+ while time.time() < deadline:
175
+ try:
176
+ health = get("/healthz", timeout=3)
177
+ except Exception:
178
+ health = None
179
+ if isinstance(health, dict):
180
+ health_payload = health
181
+ if health.get("ok"):
182
+ if health.get("ready") is False:
183
+ print(f"[ocmemog-release-check] healthz degraded mode={health.get('mode')}: continuing smoke checks with warning")
184
+ return
185
+ return
186
+ # keep probing only for operational OK states
187
+ time.sleep(0.3)
188
+ if health_payload:
189
+ if not isinstance(health_payload, dict) or not health_payload.get("ok"):
190
+ raise SystemExit(f"healthz did not return ok: {health_payload}")
191
+ raise SystemExit(f"healthz did not become ready after {timeout}s (mode={health_payload.get('mode')})")
192
+ raise SystemExit("healthz did not become ready")
193
+
194
+ def drain_queue():
195
+ post("/memory/ingest_flush", {"limit": 0}, timeout=10)
196
+ deadline = time.time() + 10
197
+ while time.time() < deadline:
198
+ status = get("/memory/ingest_status", timeout=10)
199
+ if int(status.get("queueDepth", 0) or 0) <= 0:
200
+ return
201
+ time.sleep(0.25)
202
+ raise SystemExit("post-process queue did not drain")
203
+
204
+ wait_ready()
205
+
206
+ conversation_id = f"release-check-{uuid.uuid4().hex}"
207
+ session_id = f"release-check-session-{uuid.uuid4().hex}"
208
+ thread_id = f"release-check-thread-{uuid.uuid4().hex}"
209
+ token = f"release-check-token-{uuid.uuid4().hex}"
210
+
211
+ ingest = post(
212
+ "/memory/ingest",
213
+ {
214
+ "content": f"{token} for release gate verification",
215
+ "kind": "memory",
216
+ "memory_type": "knowledge",
217
+ "conversation_id": conversation_id,
218
+ "session_id": session_id,
219
+ "thread_id": thread_id,
220
+ },
221
+ timeout=20,
222
+ )
223
+ if not ingest.get("ok") or "reference" not in ingest:
224
+ raise SystemExit("memory ingest endpoint failed")
225
+ reference = str(ingest.get("reference"))
226
+
227
+ # /memory/ingest is synchronous for the core write; async post-processing may settle later.
228
+ search = post("/memory/search", {"query": token, "limit": 2}, timeout=20)
229
+ if not search.get("ok"):
230
+ raise SystemExit("memory search endpoint failed")
231
+ results = list(search.get("results") or [])
232
+ if not results:
233
+ raise SystemExit("memory search returned no results")
234
+ if len(results) > 2:
235
+ raise SystemExit("memory search returned unbounded results")
236
+ if not any(str(item.get("reference") or "") == reference for item in results):
237
+ raise SystemExit("memory search did not recall inserted memory reference")
238
+
239
+ get_response = post("/memory/get", {"reference": reference}, timeout=15)
240
+ if not get_response.get("ok"):
241
+ raise SystemExit("memory get endpoint failed")
242
+ if token not in str(get_response.get("memory", {}).get("content") or ""):
243
+ raise SystemExit("memory get response did not include ingested content")
244
+
245
+ hydrate = post(
246
+ "/conversation/hydrate",
247
+ {
248
+ "conversation_id": conversation_id,
249
+ "session_id": session_id,
250
+ "thread_id": thread_id,
251
+ "turns_limit": 2,
252
+ "memory_limit": 2,
253
+ },
254
+ timeout=20,
255
+ )
256
+ if not hydrate.get("ok"):
257
+ raise SystemExit("conversation hydrate endpoint failed")
258
+ if len(hydrate.get("linked_memories") or []) > 2:
259
+ raise SystemExit("conversation hydrate did not compact linked memories")
260
+ print(json.dumps({"health": True, "reference": reference, "search_count": len(results)}, sort_keys=True))
261
+ PY
262
+
263
+ run_step "Running integrated OpenClaw memory contract proof (fresh-state)" \
264
+ bash -lc '
265
+ set -e
266
+ cd "$1"
267
+ "$2" scripts/ocmemog-integrated-proof.py \
268
+ --start-sidecar \
269
+ --endpoint "$3" \
270
+ --state-dir "$4" \
271
+ --timeout 180 \
272
+ ${5:+--legacy-endpoint "$5"} \
273
+ >"$6" 2>"$6.stderr"
274
+ ' -- \
275
+ "$ROOT_DIR" "$PYTHON_BIN" "$LIVE_CHECK_URL" "$LIVE_STATE_DIR" "$PROOF_LEGACY_ENDPOINT" "$PROOF_REPORT_FILE"
276
+
277
+ run_step "Validating proof output" \
278
+ "$PYTHON_BIN" - <<PY
279
+ import json
280
+ from pathlib import Path
281
+
282
+ try:
283
+ payload = json.loads(Path("$PROOF_REPORT_FILE").read_text(encoding="utf-8"))
284
+ except FileNotFoundError:
285
+ raise SystemExit("proof output file was not produced")
286
+ except json.JSONDecodeError as exc:
287
+ raise SystemExit(f"proof output invalid JSON: {exc}")
288
+
289
+ if not payload.get("ingest_ok"):
290
+ raise SystemExit("proof ingest check failed")
291
+ if not payload.get("search_ok"):
292
+ raise SystemExit("proof search check failed")
293
+ if not payload.get("get_ok"):
294
+ raise SystemExit("proof get check failed")
295
+ if not payload.get("hydrate_ok"):
296
+ raise SystemExit("proof hydrate check failed")
297
+ if int(payload.get("search_count") or 0) > 2:
298
+ raise SystemExit("proof search_count must be capped")
299
+ if int(payload.get("linked_count") or 0) > 2:
300
+ raise SystemExit("proof linked_count must be compact")
301
+ if "reference" not in payload:
302
+ raise SystemExit("proof missing reference")
303
+ json.dumps(payload, sort_keys=True)
304
+ PY
305
+
306
+ run_optional_step "Checking npm pack --dry-run (non-blocking)" \
307
+ bash -c "cd \"$ROOT_DIR\" && npm pack --dry-run"
308
+
309
+ run_step "Running package and syntax proofs" \
310
+ "$PYTHON_BIN" - <<'PY'
311
+ import json
312
+ from pathlib import Path
313
+
314
+ json.loads(Path("package.json").read_text(encoding="utf-8"))
315
+
316
+ for candidate in sorted(Path("ocmemog").rglob("*.py")):
317
+ compile(candidate.read_text(encoding="utf-8"), str(candidate), "exec")
318
+
319
+ for candidate in sorted(Path("scripts").glob("*.py")):
320
+ compile(candidate.read_text(encoding="utf-8"), str(candidate), "exec")
321
+ PY
322
+
323
+ echo
324
+ if [[ "$STATUS" -ne 0 ]]; then
325
+ echo "[ocmemog-release-check] RELEASE CHECK FAILED"
326
+ exit 1
327
+ fi
328
+
329
+ echo "[ocmemog-release-check] RELEASE CHECK PASSED"
330
+ echo "[ocmemog-release-check] Proof report: $PROOF_REPORT_FILE"
@@ -40,8 +40,10 @@ export OCMEMOG_LOCAL_EMBED_MODEL="${OCMEMOG_LOCAL_EMBED_MODEL:-nomic-embed-text-
40
40
  export OCMEMOG_OLLAMA_MODEL="${OCMEMOG_OLLAMA_MODEL:-qwen2.5:7b}"
41
41
  export OCMEMOG_OLLAMA_EMBED_MODEL="${OCMEMOG_OLLAMA_EMBED_MODEL:-nomic-embed-text:latest}"
42
42
  export OCMEMOG_PONDER_MODEL="${OCMEMOG_PONDER_MODEL:-local-openai:qwen2.5-7b-instruct}"
43
- export BRAIN_EMBED_MODEL_PROVIDER="${BRAIN_EMBED_MODEL_PROVIDER:-local-openai}"
44
- export BRAIN_EMBED_MODEL_LOCAL="${BRAIN_EMBED_MODEL_LOCAL:-}"
43
+ export OCMEMOG_EMBED_MODEL_PROVIDER="${OCMEMOG_EMBED_MODEL_PROVIDER:-${BRAIN_EMBED_MODEL_PROVIDER:-local-openai}}"
44
+ export OCMEMOG_EMBED_MODEL_LOCAL="${OCMEMOG_EMBED_MODEL_LOCAL:-${BRAIN_EMBED_MODEL_LOCAL:-}}"
45
+ export BRAIN_EMBED_MODEL_PROVIDER="${BRAIN_EMBED_MODEL_PROVIDER:-${OCMEMOG_EMBED_MODEL_PROVIDER}}"
46
+ export BRAIN_EMBED_MODEL_LOCAL="${BRAIN_EMBED_MODEL_LOCAL:-${OCMEMOG_EMBED_MODEL_LOCAL}}"
45
47
 
46
48
  # battery-aware transcript watcher defaults
47
49
  export OCMEMOG_TRANSCRIPT_WATCHER="${OCMEMOG_TRANSCRIPT_WATCHER:-true}"
@@ -152,8 +152,10 @@ def _distill_batches(endpoint: str, target: int, batch_sizes: list[int], timeout
152
152
 
153
153
 
154
154
  def _enable_local_embeddings() -> None:
155
- os.environ.setdefault("BRAIN_EMBED_MODEL_LOCAL", "")
156
- os.environ.setdefault("BRAIN_EMBED_MODEL_PROVIDER", "local-openai")
155
+ os.environ.setdefault("OCMEMOG_EMBED_MODEL_LOCAL", "")
156
+ os.environ.setdefault("OCMEMOG_EMBED_MODEL_PROVIDER", "local-openai")
157
+ os.environ.setdefault("BRAIN_EMBED_MODEL_LOCAL", os.environ["OCMEMOG_EMBED_MODEL_LOCAL"])
158
+ os.environ.setdefault("BRAIN_EMBED_MODEL_PROVIDER", os.environ["OCMEMOG_EMBED_MODEL_PROVIDER"])
157
159
  os.environ.setdefault("OCMEMOG_LOCAL_EMBED_BASE_URL", "http://127.0.0.1:18081/v1")
158
160
  os.environ.setdefault("OCMEMOG_LOCAL_EMBED_MODEL", "nomic-embed-text-v1.5")
159
161
 
@@ -268,7 +270,7 @@ def main() -> int:
268
270
  ponder_summary = {}
269
271
  research_summary = {}
270
272
  try:
271
- from brain.runtime.memory import promote, store, pondering_engine, memory_synthesis, semantic_search
273
+ from ocmemog.runtime.memory import promote, store, pondering_engine, memory_synthesis, semantic_search
272
274
 
273
275
  conn = store.connect()
274
276
  rows = conn.execute(
@@ -1,33 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from pathlib import Path
5
- from typing import Dict, Any
6
-
7
- from brain.runtime import state_store
8
- from brain.runtime.memory import store
9
-
10
-
11
- def _artifact_dir() -> Path:
12
- path = state_store.memory_dir() / "artifacts"
13
- path.mkdir(parents=True, exist_ok=True)
14
- return path
15
-
16
-
17
- def store_artifact(artifact_id: str, content: bytes, metadata: Dict[str, Any]) -> Path:
18
- path = _artifact_dir() / f"{artifact_id}.bin"
19
- path.write_bytes(content)
20
- content_hash = str(hash(content))
21
- conn = store.connect()
22
- conn.execute(
23
- "INSERT INTO artifacts (artifact_id, artifact_type, source_path, content_hash, metadata) VALUES (?, ?, ?, ?, ?)",
24
- (artifact_id, metadata.get("artifact_type", "unknown"), metadata.get("source_path", ""), content_hash, json.dumps(metadata)),
25
- )
26
- conn.commit()
27
- conn.close()
28
- return path
29
-
30
-
31
- def load_artifact(artifact_id: str) -> bytes:
32
- path = _artifact_dir() / f"{artifact_id}.bin"
33
- return path.read_bytes()
@@ -1,112 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import os
5
- import re
6
- from typing import Any, Dict, List
7
-
8
- from brain.runtime.instrumentation import emit_event
9
- from brain.runtime import state_store, inference
10
- from brain.runtime.memory import retrieval
11
-
12
- LOGFILE = state_store.reports_dir() / "brain_memory.log.jsonl"
13
-
14
-
15
- def _heuristic_queries(prompt: str, limit: int = 3) -> List[str]:
16
- cleaned = re.sub(r"\s+", " ", prompt or "").strip()
17
- parts = re.split(r",| and | then | also ", cleaned)
18
- queries = []
19
- for part in parts:
20
- q = part.strip(" .")
21
- if len(q) >= 8 and q.lower() not in {cleaned.lower()}:
22
- queries.append(q)
23
- if cleaned and cleaned not in queries:
24
- queries.insert(0, cleaned)
25
- deduped: List[str] = []
26
- seen = set()
27
- for q in queries:
28
- key = q.lower()
29
- if key in seen:
30
- continue
31
- seen.add(key)
32
- deduped.append(q)
33
- if len(deduped) >= limit:
34
- break
35
- return deduped
36
-
37
-
38
- def _should_skip_query_grooming(prompt: str) -> bool:
39
- cleaned = re.sub(r"\s+", " ", prompt or "").strip()
40
- if not cleaned:
41
- return True
42
- if len(cleaned) <= 32 and ',' not in cleaned and ' and ' not in cleaned.lower():
43
- return True
44
- words = cleaned.split()
45
- if 1 <= len(words) <= 5 and all(len(w) >= 3 for w in words):
46
- return True
47
- return False
48
-
49
-
50
- def _groom_queries(prompt: str, limit: int = 3) -> List[str]:
51
- cleaned = re.sub(r"\s+", " ", prompt or "").strip()
52
- if not cleaned:
53
- return []
54
- if _should_skip_query_grooming(cleaned):
55
- return _heuristic_queries(cleaned, limit=limit)
56
- model = os.environ.get("OCMEMOG_PONDER_MODEL", "local-openai:qwen2.5-7b-instruct")
57
- ask = (
58
- "Rewrite this raw memory request into up to 3 short search queries. "
59
- "Return strict JSON as {\"queries\":[\"...\"]}. "
60
- "Prefer compact entity/topic phrases, not full sentences.\n\n"
61
- f"Request: {cleaned}\n"
62
- )
63
- try:
64
- result = inference.infer(ask, provider_name=model)
65
- except Exception:
66
- return _heuristic_queries(cleaned, limit=limit)
67
- if result.get("status") != "ok":
68
- return _heuristic_queries(cleaned, limit=limit)
69
- output = str(result.get("output") or "").strip()
70
- try:
71
- payload = json.loads(output)
72
- raw_queries = payload.get("queries") or []
73
- queries = [str(q).strip() for q in raw_queries if str(q).strip()]
74
- except Exception:
75
- queries = []
76
- cleaned_queries: List[str] = []
77
- seen = set()
78
- for q in queries:
79
- key = q.lower()
80
- if len(q) < 4 or key in seen:
81
- continue
82
- seen.add(key)
83
- cleaned_queries.append(q)
84
- if len(cleaned_queries) >= limit:
85
- break
86
- return cleaned_queries or _heuristic_queries(cleaned, limit=limit)
87
-
88
-
89
- def build_context(prompt: str, memory_queries: List[str] | None = None, limit: int = 5) -> Dict[str, Any]:
90
- emit_event(LOGFILE, "brain_memory_context_build_start", status="ok")
91
- queries = memory_queries or _groom_queries(prompt, limit=3)
92
- memories: List[Dict[str, Any]] = []
93
- seen: set[str] = set()
94
- for query in queries:
95
- for item in retrieval.retrieve_memories(query, limit=limit):
96
- ref = str(item.get("reference") or item.get("id") or "")
97
- if ref and ref in seen:
98
- continue
99
- if ref:
100
- seen.add(ref)
101
- memories.append(item)
102
- if len(memories) >= limit:
103
- break
104
- if len(memories) >= limit:
105
- break
106
-
107
- emit_event(LOGFILE, "brain_memory_context_build_complete", status="ok", item_count=len(memories), query_count=len(queries))
108
- return {
109
- "prompt": prompt,
110
- "queries": queries,
111
- "memories": memories,
112
- }
@@ -1,57 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import sqlite3
4
- import time
5
- from typing import Dict, List
6
-
7
- from brain.runtime import state_store
8
- from brain.runtime.instrumentation import emit_event
9
- from brain.runtime.memory import person_memory
10
-
11
- LOGFILE = state_store.reports_dir() / "brain_memory.log.jsonl"
12
-
13
-
14
- def _connect() -> sqlite3.Connection:
15
- path = state_store.data_dir() / "interaction_memory.db"
16
- path.parent.mkdir(parents=True, exist_ok=True)
17
- conn = sqlite3.connect(str(path))
18
- conn.row_factory = sqlite3.Row
19
- conn.execute(
20
- """
21
- CREATE TABLE IF NOT EXISTS interaction_memory (
22
- interaction_id INTEGER PRIMARY KEY AUTOINCREMENT,
23
- person_id TEXT,
24
- timestamp TEXT,
25
- channel TEXT,
26
- thread_id TEXT,
27
- sentiment TEXT,
28
- outcome TEXT
29
- )
30
- """
31
- )
32
- return conn
33
-
34
-
35
- def record_interaction(person_id: str, channel: str, thread_id: str, sentiment: str, outcome: str) -> None:
36
- conn = _connect()
37
- conn.execute(
38
- "INSERT INTO interaction_memory (person_id, timestamp, channel, thread_id, sentiment, outcome) VALUES (?, ?, ?, ?, ?, ?)",
39
- (person_id, time.strftime("%Y-%m-%d %H:%M:%S"), channel, thread_id, sentiment, outcome[:80]),
40
- )
41
- conn.commit()
42
- conn.close()
43
- person_memory.update_person(
44
- person_id,
45
- {"interaction_count": (person_memory.get_person(person_id) or {}).get("interaction_count", 0) + 1, "last_seen": time.strftime("%Y-%m-%d %H:%M:%S")},
46
- )
47
- emit_event(LOGFILE, "brain_person_interaction_recorded", status="ok", person_id=person_id)
48
-
49
-
50
- def get_recent_interactions(person_id: str, limit: int = 10) -> List[Dict[str, object]]:
51
- conn = _connect()
52
- rows = conn.execute(
53
- "SELECT timestamp, channel, thread_id, sentiment, outcome FROM interaction_memory WHERE person_id=? ORDER BY timestamp DESC LIMIT ?",
54
- (person_id, limit),
55
- ).fetchall()
56
- conn.close()
57
- return [dict(row) for row in rows]
@@ -1,38 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- from typing import Dict
5
-
6
- from brain.runtime import state_store
7
- from brain.runtime.instrumentation import emit_event
8
-
9
- LOGFILE = state_store.reports_dir() / "brain_memory.log.jsonl"
10
-
11
- DIRECT_THRESHOLD = float(os.environ.get("BRAIN_MEMORY_GATE_DIRECT", 1.5))
12
- ASSIST_THRESHOLD = float(os.environ.get("BRAIN_MEMORY_GATE_ASSIST", 0.8))
13
-
14
-
15
- def decide_gate(result: Dict[str, float]) -> Dict[str, float | str]:
16
- similarity = float(result.get("similarity", result.get("score", 0.0)))
17
- reinforcement = float(result.get("reinforcement_weight", 0.0))
18
- freshness = float(result.get("freshness", 0.0))
19
- promotion = float(result.get("promotion_confidence", 0.0))
20
- score = similarity + reinforcement + freshness + promotion
21
- if score >= DIRECT_THRESHOLD:
22
- decision = "memory_direct"
23
- elif score >= ASSIST_THRESHOLD:
24
- decision = "memory_assisted"
25
- else:
26
- decision = "model_escalation"
27
- payload = {
28
- "decision": decision,
29
- "score": round(score, 3),
30
- "similarity": similarity,
31
- "reinforcement_weight": reinforcement,
32
- "freshness": freshness,
33
- "promotion_confidence": promotion,
34
- "salience_score": float(result.get("salience_score", 0.0)),
35
- }
36
- emit_event(LOGFILE, "brain_memory_gate_decision", status="ok", decision=decision, score=round(score, 3))
37
- emit_event(LOGFILE, "brain_memory_gate_score", status="ok", score=round(score, 3))
38
- return payload
@@ -1,54 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import sqlite3
4
- from typing import Dict, List
5
-
6
- from brain.runtime import state_store
7
- from brain.runtime.instrumentation import emit_event
8
-
9
- LOGFILE = state_store.reports_dir() / "brain_memory.log.jsonl"
10
-
11
-
12
- def _connect() -> sqlite3.Connection:
13
- path = state_store.data_dir() / "memory_graph.db"
14
- path.parent.mkdir(parents=True, exist_ok=True)
15
- conn = sqlite3.connect(str(path))
16
- conn.row_factory = sqlite3.Row
17
- conn.execute(
18
- """
19
- CREATE TABLE IF NOT EXISTS memory_graph (
20
- source_reference TEXT NOT NULL,
21
- edge_type TEXT NOT NULL,
22
- target_reference TEXT NOT NULL,
23
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
24
- UNIQUE(source_reference, edge_type, target_reference)
25
- )
26
- """
27
- )
28
- return conn
29
-
30
-
31
- def add_memory_edge(source_reference: str, edge_type: str, target_reference: str) -> None:
32
- conn = _connect()
33
- conn.execute(
34
- "INSERT OR IGNORE INTO memory_graph (source_reference, edge_type, target_reference) VALUES (?, ?, ?)",
35
- (source_reference, edge_type, target_reference),
36
- )
37
- conn.commit()
38
- conn.close()
39
- emit_event(LOGFILE, "brain_memory_graph_edge_created", status="ok", edge_type=edge_type)
40
-
41
-
42
- def get_neighbors(source_reference: str) -> List[Dict[str, str]]:
43
- conn = _connect()
44
- rows = conn.execute(
45
- "SELECT edge_type, target_reference FROM memory_graph WHERE source_reference=?",
46
- (source_reference,),
47
- ).fetchall()
48
- conn.close()
49
- return [{"edge_type": row[0], "target_reference": row[1]} for row in rows]
50
-
51
-
52
- def get_cluster(source_reference: str, limit: int = 5) -> List[str]:
53
- neighbors = get_neighbors(source_reference)
54
- return [item["target_reference"] for item in neighbors[:limit]]