@simbimbo/memory-ocmemog 0.1.19 → 0.1.20
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 +13 -0
- package/ocmemog/runtime/memory/api.py +81 -0
- package/ocmemog/runtime/memory/retrieval.py +48 -2
- package/ocmemog/sidecar/app.py +30 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.20 — 2026-03-29
|
|
6
|
+
|
|
7
|
+
Operational-artifact canonicalization, dead-lane retrieval hardening, and rehydratable-memory recall fixes.
|
|
8
|
+
|
|
9
|
+
### Highlights
|
|
10
|
+
- added first-class operational artifact canonicalization with idempotent upsert semantics for durable canonical memories keyed by artifact identity
|
|
11
|
+
- added a sidecar API route to canonicalize operational artifacts explicitly, including aliases, ownership, status, kind, and provenance/source references
|
|
12
|
+
- taught retrieval to recognize operational-artifact / dead-lane queries and strongly reward exact operational literals such as cron/job names and CLI module tokens
|
|
13
|
+
- reduced transcript/reflection noise for operational-artifact queries so canonical memories win over repeated run logs and scattered debug fragments
|
|
14
|
+
- added transcript claim-search as a bounded fallback surface for exact prior-claim recovery, alongside larger `/memory/context` transcript windows with provenance-anchor fallback
|
|
15
|
+
- added focused regression coverage for operational artifact canonicalization, claim-search/memory-context rehydration, and canonical top-answer retrieval behavior
|
|
16
|
+
- validated the architecture fix with a targeted release gate: `30 passed`
|
|
17
|
+
|
|
5
18
|
## 0.1.19 — 2026-03-29
|
|
6
19
|
|
|
7
20
|
Hydrate/resume hardening, unresolved-state main-DB consolidation, and retrieval/rehydration source-of-truth completion.
|
|
@@ -789,6 +789,87 @@ def find_contradiction_candidates(
|
|
|
789
789
|
return top
|
|
790
790
|
|
|
791
791
|
|
|
792
|
+
def canonicalize_operational_artifact(
|
|
793
|
+
*,
|
|
794
|
+
key: str,
|
|
795
|
+
summary: str,
|
|
796
|
+
aliases: Optional[List[str]] = None,
|
|
797
|
+
status: str = "active",
|
|
798
|
+
owner: Optional[str] = None,
|
|
799
|
+
artifact_kind: Optional[str] = None,
|
|
800
|
+
source_references: Optional[List[str]] = None,
|
|
801
|
+
source_labels: Optional[List[str]] = None,
|
|
802
|
+
) -> Dict[str, Any]:
|
|
803
|
+
normalized_key = str(key or "").strip().lower()
|
|
804
|
+
if not normalized_key:
|
|
805
|
+
return {"ok": False, "error": "missing_key"}
|
|
806
|
+
|
|
807
|
+
aliases = [str(item).strip() for item in (aliases or []) if str(item).strip()]
|
|
808
|
+
source_references = [str(item).strip() for item in (source_references or []) if str(item).strip()]
|
|
809
|
+
source_labels = [str(item).strip() for item in (source_labels or []) if str(item).strip()]
|
|
810
|
+
|
|
811
|
+
conn = store.connect()
|
|
812
|
+
existing_reference: Optional[str] = None
|
|
813
|
+
existing_row: Optional[Dict[str, Any]] = None
|
|
814
|
+
try:
|
|
815
|
+
rows = conn.execute(
|
|
816
|
+
"SELECT id, content, metadata_json FROM knowledge ORDER BY id DESC LIMIT 500"
|
|
817
|
+
).fetchall()
|
|
818
|
+
for row in rows:
|
|
819
|
+
memory_id = int(row["id"] if isinstance(row, dict) else row[0])
|
|
820
|
+
content = str(row["content"] if isinstance(row, dict) else row[1] or "")
|
|
821
|
+
raw_metadata = row["metadata_json"] if isinstance(row, dict) else row[2]
|
|
822
|
+
metadata = json.loads(raw_metadata or "{}")
|
|
823
|
+
prov = metadata.get("provenance") if isinstance(metadata.get("provenance"), dict) else {}
|
|
824
|
+
artifact_key = str(prov.get("artifact_key") or metadata.get("artifact_key") or "").strip().lower()
|
|
825
|
+
haystack = f"{content}\n{json.dumps(metadata, ensure_ascii=False)}".lower()
|
|
826
|
+
if artifact_key == normalized_key or normalized_key in haystack:
|
|
827
|
+
existing_reference = f"knowledge:{memory_id}"
|
|
828
|
+
existing_row = {"content": content, "metadata": metadata}
|
|
829
|
+
break
|
|
830
|
+
finally:
|
|
831
|
+
conn.close()
|
|
832
|
+
|
|
833
|
+
metadata = provenance.normalize_metadata({
|
|
834
|
+
"artifact_key": normalized_key,
|
|
835
|
+
"artifact_aliases": aliases,
|
|
836
|
+
"artifact_kind": artifact_kind or "operational_artifact",
|
|
837
|
+
"owner": owner or "openclaw",
|
|
838
|
+
"memory_status": status,
|
|
839
|
+
"canonical": True,
|
|
840
|
+
"source_references": source_references,
|
|
841
|
+
"source_labels": list(dict.fromkeys(source_labels + ["canonical-operational-artifact"])),
|
|
842
|
+
"derived_via": "operational_artifact_canonicalize",
|
|
843
|
+
}, source="operational_artifact")
|
|
844
|
+
|
|
845
|
+
if existing_reference:
|
|
846
|
+
provenance.force_update_memory_metadata(existing_reference, metadata)
|
|
847
|
+
parsed = _parse_memory_reference(existing_reference)
|
|
848
|
+
if parsed:
|
|
849
|
+
table, identifier = parsed
|
|
850
|
+
conn = store.connect()
|
|
851
|
+
try:
|
|
852
|
+
conn.execute(
|
|
853
|
+
f"UPDATE {table} SET content=?, metadata_json=? WHERE id=?",
|
|
854
|
+
(summary, json.dumps(provenance.normalize_metadata({**metadata, **(existing_row.get('metadata') if existing_row else {})}, source='operational_artifact'), ensure_ascii=False), int(identifier)),
|
|
855
|
+
)
|
|
856
|
+
conn.commit()
|
|
857
|
+
finally:
|
|
858
|
+
conn.close()
|
|
859
|
+
return {"ok": True, "reference": existing_reference, "created": False}
|
|
860
|
+
|
|
861
|
+
memory_id = store_memory(
|
|
862
|
+
"knowledge",
|
|
863
|
+
summary,
|
|
864
|
+
source="operational_artifact",
|
|
865
|
+
metadata=metadata,
|
|
866
|
+
post_process=True,
|
|
867
|
+
)
|
|
868
|
+
reference = f"knowledge:{memory_id}"
|
|
869
|
+
provenance.force_update_memory_metadata(reference, {"canonical_reference": reference, "memory_status": status, "artifact_key": normalized_key})
|
|
870
|
+
return {"ok": True, "reference": reference, "created": True}
|
|
871
|
+
|
|
872
|
+
|
|
792
873
|
def mark_memory_relationship(
|
|
793
874
|
reference: str,
|
|
794
875
|
*,
|
|
@@ -98,6 +98,35 @@ def _recency_score(timestamp: str | None) -> float:
|
|
|
98
98
|
return 0.0
|
|
99
99
|
|
|
100
100
|
|
|
101
|
+
def _operational_literal_bonus(prompt: str, metadata: Dict[str, Any], content: str) -> float:
|
|
102
|
+
lowered_prompt = str(prompt or '').lower()
|
|
103
|
+
lowered_text = f"{metadata} {content}".lower()
|
|
104
|
+
bonus = 0.0
|
|
105
|
+
strong_literals = [
|
|
106
|
+
token for token in (
|
|
107
|
+
'provider-monitor',
|
|
108
|
+
'provider monitor',
|
|
109
|
+
'brain.cli provider-monitor',
|
|
110
|
+
'cron job',
|
|
111
|
+
'auto-assign',
|
|
112
|
+
'lan discovery',
|
|
113
|
+
'auto discovery',
|
|
114
|
+
'autodiscovery',
|
|
115
|
+
)
|
|
116
|
+
if token in lowered_prompt and token in lowered_text
|
|
117
|
+
]
|
|
118
|
+
if strong_literals:
|
|
119
|
+
bonus += 0.45
|
|
120
|
+
query_literals = [token for token in _tokenize(lowered_prompt) if len(token) >= 6]
|
|
121
|
+
matching_literals = [token for token in query_literals if token in lowered_text]
|
|
122
|
+
if matching_literals:
|
|
123
|
+
bonus += min(0.28, 0.07 * len(set(matching_literals)))
|
|
124
|
+
if any(marker in lowered_prompt for marker in ('dead lane', 'stale automation', 'why does this exist', 'what is this old job')):
|
|
125
|
+
if any(marker in lowered_text for marker in ('provider-monitor', 'cron', 'disabled', 'stale', 'dead lane', 'auto-assign', 'lan discovery')):
|
|
126
|
+
bonus += 0.22
|
|
127
|
+
return round(min(0.75, bonus), 3)
|
|
128
|
+
|
|
129
|
+
|
|
101
130
|
MEMORY_BUCKETS: Tuple[str, ...] = tuple(store.MEMORY_TABLES)
|
|
102
131
|
|
|
103
132
|
_PROCEDURAL_QUERY_MARKERS: Tuple[str, ...] = (
|
|
@@ -194,6 +223,14 @@ def _source_authority_bonus(bucket: str, metadata: Dict[str, Any], content: str,
|
|
|
194
223
|
if is_policy_doc or is_audit_doc:
|
|
195
224
|
bonus -= 0.18
|
|
196
225
|
|
|
226
|
+
if qtype == "operational_artifact":
|
|
227
|
+
if bucket == "reflections" and is_transcript:
|
|
228
|
+
bonus -= 0.38
|
|
229
|
+
if bucket in {"knowledge", "directives"} and (is_canonical_file or "canonical" in text or "deprecated" in text or "disabled" in text):
|
|
230
|
+
bonus += 0.36
|
|
231
|
+
if is_policy_doc or is_audit_doc:
|
|
232
|
+
bonus -= 0.12
|
|
233
|
+
|
|
197
234
|
if bucket == "runbooks":
|
|
198
235
|
if "readme.md" in origin_source or "repos/openclaw/readme.md" in origin_source or "start with the docs index" in text:
|
|
199
236
|
bonus -= 0.55
|
|
@@ -209,6 +246,8 @@ def _doc_type(metadata: Dict[str, Any], bucket: str, content: str) -> str:
|
|
|
209
246
|
origin_source = str(prov.get("origin_source") or metadata.get("source_path") or "").lower()
|
|
210
247
|
source_label = str(metadata.get("source_label") or "").lower()
|
|
211
248
|
text = f"{metadata} {content}".lower()
|
|
249
|
+
if any(marker in text for marker in ("provider-monitor", "provider monitor", "auto-assign", "lan discovery", "auto discovery", "autodiscovery", "cron job", "brain.cli provider-monitor")):
|
|
250
|
+
return "operational_artifact"
|
|
212
251
|
if "canonical-openclaw-upgrade-runbook" in source_label or "/docs/runbooks/" in origin_source or "openclaw-upgrade-validation-runbook.md" in origin_source:
|
|
213
252
|
return "runbook"
|
|
214
253
|
if "user-preferences-source-of-truth" in origin_source or "gpt 5.2 codex (ultra)" in text or "never send authentication, challenge, or verification prompts" in text:
|
|
@@ -299,6 +338,8 @@ def _query_type(prompt: str) -> str:
|
|
|
299
338
|
lowered = str(prompt or "").strip().lower()
|
|
300
339
|
if not lowered:
|
|
301
340
|
return "generic"
|
|
341
|
+
if any(marker in lowered for marker in ("provider-monitor", "provider monitor", "dead lane", "stale automation", "old cron", "cron job", "auto-discovery", "autodiscovery", "lan discovery", "auto-assign", "what is this old job", "why does this exist")):
|
|
342
|
+
return "operational_artifact"
|
|
302
343
|
if "policy" in lowered or "rule" in lowered or "anti-rediscovery" in lowered:
|
|
303
344
|
return "policy"
|
|
304
345
|
if "what launches" in lowered or "runtime authority" in lowered or "authoritative source tree" in lowered or "health endpoint" in lowered:
|
|
@@ -320,7 +361,7 @@ def _doc_type_bonus(prompt: str, metadata: Dict[str, Any], bucket: str, content:
|
|
|
320
361
|
if qtype == "generic":
|
|
321
362
|
return 0.0
|
|
322
363
|
if qtype == dtype:
|
|
323
|
-
return 0.35
|
|
364
|
+
return 0.35 if qtype != "operational_artifact" else 0.55
|
|
324
365
|
compatible = {
|
|
325
366
|
("runbook", "authority_map"): -0.16,
|
|
326
367
|
("runbook", "policy"): -0.28,
|
|
@@ -332,6 +373,9 @@ def _doc_type_bonus(prompt: str, metadata: Dict[str, Any], bucket: str, content:
|
|
|
332
373
|
("authority_map", "audit"): -0.10,
|
|
333
374
|
("continuity", "audit"): -0.08,
|
|
334
375
|
("user_preference", "policy"): -0.06,
|
|
376
|
+
("operational_artifact", "policy"): -0.22,
|
|
377
|
+
("operational_artifact", "audit"): -0.18,
|
|
378
|
+
("operational_artifact", "authority_map"): -0.10,
|
|
335
379
|
}
|
|
336
380
|
if (qtype, dtype) in compatible:
|
|
337
381
|
return compatible[(qtype, dtype)]
|
|
@@ -636,9 +680,10 @@ def retrieve(
|
|
|
636
680
|
intent_bonus = _bucket_intent_bonus(bucket, prompt, metadata_payload)
|
|
637
681
|
authority_bonus = _source_authority_bonus(bucket, metadata_payload, content, prompt)
|
|
638
682
|
doc_type_bonus = _doc_type_bonus(prompt, metadata_payload, bucket, content)
|
|
683
|
+
operational_literal_bonus = _operational_literal_bonus(prompt, metadata_payload, content)
|
|
639
684
|
family_penalty = _artifact_family_penalty(prompt, metadata_payload, bucket, content)
|
|
640
685
|
derivative_penalty = _derivative_penalty(bucket, metadata_payload, content, _governance_summary(_governance_state(metadata_payload)[1]))
|
|
641
|
-
score = round((keyword * 0.45) + (semantic * 0.35) + reinf_score + promo_score + recency + lane_bonus + intent_bonus + authority_bonus + doc_type_bonus + family_penalty + derivative_penalty, 3)
|
|
686
|
+
score = round((keyword * 0.45) + (semantic * 0.35) + reinf_score + promo_score + recency + lane_bonus + intent_bonus + authority_bonus + doc_type_bonus + operational_literal_bonus + family_penalty + derivative_penalty, 3)
|
|
642
687
|
return score, {
|
|
643
688
|
"keyword": round(keyword, 3),
|
|
644
689
|
"semantic": round(semantic, 3),
|
|
@@ -653,6 +698,7 @@ def retrieve(
|
|
|
653
698
|
"intent_bonus": round(intent_bonus, 3),
|
|
654
699
|
"authority_bonus": round(authority_bonus, 3),
|
|
655
700
|
"doc_type_bonus": round(doc_type_bonus, 3),
|
|
701
|
+
"operational_literal_bonus": round(operational_literal_bonus, 3),
|
|
656
702
|
"family_penalty": round(family_penalty, 3),
|
|
657
703
|
"derivative_penalty": round(derivative_penalty, 3),
|
|
658
704
|
}
|
package/ocmemog/sidecar/app.py
CHANGED
|
@@ -693,6 +693,17 @@ class TranscriptClaimSearchRequest(BaseModel):
|
|
|
693
693
|
limit: int = Field(default=5, ge=1, le=20)
|
|
694
694
|
|
|
695
695
|
|
|
696
|
+
class OperationalArtifactCanonicalizeRequest(BaseModel):
|
|
697
|
+
key: str
|
|
698
|
+
summary: str
|
|
699
|
+
aliases: List[str] = Field(default_factory=list)
|
|
700
|
+
status: str = "active"
|
|
701
|
+
owner: Optional[str] = None
|
|
702
|
+
artifact_kind: Optional[str] = None
|
|
703
|
+
source_references: List[str] = Field(default_factory=list)
|
|
704
|
+
source_labels: List[str] = Field(default_factory=list)
|
|
705
|
+
|
|
706
|
+
|
|
696
707
|
class RecentRequest(BaseModel):
|
|
697
708
|
categories: Optional[List[str]] = Field(default=None, description="Filter by memory categories")
|
|
698
709
|
limit: int = Field(default=12, ge=1, le=100, description="Maximum items per category")
|
|
@@ -1900,6 +1911,25 @@ def transcript_claim_search(request: TranscriptClaimSearchRequest) -> dict[str,
|
|
|
1900
1911
|
}
|
|
1901
1912
|
|
|
1902
1913
|
|
|
1914
|
+
@app.post("/memory/canonicalize_operational_artifact")
|
|
1915
|
+
def canonicalize_operational_artifact(request: OperationalArtifactCanonicalizeRequest) -> dict[str, Any]:
|
|
1916
|
+
runtime = _runtime_payload()
|
|
1917
|
+
result = api.canonicalize_operational_artifact(
|
|
1918
|
+
key=request.key,
|
|
1919
|
+
summary=request.summary,
|
|
1920
|
+
aliases=request.aliases,
|
|
1921
|
+
status=request.status,
|
|
1922
|
+
owner=request.owner,
|
|
1923
|
+
artifact_kind=request.artifact_kind,
|
|
1924
|
+
source_references=request.source_references,
|
|
1925
|
+
source_labels=request.source_labels,
|
|
1926
|
+
)
|
|
1927
|
+
return {
|
|
1928
|
+
**result,
|
|
1929
|
+
**runtime,
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
|
|
1903
1933
|
@app.post("/memory/recent")
|
|
1904
1934
|
def memory_recent(request: RecentRequest) -> dict[str, Any]:
|
|
1905
1935
|
runtime = _runtime_payload()
|
package/package.json
CHANGED