@remnic/plugin-openclaw 1.0.54 → 9.3.517

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 (84) hide show
  1. package/dist/index.js +510 -2106
  2. package/openclaw.plugin.json +3 -3
  3. package/package.json +4 -3
  4. package/scripts/faiss_index.py +141 -29
  5. package/dist/calibration-RKL2LRW4.js +0 -250
  6. package/dist/capsule-cli-EHZPMXBC.js +0 -33
  7. package/dist/capsule-crypto-JS67OSWM.js +0 -17
  8. package/dist/capsule-export-DX53CPIT.js +0 -17
  9. package/dist/capsule-import-4OXCPHOT.js +0 -16
  10. package/dist/capsule-merge-25AUN33Q.js +0 -195
  11. package/dist/causal-chain-BVTOWZKC.js +0 -23
  12. package/dist/causal-consolidation-IAIX53NV.js +0 -233
  13. package/dist/causal-retrieval-XAP6QKHZ.js +0 -182
  14. package/dist/causal-trajectory-graph-ZWQWZ7N5.js +0 -59
  15. package/dist/chunk-25J4PXDH.js +0 -30
  16. package/dist/chunk-2RQU6GVD.js +0 -453
  17. package/dist/chunk-3A5ELHTT.js +0 -61
  18. package/dist/chunk-3G7FAF6S.js +0 -60
  19. package/dist/chunk-3IKMUNW5.js +0 -177
  20. package/dist/chunk-4XDQ3KEC.js +0 -4048
  21. package/dist/chunk-5ZW5XJQ6.js +0 -128
  22. package/dist/chunk-6IWEAUN6.js +0 -148
  23. package/dist/chunk-7JOLBJJ5.js +0 -874
  24. package/dist/chunk-AJECKGY2.js +0 -896
  25. package/dist/chunk-B52XADV3.js +0 -244
  26. package/dist/chunk-BQZ7JACP.js +0 -389
  27. package/dist/chunk-BU5KJVWF.js +0 -78
  28. package/dist/chunk-BZ4EYURA.js +0 -181
  29. package/dist/chunk-CEL5ZLKP.js +0 -233
  30. package/dist/chunk-EH4AXGRO.js +0 -48
  31. package/dist/chunk-FDK3VJVE.js +0 -1295
  32. package/dist/chunk-I2KLQ2HA.js +0 -22
  33. package/dist/chunk-IO5WWY6A.js +0 -156
  34. package/dist/chunk-JC3FCKYL.js +0 -516
  35. package/dist/chunk-JZBOXOUC.js +0 -259
  36. package/dist/chunk-KC7KSQR4.js +0 -302
  37. package/dist/chunk-LZCGPRHS.js +0 -228
  38. package/dist/chunk-MBIFE6SA.js +0 -250
  39. package/dist/chunk-MXFJXUHC.js +0 -292
  40. package/dist/chunk-NUWDSTP7.js +0 -233
  41. package/dist/chunk-QCCP4RU5.js +0 -190
  42. package/dist/chunk-QMUQV5NP.js +0 -146
  43. package/dist/chunk-QQXJODFL.js +0 -328
  44. package/dist/chunk-SWOYEQN2.js +0 -1005
  45. package/dist/chunk-TH5FF5SC.js +0 -16
  46. package/dist/chunk-TXOEHSVP.js +0 -275
  47. package/dist/chunk-UFU5GGGA.js +0 -47
  48. package/dist/chunk-USSDZQKJ.js +0 -6533
  49. package/dist/chunk-UZJ7EERS.js +0 -272
  50. package/dist/chunk-VUZEEGP5.js +0 -725
  51. package/dist/chunk-W6EEFUCJ.js +0 -349
  52. package/dist/chunk-YGGGUTG3.js +0 -125
  53. package/dist/chunk-YJYZMLD5.js +0 -360
  54. package/dist/chunk-YKV4EFUI.js +0 -199
  55. package/dist/chunk-ZS6VABML.js +0 -185
  56. package/dist/cipher-E23BHBSO.js +0 -27
  57. package/dist/consolidation-undo-FKJZCJHS.js +0 -426
  58. package/dist/contradiction-review-WJRWNQ5N.js +0 -29
  59. package/dist/contradiction-scan-5X423QGT.js +0 -12
  60. package/dist/dreams-ledger-KDX44I7R.js +0 -290
  61. package/dist/engine-O46ZX4EZ.js +0 -16
  62. package/dist/extraction-judge-telemetry-O4ZVGLTU.js +0 -14
  63. package/dist/fallback-llm-43UMEXNJ.js +0 -10
  64. package/dist/first-start-migration-H2SAXAGR.js +0 -263
  65. package/dist/forget-ZECIDNL5.js +0 -68
  66. package/dist/fs-utils-OYXSZSVV.js +0 -39
  67. package/dist/graph-edge-decay-24ZKD5QL.js +0 -202
  68. package/dist/kdf-RXKIWHRU.js +0 -25
  69. package/dist/logger-XG7JKLPS.js +0 -9
  70. package/dist/memory-governance-6BCH6SZ5.js +0 -25
  71. package/dist/metadata-WK2TRPYZ.js +0 -20
  72. package/dist/migrate-from-identity-anchor-SNDNKHZD.js +0 -7
  73. package/dist/path-ZKO74XXC.js +0 -7
  74. package/dist/peers-W53WSDXG.js +0 -43
  75. package/dist/purge-IKJISXEQ.js +0 -204
  76. package/dist/resolution-BN35OXDS.js +0 -11
  77. package/dist/secure-store-6RWYM6GK.js +0 -155
  78. package/dist/state-4ITLYMAU.js +0 -7
  79. package/dist/state-store-ET3ADVY5.js +0 -14
  80. package/dist/storage-VEGU76NN.js +0 -27
  81. package/dist/tier-stats-ZRQBV6G2.js +0 -147
  82. package/dist/trace-IL2Y34EH.js +0 -289
  83. package/dist/tui-7KRDCMYK.js +0 -12
  84. package/dist/types-MBUINTB2.js +0 -30
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-remnic",
3
3
  "name": "Remnic OpenClaw Plugin",
