@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 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
  }
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simbimbo/memory-ocmemog",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "Advanced OpenClaw memory plugin with durable recall, transcript-backed continuity, and sidecar APIs",
5
5
  "license": "MIT",
6
6
  "repository": {