@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.
- package/CHANGELOG.md +16 -0
- package/README.md +83 -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 +310 -23
- package/ocmemog/sidecar/compat.py +50 -13
- package/ocmemog/sidecar/transcript_watcher.py +318 -240
- 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
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Dict, Tuple
|
|
4
|
-
import re
|
|
5
|
-
|
|
6
|
-
from brain.runtime import state_store
|
|
7
|
-
from brain.runtime.instrumentation import emit_event
|
|
8
|
-
from brain.runtime.memory import person_memory
|
|
9
|
-
|
|
10
|
-
LOGFILE = state_store.reports_dir() / "brain_memory.log.jsonl"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def extract_intro_candidate(text: str) -> str | None:
|
|
14
|
-
if not isinstance(text, str):
|
|
15
|
-
return None
|
|
16
|
-
cleaned = text.strip()
|
|
17
|
-
if not cleaned:
|
|
18
|
-
return None
|
|
19
|
-
lowered = cleaned.lower().strip(".!")
|
|
20
|
-
call_me_match = re.search(r"\byou can call me\s+([a-z][a-z\-']*)", lowered)
|
|
21
|
-
if call_me_match:
|
|
22
|
-
return call_me_match.group(1).strip(" .!").title()
|
|
23
|
-
direct_call_match = re.search(r"\bcall me\s+([a-z][a-z\-']*)", lowered)
|
|
24
|
-
if direct_call_match:
|
|
25
|
-
return direct_call_match.group(1).strip(" .!").title()
|
|
26
|
-
patterns = [
|
|
27
|
-
r"^my name is (.+)$",
|
|
28
|
-
r"^i am (.+)$",
|
|
29
|
-
r"^i'm (.+)$",
|
|
30
|
-
]
|
|
31
|
-
for pattern in patterns:
|
|
32
|
-
match = re.match(pattern, lowered)
|
|
33
|
-
if match:
|
|
34
|
-
candidate = match.group(1).split(" but ")[0].strip(" .!")
|
|
35
|
-
return candidate.title() if candidate else None
|
|
36
|
-
return None
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def extract_name_candidate(text: str) -> str | None:
|
|
40
|
-
candidate = extract_intro_candidate(text)
|
|
41
|
-
if candidate:
|
|
42
|
-
return candidate
|
|
43
|
-
if not isinstance(text, str):
|
|
44
|
-
return None
|
|
45
|
-
cleaned = text.strip()
|
|
46
|
-
if not cleaned:
|
|
47
|
-
return None
|
|
48
|
-
tokens = [token for token in cleaned.replace(".", "").split() if token]
|
|
49
|
-
if 1 <= len(tokens) <= 3:
|
|
50
|
-
return " ".join(tokens)
|
|
51
|
-
return None
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def extract_operator_name(text: str) -> str | None:
|
|
55
|
-
from brain.runtime import inference
|
|
56
|
-
|
|
57
|
-
llm_result = inference.parse_operator_name(text)
|
|
58
|
-
llm_name = llm_result.get("name") if isinstance(llm_result, dict) else ""
|
|
59
|
-
if isinstance(llm_name, str) and llm_name.strip():
|
|
60
|
-
return llm_name.strip()
|
|
61
|
-
return extract_name_candidate(text)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def resolve_interaction_person(metadata: Dict[str, str]) -> Tuple[Dict[str, object] | None, float, bool]:
|
|
65
|
-
person = None
|
|
66
|
-
confidence = 0.0
|
|
67
|
-
name_input = metadata.get("name") or ""
|
|
68
|
-
name_candidate = extract_name_candidate(name_input) if name_input else None
|
|
69
|
-
if name_candidate:
|
|
70
|
-
person = person_memory.get_person(name_candidate) or person_memory.create_person(name_candidate, name_candidate)
|
|
71
|
-
confidence = 0.7
|
|
72
|
-
if not person and metadata.get("email"):
|
|
73
|
-
person = person_memory.find_person_by_email(metadata["email"])
|
|
74
|
-
confidence = 0.6 if person else 0.0
|
|
75
|
-
if not person and metadata.get("phone"):
|
|
76
|
-
person = person_memory.find_person_by_phone(metadata["phone"])
|
|
77
|
-
confidence = 0.6 if person else 0.0
|
|
78
|
-
ask_name_required = confidence < 0.5
|
|
79
|
-
if person:
|
|
80
|
-
emit_event(LOGFILE, "brain_person_identity_resolved", status="ok", person_id=person.get("person_id"))
|
|
81
|
-
else:
|
|
82
|
-
emit_event(LOGFILE, "brain_person_identity_uncertain", status="ok")
|
|
83
|
-
return person, confidence, ask_name_required
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import sqlite3
|
|
5
|
-
import time
|
|
6
|
-
from typing import Dict, List
|
|
7
|
-
|
|
8
|
-
from brain.runtime import state_store
|
|
9
|
-
from brain.runtime.instrumentation import emit_event
|
|
10
|
-
|
|
11
|
-
LOGFILE = state_store.reports_dir() / "brain_memory.log.jsonl"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _connect() -> sqlite3.Connection:
|
|
15
|
-
path = state_store.data_dir() / "person_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 person_memory (
|
|
22
|
-
person_id TEXT PRIMARY KEY,
|
|
23
|
-
display_name TEXT,
|
|
24
|
-
aliases_json TEXT,
|
|
25
|
-
email_addresses_json TEXT,
|
|
26
|
-
phone_numbers_json TEXT,
|
|
27
|
-
interaction_count INTEGER DEFAULT 0,
|
|
28
|
-
trust_score REAL DEFAULT 0.5,
|
|
29
|
-
trust_level TEXT DEFAULT 'operator',
|
|
30
|
-
expertise_tags_json TEXT,
|
|
31
|
-
communication_style_json TEXT,
|
|
32
|
-
last_seen TEXT,
|
|
33
|
-
relationship_type TEXT,
|
|
34
|
-
notes_json TEXT,
|
|
35
|
-
created_at TEXT
|
|
36
|
-
)
|
|
37
|
-
"""
|
|
38
|
-
)
|
|
39
|
-
_ensure_columns(conn)
|
|
40
|
-
return conn
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _ensure_columns(conn: sqlite3.Connection) -> None:
|
|
44
|
-
existing = {row[1] for row in conn.execute("PRAGMA table_info(person_memory)")}
|
|
45
|
-
columns = {
|
|
46
|
-
"trust_level": "ALTER TABLE person_memory ADD COLUMN trust_level TEXT DEFAULT 'operator'",
|
|
47
|
-
"expertise_tags_json": "ALTER TABLE person_memory ADD COLUMN expertise_tags_json TEXT",
|
|
48
|
-
"communication_style_json": "ALTER TABLE person_memory ADD COLUMN communication_style_json TEXT",
|
|
49
|
-
"relationship_type": "ALTER TABLE person_memory ADD COLUMN relationship_type TEXT",
|
|
50
|
-
"notes_json": "ALTER TABLE person_memory ADD COLUMN notes_json TEXT",
|
|
51
|
-
"created_at": "ALTER TABLE person_memory ADD COLUMN created_at TEXT",
|
|
52
|
-
}
|
|
53
|
-
for name, ddl in columns.items():
|
|
54
|
-
if name not in existing:
|
|
55
|
-
conn.execute(ddl)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def create_person(person_id: str, display_name: str = "") -> Dict[str, object]:
|
|
59
|
-
conn = _connect()
|
|
60
|
-
conn.execute(
|
|
61
|
-
"""
|
|
62
|
-
INSERT OR IGNORE INTO person_memory (
|
|
63
|
-
person_id, display_name, aliases_json, email_addresses_json, phone_numbers_json,
|
|
64
|
-
interaction_count, trust_score, trust_level, expertise_tags_json, communication_style_json,
|
|
65
|
-
last_seen, relationship_type, notes_json
|
|
66
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
67
|
-
""",
|
|
68
|
-
(
|
|
69
|
-
person_id,
|
|
70
|
-
display_name,
|
|
71
|
-
json.dumps([]),
|
|
72
|
-
json.dumps([]),
|
|
73
|
-
json.dumps([]),
|
|
74
|
-
0,
|
|
75
|
-
0.5,
|
|
76
|
-
"operator",
|
|
77
|
-
json.dumps([]),
|
|
78
|
-
json.dumps({}),
|
|
79
|
-
time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
80
|
-
"",
|
|
81
|
-
json.dumps([]),
|
|
82
|
-
time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
83
|
-
),
|
|
84
|
-
)
|
|
85
|
-
conn.commit()
|
|
86
|
-
conn.close()
|
|
87
|
-
emit_event(LOGFILE, "brain_person_memory_created", status="ok", person_id=person_id)
|
|
88
|
-
return get_person(person_id) or {}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def get_person(person_id: str) -> Dict[str, object] | None:
|
|
92
|
-
conn = _connect()
|
|
93
|
-
row = conn.execute("SELECT * FROM person_memory WHERE person_id=?", (person_id,)).fetchone()
|
|
94
|
-
conn.close()
|
|
95
|
-
return dict(row) if row else None
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def update_person(person_id: str, updates: Dict[str, object]) -> None:
|
|
99
|
-
conn = _connect()
|
|
100
|
-
fields = []
|
|
101
|
-
values = []
|
|
102
|
-
for key, value in updates.items():
|
|
103
|
-
fields.append(f"{key}=?")
|
|
104
|
-
values.append(value)
|
|
105
|
-
if not fields:
|
|
106
|
-
conn.close()
|
|
107
|
-
return
|
|
108
|
-
values.append(person_id)
|
|
109
|
-
conn.execute(f"UPDATE person_memory SET {', '.join(fields)} WHERE person_id=?", values)
|
|
110
|
-
conn.commit()
|
|
111
|
-
conn.close()
|
|
112
|
-
emit_event(LOGFILE, "brain_person_memory_updated", status="ok", person_id=person_id)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _find_by_json(field: str, value: str) -> Dict[str, object] | None:
|
|
116
|
-
conn = _connect()
|
|
117
|
-
rows = conn.execute("SELECT * FROM person_memory").fetchall()
|
|
118
|
-
conn.close()
|
|
119
|
-
for row in rows:
|
|
120
|
-
payload = json.loads(row[field] or "[]")
|
|
121
|
-
if value in payload:
|
|
122
|
-
return dict(row)
|
|
123
|
-
return None
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def find_person_by_email(email: str) -> Dict[str, object] | None:
|
|
127
|
-
return _find_by_json("email_addresses_json", email)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def find_person_by_phone(phone: str) -> Dict[str, object] | None:
|
|
131
|
-
return _find_by_json("phone_numbers_json", phone)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def list_people(limit: int = 50) -> List[Dict[str, object]]:
|
|
135
|
-
conn = _connect()
|
|
136
|
-
rows = conn.execute("SELECT * FROM person_memory ORDER BY last_seen DESC LIMIT ?", (limit,)).fetchall()
|
|
137
|
-
conn.close()
|
|
138
|
-
return [dict(row) for row in rows]
|
|
@@ -1,67 +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
|
-
SENTIMENTS = {"positive", "neutral", "negative", "frustrated", "urgent", "excited"}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def classify_sentiment(text: str) -> str:
|
|
17
|
-
lowered = (text or "").lower()
|
|
18
|
-
if "urgent" in lowered:
|
|
19
|
-
return "urgent"
|
|
20
|
-
if "frustrated" in lowered or "angry" in lowered:
|
|
21
|
-
return "frustrated"
|
|
22
|
-
if "excited" in lowered or "great" in lowered:
|
|
23
|
-
return "excited"
|
|
24
|
-
if "bad" in lowered:
|
|
25
|
-
return "negative"
|
|
26
|
-
if "good" in lowered:
|
|
27
|
-
return "positive"
|
|
28
|
-
return "neutral"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _connect() -> sqlite3.Connection:
|
|
32
|
-
path = state_store.data_dir() / "sentiment_memory.db"
|
|
33
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
34
|
-
conn = sqlite3.connect(str(path))
|
|
35
|
-
conn.row_factory = sqlite3.Row
|
|
36
|
-
conn.execute(
|
|
37
|
-
"""
|
|
38
|
-
CREATE TABLE IF NOT EXISTS sentiment_memory (
|
|
39
|
-
person_id TEXT,
|
|
40
|
-
sentiment TEXT,
|
|
41
|
-
timestamp TEXT
|
|
42
|
-
)
|
|
43
|
-
"""
|
|
44
|
-
)
|
|
45
|
-
return conn
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def update_person_sentiment_baseline(person_id: str, sentiment: str) -> None:
|
|
49
|
-
sentiment = sentiment if sentiment in SENTIMENTS else "neutral"
|
|
50
|
-
conn = _connect()
|
|
51
|
-
conn.execute(
|
|
52
|
-
"INSERT INTO sentiment_memory (person_id, sentiment, timestamp) VALUES (?, ?, ?)",
|
|
53
|
-
(person_id, sentiment, time.strftime("%Y-%m-%d %H:%M:%S")),
|
|
54
|
-
)
|
|
55
|
-
conn.commit()
|
|
56
|
-
conn.close()
|
|
57
|
-
emit_event(LOGFILE, "brain_person_sentiment_updated", status="ok", person_id=person_id)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def list_sentiment(person_id: str, limit: int = 10) -> List[Dict[str, str]]:
|
|
61
|
-
conn = _connect()
|
|
62
|
-
rows = conn.execute(
|
|
63
|
-
"SELECT sentiment, timestamp FROM sentiment_memory WHERE person_id=? ORDER BY timestamp DESC LIMIT ?",
|
|
64
|
-
(person_id, limit),
|
|
65
|
-
).fetchall()
|
|
66
|
-
conn.close()
|
|
67
|
-
return [dict(row) for row in rows]
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
from typing import Dict, Any
|
|
5
|
-
|
|
6
|
-
from brain.runtime.instrumentation import emit_event
|
|
7
|
-
from brain.runtime import state_store
|
|
8
|
-
from brain.runtime.memory import store
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _ensure_table(conn) -> None:
|
|
12
|
-
conn.execute(
|
|
13
|
-
"""
|
|
14
|
-
CREATE TABLE IF NOT EXISTS tool_catalog (
|
|
15
|
-
tool_id TEXT PRIMARY KEY,
|
|
16
|
-
description TEXT,
|
|
17
|
-
permission_class TEXT,
|
|
18
|
-
capability_tags TEXT,
|
|
19
|
-
first_seen TEXT DEFAULT (datetime('now')),
|
|
20
|
-
last_used TEXT
|
|
21
|
-
)
|
|
22
|
-
"""
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def record_tool_metadata(metadata: Dict[str, Any]) -> None:
|
|
27
|
-
conn = store.connect()
|
|
28
|
-
_ensure_table(conn)
|
|
29
|
-
tool_id = metadata.get("tool_id")
|
|
30
|
-
description = metadata.get("description", "")
|
|
31
|
-
permission_class = metadata.get("permission_class", "restricted")
|
|
32
|
-
capability_tags = json.dumps(metadata.get("capability_tags", []) or [])
|
|
33
|
-
conn.execute(
|
|
34
|
-
"""
|
|
35
|
-
INSERT INTO tool_catalog (tool_id, description, permission_class, capability_tags)
|
|
36
|
-
VALUES (?, ?, ?, ?)
|
|
37
|
-
ON CONFLICT(tool_id) DO UPDATE SET
|
|
38
|
-
description=excluded.description,
|
|
39
|
-
permission_class=excluded.permission_class,
|
|
40
|
-
capability_tags=excluded.capability_tags
|
|
41
|
-
""",
|
|
42
|
-
(tool_id, description, permission_class, capability_tags),
|
|
43
|
-
)
|
|
44
|
-
conn.commit()
|
|
45
|
-
conn.close()
|
|
46
|
-
emit_event(
|
|
47
|
-
state_store.reports_dir() / "brain_memory.log.jsonl",
|
|
48
|
-
"brain_memory_tool_catalog_update",
|
|
49
|
-
status="ok",
|
|
50
|
-
tool_id=tool_id,
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def record_tool_usage(tool_id: str) -> None:
|
|
55
|
-
conn = store.connect()
|
|
56
|
-
_ensure_table(conn)
|
|
57
|
-
conn.execute(
|
|
58
|
-
"UPDATE tool_catalog SET last_used=datetime('now') WHERE tool_id=?",
|
|
59
|
-
(tool_id,),
|
|
60
|
-
)
|
|
61
|
-
conn.commit()
|
|
62
|
-
conn.close()
|
|
63
|
-
emit_event(
|
|
64
|
-
state_store.reports_dir() / "brain_memory.log.jsonl",
|
|
65
|
-
"brain_memory_tool_catalog_update",
|
|
66
|
-
status="ok",
|
|
67
|
-
tool_id=tool_id,
|
|
68
|
-
)
|