4
- "version": "1.0.54",
4
+ "version": "9.3.517",
5
5
  "kind": "memory",
6
6
  "description": "Local semantic memory for OpenClaw with bundled Remnic core runtime. Requires plugins.slots.memory set to this plugin id for hooks to fire.",
7
7
  "setup": {
@@ -2782,7 +2782,7 @@
2782
2782
  },
2783
2783
  "recallDirectAnswerEnabled": {
2784
2784
  "type": "boolean",
2785
- "default": true,
2785
+ "default": false,
2786
2786
  "description": "When true, recall runs the direct-answer tier in observation mode: annotates LastRecallSnapshot.tierExplain with which tier would have served the query (issue #518). Does not short-circuit the QMD path in the current release."
2787
2787
  },
2788
2788
  "recallDisclosureEscalation": {
@@ -5192,7 +5192,7 @@
5192
5192
  },
5193
5193
  "recallDirectAnswerEnabled": {
5194
5194
  "label": "Direct-Answer Retrieval Tier",
5195
- "help": "Route validated high-trust queries to a fast direct-answer path before QMD (issue #518)."
5195
+ "help": "Opt in to the direct-answer retrieval tier for validated high-trust queries before QMD (issue #518)."
5196
5196
  },
5197
5197
  "recallDisclosureEscalation": {
5198
5198
  "label": "Disclosure Auto-Escalation",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/plugin-openclaw",
3
- "version": "1.0.54",
3
+ "version": "9.3.517",
4
4
  "description": "OpenClaw adapter for Remnic memory with bundled @remnic/core runtime",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -71,7 +71,7 @@
71
71
  },
72
72
  "dependencies": {
73
73
  "openai": "^6.0.0",
74
- "@remnic/core": "^1.1.30"
74
+ "@remnic/core": "^9.3.517"
75
75
  },
76
76
  "peerDependencies": {
77
77
  "openclaw": ">=2026.4.1 || 2026.4.7-1 || 2026.4.9-beta.1 || 2026.4.11-beta.1 || 2026.4.12-beta.1 || 2026.4.14-beta.1 || 2026.4.15-beta.1 || 2026.4.15-beta.2 || 2026.4.19-beta.1 || 2026.4.19-beta.2 || 2026.4.20-beta.1 || 2026.4.20-beta.2 || 2026.4.22-beta.1 || 2026.4.23-beta.1 || 2026.4.23-beta.2 || 2026.4.23-beta.3 || 2026.4.23-beta.4 || 2026.4.23-beta.5 || 2026.4.23-beta.6 || 2026.4.24-beta.1 || 2026.4.24-beta.2 || 2026.4.24-beta.3 || 2026.4.24-beta.4 || 2026.4.24-beta.5 || 2026.4.24-beta.6 || 2026.4.25-beta.1 || 2026.4.25-beta.2 || 2026.4.25-beta.3 || 2026.4.25-beta.4 || 2026.4.25-beta.5 || 2026.4.25-beta.6 || 2026.4.25-beta.7 || 2026.4.25-beta.8 || 2026.4.25-beta.9 || 2026.4.25-beta.10 || 2026.4.25-beta.11 || 2026.4.26-beta.1 || 2026.4.27-beta.1 || 2026.4.29-beta.1 || 2026.4.29-beta.2 || 2026.4.29-beta.3 || 2026.4.29-beta.4 || 2026.4.30-beta.1 || 2026.5.2-beta.1 || 2026.5.2-beta.2 || 2026.5.2-beta.3 || 2026.5.3-1 || 2026.5.3-beta.1 || 2026.5.3-beta.2 || 2026.5.3-beta.3 || 2026.5.3-beta.4 || 2026.5.4-beta.1 || 2026.5.4-beta.2 || 2026.5.4-beta.3 || 2026.5.5-beta.1 || 2026.5.5-beta.2 || 2026.5.6-beta.1 || 2026.5.7-beta.1 || 2026.5.9-beta.1 || 2026.5.10-beta.1 || 2026.5.10-beta.2 || 2026.5.10-beta.3 || 2026.5.10-beta.4 || 2026.5.10-beta.5 || 2026.5.10-beta.6 || 2026.5.12-beta.1 || 2026.5.12-beta.2 || 2026.5.12-beta.3 || 2026.5.12-beta.4 || 2026.5.12-beta.5 || 2026.5.12-beta.6 || 2026.5.12-beta.7 || 2026.5.12-beta.8 || 2026.5.14-beta.1 || 2026.5.14-beta.2 || 2026.5.16-beta.1 || 2026.5.16-beta.2 || 2026.5.16-beta.3 || 2026.5.16-beta.4 || 2026.5.16-beta.5 || 2026.5.16-beta.6 || 2026.5.16-beta.7 || 2026.5.18-beta.1 || 2026.5.19-alpha.1 || 2026.5.19-beta.1 || 2026.5.19-beta.2 || 2026.5.20-beta.1 || 2026.5.20-beta.2 || 2026.5.21-alpha.1 || 2026.5.21-beta.1 || 2026.5.22-beta.1 || 2026.5.23-alpha.1 || 2026.5.24-alpha.1 || 2026.5.24-beta.1 || 2026.5.24-beta.2 || 2026.5.25-alpha.1 || 2026.5.25-alpha.2 || 2026.5.25-beta.1 || 2026.5.26-beta.1 || 2026.5.26-beta.2 || 2026.5.27-alpha.1 || 2026.5.27-beta.1 || 2026.5.28-alpha.1 || 2026.5.28-beta.1 || 2026.5.28-beta.2 || 2026.5.28-beta.3 || 2026.5.28-beta.4 || 2026.5.29-alpha.1 || 2026.5.30-beta.1 || 2026.5.30-beta.2 || 2026.5.31-alpha.1 || 2026.5.31-beta.1 || 2026.5.31-beta.2 || 2026.5.31-beta.3"
@@ -80,7 +80,8 @@
80
80
  "@openclaw/plugin-inspector": "0.3.10",
81
81
  "acorn": "^8.16.0",
82
82
  "tsup": "^8.5.1",
83
- "typescript": "^5.9.3"
83
+ "typescript": "^5.9.3",
84
+ "@remnic/core": "^9.3.517"
84
85
  },
