@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.
- package/CHANGELOG.md +30 -0
- package/README.md +85 -18
- package/brain/runtime/__init__.py +2 -12
- package/brain/runtime/config.py +1 -24
- package/brain/runtime/inference.py +1 -151
- package/brain/runtime/instrumentation.py +1 -15
- package/brain/runtime/memory/__init__.py +3 -13
- package/brain/runtime/memory/api.py +1 -1219
- package/brain/runtime/memory/candidate.py +1 -185
- package/brain/runtime/memory/conversation_state.py +1 -1823
- package/brain/runtime/memory/distill.py +1 -344
- package/brain/runtime/memory/embedding_engine.py +1 -92
- package/brain/runtime/memory/freshness.py +1 -112
- package/brain/runtime/memory/health.py +1 -40
- package/brain/runtime/memory/integrity.py +1 -186
- package/brain/runtime/memory/memory_consolidation.py +1 -58
- package/brain/runtime/memory/memory_links.py +1 -107
- package/brain/runtime/memory/memory_salience.py +1 -233
- package/brain/runtime/memory/memory_synthesis.py +1 -31
- package/brain/runtime/memory/memory_taxonomy.py +1 -33
- package/brain/runtime/memory/pondering_engine.py +1 -654
- package/brain/runtime/memory/promote.py +1 -277
- package/brain/runtime/memory/provenance.py +1 -406
- package/brain/runtime/memory/reinforcement.py +1 -71
- package/brain/runtime/memory/retrieval.py +1 -210
- package/brain/runtime/memory/semantic_search.py +1 -64
- package/brain/runtime/memory/store.py +1 -429
- package/brain/runtime/memory/unresolved_state.py +1 -91
- package/brain/runtime/memory/vector_index.py +1 -323
- package/brain/runtime/model_roles.py +1 -9
- package/brain/runtime/model_router.py +1 -22
- package/brain/runtime/providers.py +1 -66
- package/brain/runtime/security/redaction.py +1 -12
- package/brain/runtime/state_store.py +1 -23
- package/brain/runtime/storage_paths.py +1 -39
- package/docs/architecture/memory.md +20 -24
- package/docs/release-checklist.md +19 -6
- package/docs/usage.md +33 -17
- package/index.ts +8 -1
- package/ocmemog/__init__.py +11 -0
- package/ocmemog/doctor.py +1255 -0
- package/ocmemog/runtime/__init__.py +18 -0
- package/ocmemog/runtime/_compat_bridge.py +28 -0
- package/ocmemog/runtime/config.py +35 -0
- package/ocmemog/runtime/identity.py +115 -0
- package/ocmemog/runtime/inference.py +164 -0
- package/ocmemog/runtime/instrumentation.py +20 -0
- package/ocmemog/runtime/memory/__init__.py +91 -0
- package/ocmemog/runtime/memory/api.py +1431 -0
- package/ocmemog/runtime/memory/candidate.py +192 -0
- package/ocmemog/runtime/memory/conversation_state.py +1831 -0
- package/ocmemog/runtime/memory/distill.py +282 -0
- package/ocmemog/runtime/memory/embedding_engine.py +151 -0
- package/ocmemog/runtime/memory/freshness.py +114 -0
- package/ocmemog/runtime/memory/health.py +57 -0
- package/ocmemog/runtime/memory/integrity.py +208 -0
- package/ocmemog/runtime/memory/memory_consolidation.py +60 -0
- package/ocmemog/runtime/memory/memory_links.py +109 -0
- package/ocmemog/runtime/memory/memory_salience.py +235 -0
- package/ocmemog/runtime/memory/memory_synthesis.py +33 -0
- package/ocmemog/runtime/memory/memory_taxonomy.py +35 -0
- package/ocmemog/runtime/memory/pondering_engine.py +681 -0
- package/ocmemog/runtime/memory/promote.py +279 -0
- package/ocmemog/runtime/memory/provenance.py +408 -0
- package/ocmemog/runtime/memory/reinforcement.py +73 -0
- package/ocmemog/runtime/memory/retrieval.py +224 -0
- package/ocmemog/runtime/memory/semantic_search.py +66 -0
- package/ocmemog/runtime/memory/store.py +433 -0
- package/ocmemog/runtime/memory/unresolved_state.py +93 -0
- package/ocmemog/runtime/memory/vector_index.py +411 -0
- package/ocmemog/runtime/model_roles.py +16 -0
- package/ocmemog/runtime/model_router.py +29 -0
- package/ocmemog/runtime/providers.py +79 -0
- package/ocmemog/runtime/roles.py +92 -0
- package/ocmemog/runtime/security/__init__.py +8 -0
- package/ocmemog/runtime/security/redaction.py +17 -0
- package/ocmemog/runtime/state_store.py +34 -0
- package/ocmemog/runtime/storage_paths.py +70 -0
- package/ocmemog/sidecar/app.py +311 -23
- package/ocmemog/sidecar/compat.py +50 -13
- package/ocmemog/sidecar/transcript_watcher.py +391 -190
- package/openclaw.plugin.json +4 -0
- package/package.json +1 -1
- package/scripts/ocmemog-backfill-vectors.py +5 -3
- package/scripts/ocmemog-continuity-benchmark.py +1 -1
- package/scripts/ocmemog-demo.py +1 -1
- package/scripts/ocmemog-doctor.py +15 -0
- package/scripts/ocmemog-install.sh +29 -7
- package/scripts/ocmemog-integrated-proof.py +373 -0
- package/scripts/ocmemog-reindex-vectors.py +5 -3
- package/scripts/ocmemog-release-check.sh +330 -0
- package/scripts/ocmemog-sidecar.sh +4 -2
- package/scripts/ocmemog-test-rig.py +5 -3
- package/brain/runtime/memory/artifacts.py +0 -33
- package/brain/runtime/memory/context_builder.py +0 -112
- package/brain/runtime/memory/interaction_memory.py +0 -57
- package/brain/runtime/memory/memory_gate.py +0 -38
- package/brain/runtime/memory/memory_graph.py +0 -54
- package/brain/runtime/memory/person_identity.py +0 -83
- package/brain/runtime/memory/person_memory.py +0 -138
- package/brain/runtime/memory/sentiment_memory.py +0 -67
- 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
|
|
44
|
-
export
|
|
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("
|
|
156
|
-
os.environ.setdefault("
|
|
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
|
|
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]]
|