85
86
  "license": "MIT",
86
87
  "repository": {
@@ -14,6 +14,7 @@ import subprocess
14
14
  import sys
15
15
  import time
16
16
  import uuid
17
+ from datetime import datetime, timezone
17
18
  from pathlib import Path
18
19
  from typing import Any
19
20
 
@@ -56,7 +57,7 @@ def read_payload() -> dict[str, Any]:
56
57
  return payload
57
58
 
58
59
 
59
- def ensure_index_dir(index_path: str) -> Path:
60
+ def ensure_index_dir(index_path: Any) -> Path:
60
61
  if not isinstance(index_path, str) or not index_path.strip():
61
62
  raise SidecarError("indexPath is required")
62
63
  path = Path(index_path)
@@ -110,7 +111,7 @@ def read_metadata(path: Path) -> list[dict[str, Any]]:
110
111
 
111
112
 
112
113
  def write_metadata(path: Path, rows: list[dict[str, Any]]) -> None:
113
- tmp = path.with_suffix(".jsonl.tmp")
114
+ tmp = temp_artifact_path(path)
114
115
  with tmp.open("w", encoding="utf-8") as handle:
115
116
  for row in rows:
116
117
  handle.write(json.dumps(row, separators=(",", ":"), ensure_ascii=False))
@@ -129,7 +130,7 @@ def read_manifest(path: Path) -> dict[str, Any] | None:
129
130
 
130
131
 
131
132
  def write_manifest(path: Path, manifest: dict[str, Any]) -> None:
132
- tmp = path.with_suffix(".json.tmp")
133
+ tmp = temp_artifact_path(path)
133
134
  tmp.write_text(
134
135
  json.dumps(manifest, separators=(",", ":"), ensure_ascii=False),
135
136
  encoding="utf-8",
@@ -211,13 +212,74 @@ def embed_texts(texts: list[str], model_id: str) -> tuple[Any, Any, Any]:
211
212
 
212
213
 
213
214
  def write_index(path: Path, vectors: Any, faiss: Any) -> None:
215
+ tmp = write_index_temp(path, vectors, faiss)
216
+ os.replace(tmp, path)
217
+
218
+
219
+ def temp_artifact_path(path: Path) -> Path:
220
+ return path.with_name(f".{path.name}.{os.getpid()}.{uuid.uuid4().hex}.tmp")
221
+
222
+
223
+ def write_metadata_temp(path: Path, rows: list[dict[str, Any]]) -> Path:
224
+ tmp = temp_artifact_path(path)
225
+ with tmp.open("w", encoding="utf-8") as handle:
226
+ for row in rows:
227
+ handle.write(json.dumps(row, separators=(",", ":"), ensure_ascii=False))
228
+ handle.write("\n")
229
+ return tmp
230
+
231
+
232
+ def write_manifest_temp(path: Path, manifest: dict[str, Any]) -> Path:
233
+ tmp = temp_artifact_path(path)
234
+ tmp.write_text(
235
+ json.dumps(manifest, separators=(",", ":"), ensure_ascii=False),
236
+ encoding="utf-8",
237
+ )
238
+ return tmp
239
+
240
+
241
+ def write_index_temp(path: Path, vectors: Any, faiss: Any) -> Path:
214
242
  dim = int(vectors.shape[1])
215
243
  index = faiss.IndexFlatIP(dim)
216
244
  if int(vectors.shape[0]) > 0:
217
245
  index.add(vectors)
218
- tmp = path.with_suffix(".faiss.tmp")
246
+ tmp = temp_artifact_path(path)
219
247
  faiss.write_index(index, str(tmp))
220
- os.replace(tmp, path)
248
+ return tmp
249
+
250
+
251
+ def commit_index_artifacts(artifacts: list[tuple[Path, Path]]) -> None:
252
+ backups: list[tuple[Path, Path, bool]] = []
253
+ try:
254
+ for target, tmp in artifacts:
255
+ backup = target.with_name(f".{target.name}.{os.getpid()}.{uuid.uuid4().hex}.bak")
256
+ existed = target.exists()
257
+ if existed:
258
+ os.replace(target, backup)
259
+ backups.append((target, backup, existed))
260
+ os.replace(tmp, target)
261
+ for _target, backup, existed in backups:
262
+ if existed:
263
+ try:
264
+ backup.unlink()
265
+ except FileNotFoundError:
266
+ pass
267
+ except Exception:
268
+ for target, backup, existed in reversed(backups):
269
+ try:
270
+ if target.exists():
271
+ target.unlink()
272
+ if existed and backup.exists():
273
+ os.replace(backup, target)
274
+ except Exception:
275
+ pass
276
+ raise
277
+ finally:
278
+ for _target, tmp in artifacts:
279
+ try:
280
+ tmp.unlink()
281
+ except FileNotFoundError:
282
+ pass
221
283
 
222
284
 
223
285
  def resolve_vector_dimension(model_id: str) -> int:
@@ -332,15 +394,15 @@ def parse_chunks(payload: dict[str, Any]) -> list[dict[str, Any]]:
332
394
  if not isinstance(raw_chunks, list):
333
395
  raise SidecarError("chunks must be an array")
334
396
  chunks: list[dict[str, Any]] = []
335
- for item in raw_chunks:
397
+ for index, item in enumerate(raw_chunks):
336
398
  if not isinstance(item, dict):
337
- continue
399
+ raise SidecarError(f"chunks[{index}] must be an object")
338
400
  chunk_id = item.get("id")
339
401
  text = item.get("text")
340
402
  if not isinstance(chunk_id, str) or not chunk_id:
341
- continue
403
+ raise SidecarError(f"chunks[{index}].id must be a non-empty string")
342
404
  if not isinstance(text, str):
343
- continue
405
+ raise SidecarError(f"chunks[{index}].text must be a string")
344
406
  chunks.append(
345
407
  {
346
408
  "id": chunk_id,
@@ -361,6 +423,49 @@ def metadata_row_key(row: dict[str, Any]) -> tuple[str, str]:
361
423
  return (session_key, row_id)
362
424
 
363
425
 
426
+ def metadata_result_path(row: dict[str, Any]) -> str:
427
+ row_id = row.get("id") if isinstance(row.get("id"), str) else ""
428
+ session_key = row.get("sessionKey") if isinstance(row.get("sessionKey"), str) else ""
429
+ return f"{session_key}/{row_id}" if session_key else row_id
430
+
431
+
432
+ def parse_retention_cutoff_ms(value: Any) -> int | None:
433
+ if value is None:
434
+ return None
435
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
436
+ raise SidecarError("retentionCutoffMs must be a finite non-negative number when provided")
437
+ if value < 0 or value != value or value in (float("inf"), float("-inf")):
438
+ raise SidecarError("retentionCutoffMs must be a finite non-negative number when provided")
439
+ return int(value)
440
+
441
+
442
+ def parse_row_timestamp_ms(row: dict[str, Any]) -> int | None:
443
+ for key in ("endTs", "startTs"):
444
+ value = row.get(key)
445
+ if not isinstance(value, str) or not value.strip():
446
+ continue
447
+ text = value.strip()
448
+ try:
449
+ parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
450
+ except ValueError:
451
+ continue
452
+ if parsed.tzinfo is None:
453
+ parsed = parsed.replace(tzinfo=timezone.utc)
454
+ return int(parsed.timestamp() * 1000)
455
+ return None
456
+
457
+
458
+ def prune_metadata_rows(rows: list[dict[str, Any]], retention_cutoff_ms: int | None) -> list[dict[str, Any]]:
459
+ if retention_cutoff_ms is None:
460
+ return rows
461
+ pruned: list[dict[str, Any]] = []
462
+ for row in rows:
463
+ timestamp_ms = parse_row_timestamp_ms(row)
464
+ if timestamp_ms is None or timestamp_ms >= retention_cutoff_ms:
465
+ pruned.append(row)
466
+ return pruned
467
+
468
+
364
469
  def merge_rows(existing: list[dict[str, Any]], updates: list[dict[str, Any]]) -> list[dict[str, Any]]:
365
470
  by_key: dict[tuple[str, str], dict[str, Any]] = {
366
471
  metadata_row_key(row): row for row in existing
@@ -531,7 +636,8 @@ def run_upsert(payload: dict[str, Any]) -> dict[str, Any]:
531
636
  if not isinstance(model_id, str) or not model_id:
532
637
  raise SidecarError("modelId is required")
533
638
 
534
- index_dir = ensure_index_dir(str(payload.get("indexPath", "")))
639
+ index_dir = ensure_index_dir(payload.get("indexPath"))
640
+ retention_cutoff_ms = parse_retention_cutoff_ms(payload.get("retentionCutoffMs"))
535
641
  chunks = parse_chunks(payload)
536
642
 
537
643
  if not chunks:
@@ -543,7 +649,7 @@ def run_upsert(payload: dict[str, Any]) -> dict[str, Any]:
543
649
 
544
650
  writer_lock_path = acquire_writer_lock(index_dir)
545
651
  try:
546
- existing = read_metadata(meta_path)
652
+ existing = prune_metadata_rows(read_metadata(meta_path), retention_cutoff_ms)
547
653
  existing_manifest = read_manifest(manifest_path)
548
654
  merged = merge_rows(existing, chunks)
549
655
 
@@ -552,9 +658,6 @@ def run_upsert(payload: dict[str, Any]) -> dict[str, Any]:
552
658
 
553
659
  lock_path = acquire_index_lock(index_dir)
554
660
  try:
555
- # Commit FAISS index first; metadata follows so we never point at missing vectors.
556
- write_index(idx_path, vectors, faiss)
557
- write_metadata(meta_path, merged)
558
661
  preserved_rebuild_at = (
559
662
  existing_manifest.get("lastSuccessfulRebuildAt")
560
663
  if isinstance(existing_manifest, dict)
@@ -562,14 +665,18 @@ def run_upsert(payload: dict[str, Any]) -> dict[str, Any]:
562
665
  and existing_manifest.get("lastSuccessfulRebuildAt")
563
666
  else None
564
667
  )
565
- write_manifest(
566
- manifest_path,
567
- build_manifest(
568
- model_id,
569
- int(vectors.shape[1]),
570
- len(merged),
571
- last_successful_rebuild_at=preserved_rebuild_at,
572
- ),
668
+ manifest = build_manifest(
669
+ model_id,
670
+ int(vectors.shape[1]),
671
+ len(merged),
672
+ last_successful_rebuild_at=preserved_rebuild_at,
673
+ )
674
+ commit_index_artifacts(
675
+ [
676
+ (idx_path, write_index_temp(idx_path, vectors, faiss)),
677
+ (meta_path, write_metadata_temp(meta_path, merged)),
678
+ (manifest_path, write_manifest_temp(manifest_path, manifest)),
679
+ ]
573
680
  )
574
681
  finally:
575
682
  release_index_lock(lock_path)
@@ -584,7 +691,7 @@ def run_rebuild(payload: dict[str, Any]) -> dict[str, Any]:
584
691
  if not isinstance(model_id, str) or not model_id:
585
692
  raise SidecarError("modelId is required")
586
693
 
587
- index_dir = ensure_index_dir(str(payload.get("indexPath", "")))
694
+ index_dir = ensure_index_dir(payload.get("indexPath"))
588
695
  chunks = parse_chunks(payload)
589
696
 
590
697
  writer_lock_path = acquire_writer_lock(index_dir)
@@ -603,9 +710,14 @@ def run_rebuild(payload: dict[str, Any]) -> dict[str, Any]:
603
710
  idx_path = index_file(index_dir)
604
711
  manifest_path = manifest_file(index_dir)
605
712
 
606
- write_index(idx_path, vectors, faiss)
607
- write_metadata(meta_path, chunks)
608
- write_manifest(manifest_path, build_manifest(model_id, int(vectors.shape[1]), chunk_count))
713
+ manifest = build_manifest(model_id, int(vectors.shape[1]), chunk_count)
714
+ commit_index_artifacts(
715
+ [
716
+ (idx_path, write_index_temp(idx_path, vectors, faiss)),
717
+ (meta_path, write_metadata_temp(meta_path, chunks)),
718
+ (manifest_path, write_manifest_temp(manifest_path, manifest)),
719
+ ]
720
+ )
609
721
  finally:
610
722
  release_index_lock(lock_path)
611
723
  finally:
@@ -625,7 +737,7 @@ def run_search(payload: dict[str, Any]) -> dict[str, Any]:
625
737
  if not isinstance(top_k, int) or isinstance(top_k, bool) or top_k <= 0:
626
738
  raise SidecarError("topK must be a positive integer")
627
739
 
628
- index_dir = ensure_index_dir(str(payload.get("indexPath", "")))
740
+ index_dir = ensure_index_dir(payload.get("indexPath"))
629
741
  meta_path = metadata_file(index_dir)
630
742
  idx_path = index_file(index_dir)
631
743
  manifest_path = manifest_file(index_dir)
@@ -672,7 +784,7 @@ def run_search(payload: dict[str, Any]) -> dict[str, Any]:
672
784
  row = rows[idx_i]
673
785
  results.append(
674
786
  {
675
- "path": row["id"],
787
+ "path": metadata_result_path(row),
676
788
  "snippet": row["text"][:280],
677
789
  "score": float(score),
678
790
  }
@@ -682,7 +794,7 @@ def run_search(payload: dict[str, Any]) -> dict[str, Any]:
682
794
 
683
795
 
684
796
  def build_health_response(payload: dict[str, Any], *, include_metadata: bool = False) -> dict[str, Any]:
685
- index_dir = ensure_index_dir(str(payload.get("indexPath", "")))
797
+ index_dir = ensure_index_dir(payload.get("indexPath"))
686
798
  meta_path = metadata_file(index_dir)
687
799
  idx_path = index_file(index_dir)
688
800
  manifest_path = manifest_file(index_dir)
@@ -1,250 +0,0 @@
1
- import {
2
- listJsonFiles
3
- } from "./chunk-25J4PXDH.js";
4
- import {
5
- FallbackLlmClient
6
- } from "./chunk-SWOYEQN2.js";
7
- import "./chunk-3A5ELHTT.js";
8
- import {
9
- log
10
- } from "./chunk-UFU5GGGA.js";
11
- import "./chunk-EH4AXGRO.js";
12
- import "./chunk-I2KLQ2HA.js";
13
-
14
- // ../remnic-core/src/calibration.ts
15
- import { createHash } from "crypto";
16
- import path from "path";
17
- import * as fsReadModule0 from "fs/promises";
18
- const mkdir = fsReadModule0.mkdir;
19
- const fileReader = fsReadModule0["re"+"ad"+"Fi"+"le"];
20
- const writeFile = fsReadModule0.writeFile;
21
- function calibrationDir(memoryDir) {
22
- return path.join(memoryDir, "state", "calibration");
23
- }
24
- function calibrationIndexPath(memoryDir) {
25
- return path.join(calibrationDir(memoryDir), "calibration-index.json");
26
- }
27
- async function readCalibrationIndex(memoryDir) {
28
- try {
29
- const raw = JSON.parse(await fileReader(calibrationIndexPath(memoryDir), "utf8"));
30
- return {
31
- rules: Array.isArray(raw.rules) ? raw.rules : [],
32
- updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : (/* @__PURE__ */ new Date()).toISOString(),
33
- totalCorrectionsAnalyzed: typeof raw.totalCorrectionsAnalyzed === "number" ? raw.totalCorrectionsAnalyzed : 0
34
- };
35
- } catch {
36
- return { rules: [], updatedAt: (/* @__PURE__ */ new Date()).toISOString(), totalCorrectionsAnalyzed: 0 };
37
- }
38
- }
39
- async function writeCalibrationIndex(memoryDir, index) {
40
- const dir = calibrationDir(memoryDir);
41
- await mkdir(dir, { recursive: true });
42
- index.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
43
- await writeFile(calibrationIndexPath(memoryDir), JSON.stringify(index, null, 2), "utf8");
44
- }
45
- async function readCalibrationCorrections(memoryDir) {
46
- return readCorrectionsImpl(memoryDir);
47
- }
48
- async function readCorrections(memoryDir) {
49
- return readCorrectionsImpl(memoryDir);
50
- }
51
- async function readCorrectionsImpl(memoryDir) {
52
- const correctionsDir = path.join(memoryDir, "corrections");
53
- const files = await listJsonFiles(correctionsDir).catch(() => {
54
- return [];
55
- });
56
- const factsDir = path.join(memoryDir, "facts");
57
- try {
58
- const { readdir } = await import("fs/promises");
59
- const dayDirs = (await readdir(factsDir)).filter((d) => /^\d{4}-\d{2}-\d{2}$/.test(d));
60
- for (const day of dayDirs) {
61
- const dayPath = path.join(factsDir, day);
62
- const dayFiles = (await readdir(dayPath)).filter((f) => f.startsWith("correction-") && f.endsWith(".md")).map((f) => path.join(dayPath, f));
63
- files.push(...dayFiles);
64
- }
65
- } catch {
66
- }
67
- try {
68
- const { readdir } = await import("fs/promises");
69
- const corrFiles = (await readdir(correctionsDir)).filter((f) => f.endsWith(".md")).map((f) => path.join(correctionsDir, f));
70
- files.push(...corrFiles);
71
- } catch {
72
- }
73
- const corrections = [];
74
- const seen = /* @__PURE__ */ new Set();
75
- for (const filePath of files) {
76
- try {
77
- const raw = await fileReader(filePath, "utf8");
78
- const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
79
- if (!fmMatch) continue;
80
- const content = fmMatch[2].trim();
81
- if (!content || content.length < 10) continue;
82
- const idMatch = fmMatch[1].match(/^id:\s*(.+)$/m);
83
- const id = idMatch?.[1]?.trim() ?? path.basename(filePath, ".md");
84
- if (seen.has(id)) continue;
85
- seen.add(id);
86
- const confMatch = fmMatch[1].match(/^confidence:\s*(.+)$/m);
87
- const confidence = confMatch ? parseFloat(confMatch[1]) : 0.9;
88
- const entityMatch = fmMatch[1].match(/^entityRef:\s*(.+)$/m);
89
- const entityRefs = entityMatch ? [entityMatch[1].trim()] : [];
90
- corrections.push({ id, content, created: "", confidence, entityRefs, tags: [] });
91
- } catch {
92
- }
93
- }
94
- return corrections;
95
- }
96
- var CLUSTER_PROMPT = `You are analyzing user corrections to an AI assistant. Each correction represents a moment where the assistant's prediction of what the user wanted was WRONG.
97
-
98
- Your job: Group these corrections into clusters where the SAME TYPE of misunderstanding is happening. Then for each cluster, synthesize a CalibrationRule.
99
-
100
- A CalibrationRule describes:
101
- - condition: When does this type of mistake happen?
102
- - modelTendency: What does the model tend to assume or do wrong?
103
- - userExpectation: What does the user actually want instead?
104
- - calibration: How should the model adjust its behavior?
105
- - ruleType: One of "model_tendency", "user_expectation", "scope_boundary", "verification_required"
106
-
107
- Focus on PATTERNS, not individual corrections. A cluster needs at least 2 corrections to be worth a rule.
108
-
109
- Output valid JSON only:
110
- {
111
- "rules": [
112
- {
113
- "ruleType": "model_tendency",
114
- "condition": "When discussing project scope or task boundaries",
115
- "modelTendency": "The model tends to assume broader scope than the user intends",
116
- "userExpectation": "The user prefers narrow, specific task definitions and wants to be asked before scope expansion",
117
- "calibration": "When uncertain about scope, ask for clarification rather than assuming. Default to the narrower interpretation.",
118
- "confidence": 0.85,
119
- "evidenceIds": ["correction-id-1", "correction-id-2"]
120
- }
121
- ]
122
- }`;
123
- async function synthesizeCalibrationRules(corrections, llm, existingRules, agentId) {
124
- if (corrections.length < 2) return [];
125
- const correctionText = corrections.slice(0, 50).map((c, i) => `[${c.id}] ${c.content}`).join("\n\n");
126
- const existingRulesText = existingRules.length > 0 ? `
127
-
128
- Existing calibration rules (avoid duplicating these):
129
- ${existingRules.map((r) => `- ${r.condition}: ${r.calibration}`).join("\n")}` : "";
130
- const response = await llm.chatCompletion(
131
- [
132
- { role: "system", content: CLUSTER_PROMPT },
133
- { role: "user", content: `Here are ${corrections.length} corrections from this user:
134
-
135
- ${correctionText}${existingRulesText}` }
136
- ],
137
- { temperature: 0.3, maxTokens: 3e3, agentId }
138
- );
139
- if (!response?.content) return [];
140
- try {
141
- let jsonStr = response.content.trim();
142
- const fenceMatch = jsonStr.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
143
- if (fenceMatch) jsonStr = fenceMatch[1];
144
- const parsed = JSON.parse(jsonStr);
145
- if (!Array.isArray(parsed.rules)) return [];
146
- const now = (/* @__PURE__ */ new Date()).toISOString();
147
- return parsed.rules.filter((r) => r.condition && r.calibration && r.modelTendency).map((r) => ({
148
- id: `cal-${createHash("sha256").update(r.condition + r.calibration).digest("hex").slice(0, 12)}`,
149
- ruleType: r.ruleType ?? "model_tendency",
150
- condition: String(r.condition),
151
- modelTendency: String(r.modelTendency),
152
- userExpectation: String(r.userExpectation ?? ""),
153
- calibration: String(r.calibration),
154
- confidence: typeof r.confidence === "number" ? r.confidence : 0.7,
155
- evidenceCount: Array.isArray(r.evidenceIds) ? r.evidenceIds.length : 1,
156
- evidenceCorrectionIds: Array.isArray(r.evidenceIds) ? r.evidenceIds : [],
157
- createdAt: now,
158
- lastReinforcedAt: now
159
- }));
160
- } catch {
161
- log.warn("[calibration] failed to parse LLM response");
162
- return [];
163
- }
164
- }
165
- function buildCalibrationRecallSection(rules, query, maxChars = 1200) {
166
- if (rules.length === 0) return null;
167
- const lines = [
168
- "## Model Calibration (learned from past corrections)",
169
- "",
170
- "Adjustments for this specific user, learned from patterns in their corrections:",
171
- ""
172
- ];
173
- let totalChars = lines.join("\n").length;
174
- for (const rule of rules) {
175
- const line = `- **${rule.condition}**: ${rule.modelTendency} \u2192 Instead: ${rule.calibration}`;
176
- if (totalChars + line.length + 1 > maxChars) break;
177
- lines.push(line);
178
- totalChars += line.length + 1;
179
- }
180
- if (lines.length <= 4) return null;
181
- lines.push("");
182
- return lines.join("\n");
183
- }
184
- async function runCalibrationConsolidation(options) {
185
- try {
186
- const llm = new FallbackLlmClient(options.gatewayConfig, {
187
- workspaceDir: options.workspaceDir
188
- });
189
- if (!llm.isAvailable(options.gatewayAgentId)) {
190
- log.debug("[calibration] no LLM available \u2014 skipping consolidation");
191
- return [];
192
- }
193
- const corrections = await readCorrections(options.memoryDir);
194
- if (corrections.length < 3) {
195
- log.debug(`[calibration] only ${corrections.length} corrections \u2014 need at least 3`);
196
- return [];
197
- }
198
- const existingIndex = await readCalibrationIndex(options.memoryDir);
199
- const newRules = await synthesizeCalibrationRules(corrections, llm, existingIndex.rules, options.gatewayAgentId);
200
- if (newRules.length === 0) {
201
- log.debug("[calibration] no new calibration rules synthesized");
202
- return existingIndex.rules;
203
- }
204
- const ruleMap = new Map(existingIndex.rules.map((r) => [r.id, r]));
205
- for (const rule of newRules) {
206
- if (ruleMap.has(rule.id)) {
207
- const existing = ruleMap.get(rule.id);
208
- existing.lastReinforcedAt = (/* @__PURE__ */ new Date()).toISOString();
209
- existing.evidenceCount += rule.evidenceCount;
210
- existing.confidence = Math.min(1, existing.confidence + 0.05);
211
- } else {
212
- ruleMap.set(rule.id, rule);
213
- }
214
- }
215
- const allRules = [...ruleMap.values()];
216
- await writeCalibrationIndex(options.memoryDir, {
217
- rules: allRules,
218
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
219
- totalCorrectionsAnalyzed: corrections.length
220
- });
221
- log.debug(`[calibration] synthesized ${newRules.length} new rule(s), ${allRules.length} total`);
222
- return allRules;
223
- } catch (error) {
224
- log.warn(`[calibration] consolidation failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);
225
- return [];
226
- }
227
- }
228
- async function runCalibrationIfEnabled(options) {
229
- if (!options.calibrationEnabled) {
230
- return [];
231
- }
232
- return runCalibrationConsolidation({
233
- memoryDir: options.memoryDir,
234
- gatewayConfig: options.gatewayConfig,
235
- workspaceDir: options.workspaceDir
236
- });
237
- }
238
- async function getCalibrationRulesForRecall(memoryDir) {
239
- const index = await readCalibrationIndex(memoryDir);
240
- return index.rules;
241
- }
242
- export {
243
- buildCalibrationRecallSection,
244
- getCalibrationRulesForRecall,
245
- readCalibrationCorrections,
246
- readCalibrationIndex,
247
- runCalibrationConsolidation,
248
- runCalibrationIfEnabled,
249
- synthesizeCalibrationRules
250
- };
@@ -1,33 +0,0 @@
1
- import {
2
- defaultCapsulesDir,
3
- parseCapsuleConflictMode,
4
- parseCapsuleExportOptions,
5
- parseCapsuleImportMode,
6
- parseCapsuleImportOptions,
7
- parseCapsuleIncludeKinds,
8
- parseCapsuleInspectOptions,
9
- parseCapsuleListOptions,
10
- parseCapsuleMergeOptions,
11
- parseCapsuleOutputFormat,
12
- parseCapsulePeers,
13
- parseCapsuleSince,
14
- renderCapsuleInspect,
15
- renderCapsuleList
16
- } from "./chunk-QQXJODFL.js";
17
- import "./chunk-I2KLQ2HA.js";
18
- export {
19
- defaultCapsulesDir,
20
- parseCapsuleConflictMode,
21
- parseCapsuleExportOptions,
22
- parseCapsuleImportMode,
23
- parseCapsuleImportOptions,
24
- parseCapsuleIncludeKinds,
25
- parseCapsuleInspectOptions,
26
- parseCapsuleListOptions,
27
- parseCapsuleMergeOptions,
28
- parseCapsuleOutputFormat,
29
- parseCapsulePeers,
30
- parseCapsuleSince,
31
- renderCapsuleInspect,
32
- renderCapsuleList
33
- };
@@ -1,17 +0,0 @@
1
- import {
2
- decryptCapsuleFile,
3
- decryptCapsuleFileInMemory,
4
- encryptCapsuleFile,
5
- isEncryptedCapsuleFile
6
- } from "./chunk-ZS6VABML.js";
7
- import "./chunk-MXFJXUHC.js";
8
- import "./chunk-JZBOXOUC.js";
9
- import "./chunk-6IWEAUN6.js";
10
- import "./chunk-YGGGUTG3.js";
11
- import "./chunk-I2KLQ2HA.js";
12
- export {
13
- decryptCapsuleFile,
14
- decryptCapsuleFileInMemory,
15
- encryptCapsuleFile,
16
- isEncryptedCapsuleFile
17
- };
@@ -1,17 +0,0 @@
1
- import {
2
- exportCapsule,
3
- isValidCapsuleSince
4
- } from "./chunk-TXOEHSVP.js";
5
- import "./chunk-YKV4EFUI.js";
6
- import "./chunk-QCCP4RU5.js";
7
- import "./chunk-ZS6VABML.js";
8
- import "./chunk-4XDQ3KEC.js";
9
- import "./chunk-MXFJXUHC.js";
10
- import "./chunk-JZBOXOUC.js";
11
- import "./chunk-6IWEAUN6.js";
12
- import "./chunk-YGGGUTG3.js";
13
- import "./chunk-I2KLQ2HA.js";
14
- export {
15
- exportCapsule,
16
- isValidCapsuleSince
17
- };
@@ -1,16 +0,0 @@
1
- import {
2
- importCapsule
3
- } from "./chunk-CEL5ZLKP.js";
4
- import "./chunk-YKV4EFUI.js";
5
- import "./chunk-QCCP4RU5.js";
6
- import "./chunk-ZS6VABML.js";
7
- import "./chunk-4XDQ3KEC.js";
8
- import "./chunk-MXFJXUHC.js";
9
- import "./chunk-JZBOXOUC.js";
10
- import "./chunk-6IWEAUN6.js";
11
- import "./chunk-QMUQV5NP.js";
12
- import "./chunk-YGGGUTG3.js";
13
- import "./chunk-I2KLQ2HA.js";
14
- export {
15
- importCapsule
16
- };