openclaw-memory-atmem 1.0.0 → 2.0.0

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/README.md CHANGED
@@ -5,11 +5,11 @@ This npm package is the host bridge for AtMem. It is not a standalone memory eng
5
5
  Use the Python-owned installer:
6
6
 
7
7
  ```bash
8
- python -m pip install atmem==1.0.0
8
+ python -m pip install atmem==2.0.0
9
9
  atmem openclaw install
10
10
  ```
11
11
 
12
- The installer pins `openclaw-memory-atmem@1.0.0`, binds the exact `atmem` executable, copies existing OpenClaw memory, configures shadow mode, restarts the gateway and verifies the loaded plugin. Direct npm installation cannot perform or prove those steps.
12
+ The installer pins `openclaw-memory-atmem@2.0.0`, binds the exact `atmem` executable, copies existing OpenClaw memory, configures shadow mode, restarts the gateway and verifies the loaded plugin. Direct npm installation cannot perform or prove those steps.
13
13
 
14
14
  In shadow mode the bridge observes native-memory changes without injecting AtMem context. In active mode it exposes compatible memory search/get tools, model-semantic capture, bounded recall and native-path protection. `atmem control restore` restores the saved OpenClaw configuration and native memory.
15
15
 
package/dist/index.js CHANGED
@@ -333,7 +333,13 @@ function register(api) {
333
333
  }
334
334
  };
335
335
  const flightRunId = (eventRunId, ctx) => eventRunId ?? ctx.runId ?? ctx.sessionKey ?? ctx.sessionId ?? "unidentified-run";
336
- const recordBlackbox = async (eventType, eventRunId, ctx, payload, toolCallId) => {
336
+ const canonicalToolName = (value) => {
337
+ const normalized = value.trim();
338
+ return normalized.startsWith("openclaw") && normalized.length > "openclaw".length
339
+ ? normalized.slice("openclaw".length)
340
+ : normalized;
341
+ };
342
+ const recordBlackbox = async (eventType, eventRunId, ctx, payload, toolCallId, correlation = {}) => {
337
343
  if (!cfg.controlPlane.blackboxEnabled)
338
344
  return;
339
345
  try {
@@ -342,6 +348,11 @@ function register(api) {
342
348
  run_id: flightRunId(eventRunId, ctx),
343
349
  session_id: ctx.sessionId ?? ctx.sessionKey,
344
350
  tool_call_id: toolCallId,
351
+ turn_id: correlation.turnId ?? flightRunId(eventRunId, ctx),
352
+ retrieval_id: correlation.retrievalId,
353
+ context_event_id: correlation.contextEventId,
354
+ context_receipt_id: correlation.contextReceiptId,
355
+ outcome_id: correlation.outcomeId,
345
356
  payload,
346
357
  }, cfg.recall.timeoutMs);
347
358
  }
@@ -485,13 +496,28 @@ function register(api) {
485
496
  });
486
497
  api.on("llm_output", async (event, ctx) => {
487
498
  const responses = Array.isArray(event.assistantTexts) ? event.assistantTexts : [];
499
+ const visibleText = responses.map(String).join("");
500
+ const assistantVisibleTextSha256 = digestText(visibleText);
501
+ const modelOutputBundleSha256 = digestJson(responses);
502
+ const sessionKey = ctx.sessionKey ?? ctx.sessionId ?? event.sessionId ?? "default-session";
503
+ const pending = pendingPrompts.get(sessionKey);
504
+ if (pending) {
505
+ pendingPrompts.set(sessionKey, {
506
+ ...pending,
507
+ assistantVisibleTextSha256,
508
+ modelOutputBundleSha256,
509
+ });
510
+ }
488
511
  await recordBlackbox("model.output", event.runId, ctx, {
489
512
  provider: event.provider,
490
513
  model: event.model,
491
514
  resolved_ref: event.resolvedRef,
492
515
  harness_id: event.harnessId,
493
- response_sha256: digestJson(responses),
494
- response_chars: responses.reduce((total, value) => total + String(value).length, 0),
516
+ response_sha256: assistantVisibleTextSha256,
517
+ assistant_visible_text_sha256: assistantVisibleTextSha256,
518
+ model_output_bundle_sha256: modelOutputBundleSha256,
519
+ response_digest_profile: "atmem-assistant-visible-text-utf8-v1",
520
+ response_chars: visibleText.length,
495
521
  response_count: responses.length,
496
522
  usage: event.usage ?? {},
497
523
  reasoning_effort: event.reasoningEffort,
@@ -558,10 +584,10 @@ function register(api) {
558
584
  };
559
585
  async function personaBlock(sessionKey) {
560
586
  if (!cfg.persona.enabled)
561
- return "";
587
+ return { block: "", recordIds: [] };
562
588
  const now = Date.now();
563
589
  if (personaCache && now - personaCache.ts < cfg.persona.ttlSeconds * 1000) {
564
- return personaCache.block;
590
+ return personaCache;
565
591
  }
566
592
  const result = (await client.callTool("memory_persona", {
567
593
  session_id: sessionKey,
@@ -570,8 +596,13 @@ function register(api) {
570
596
  ? "compact"
571
597
  : "full",
572
598
  }, cfg.recall.timeoutMs));
573
- personaCache = { block: result?.block ?? "", ts: now };
574
- return personaCache.block;
599
+ personaCache = {
600
+ block: result?.block ?? "",
601
+ recordIds: result?.record_ids ?? [],
602
+ contextEventId: result?.context_event_id,
603
+ ts: now,
604
+ };
605
+ return personaCache;
575
606
  }
576
607
  // ---- auto-recall: persona + bounded, audited recall injection ---------
577
608
  api.on("before_prompt_build", async (event, ctx) => {
@@ -589,13 +620,26 @@ function register(api) {
589
620
  text: userText,
590
621
  ts: Date.now(),
591
622
  exposureId: prepared.exposure_id,
623
+ contextReceiptId: prepared.context_receipt_id,
592
624
  });
593
- await recordBlackbox(prepared.inject && prepared.context ? "context.injected" : "context.prepared", undefined, ctx, {
625
+ await recordBlackbox("context.disposition", undefined, ctx, {
626
+ disposition: prepared.inject && prepared.context
627
+ ? "injected"
628
+ : prepared.mode === "shadow" && (prepared.preview_context ?? "")
629
+ ? "withheld_by_policy"
630
+ : "no_relevant_memory",
594
631
  context_sha256: digestText(prepared.context ?? ""),
632
+ context_block_sha256: digestText(prepared.context ?? ""),
633
+ context_envelope_sha256: digestJson({ appendContext: prepared.context ?? "" }),
634
+ context_receipt_sha256: prepared.manifest_sha256,
635
+ digest_profile: "atmem-context-envelope-canonical-json-v1",
595
636
  context_chars: (prepared.context ?? "").length,
596
637
  candidate_ids: prepared.candidate_ids ?? [],
597
638
  exposure_id: prepared.exposure_id,
598
639
  mode: prepared.mode,
640
+ context_location: prepared.inject ? "appendContext" : "none",
641
+ }, undefined, {
642
+ contextReceiptId: prepared.context_receipt_id,
599
643
  });
600
644
  if (prepared.inject && prepared.context) {
601
645
  api.logger.info(`${TAG} memory control plane ${prepared.mode ?? "active"} context exposed`);
@@ -604,21 +648,52 @@ function register(api) {
604
648
  return;
605
649
  }
606
650
  catch (error) {
651
+ await recordBlackbox("context.disposition", undefined, ctx, {
652
+ disposition: "recall_failed",
653
+ context_block_sha256: digestText(""),
654
+ context_envelope_sha256: digestJson({}),
655
+ digest_profile: "atmem-context-envelope-canonical-json-v1",
656
+ context_chars: 0,
657
+ candidate_ids: [],
658
+ mode: "control-plane",
659
+ context_location: "none",
660
+ reason: error instanceof Error ? error.message : String(error),
661
+ });
607
662
  api.logger.warn(`${TAG} memory control plane failed closed: ${error instanceof Error ? error.message : String(error)}`);
608
663
  return;
609
664
  }
610
665
  }
611
666
  if (!cfg.recall.enabled && !cfg.persona.enabled) {
612
- if (takeoverGuidance)
613
- return { appendSystemContext: takeoverGuidance };
667
+ const result = takeoverGuidance
668
+ ? { appendSystemContext: takeoverGuidance }
669
+ : {};
670
+ await recordBlackbox("context.disposition", undefined, ctx, {
671
+ disposition: "not_applicable",
672
+ context_block_sha256: digestText(""),
673
+ context_envelope_sha256: digestJson(result),
674
+ digest_profile: "atmem-context-envelope-canonical-json-v1",
675
+ context_chars: 0,
676
+ candidate_ids: [],
677
+ mode: "direct",
678
+ context_location: "none",
679
+ });
680
+ if (Object.keys(result).length)
681
+ return result;
614
682
  return;
615
683
  }
616
684
  let persona = "";
685
+ let personaRecordIds = [];
686
+ let personaContextEventId;
617
687
  let recall = "";
688
+ let recallFailed = false;
618
689
  try {
619
- persona = await personaBlock(sessionKey);
690
+ const personaResult = await personaBlock(sessionKey);
691
+ persona = personaResult.block;
692
+ personaRecordIds = personaResult.recordIds;
693
+ personaContextEventId = personaResult.contextEventId;
620
694
  }
621
695
  catch (error) {
696
+ recallFailed = true;
622
697
  api.logger.warn(`${TAG} persona skipped: ${error instanceof Error ? error.message : String(error)}`);
623
698
  }
624
699
  try {
@@ -643,6 +718,7 @@ function register(api) {
643
718
  injectedRecordIds: result.record_ids ?? [],
644
719
  retrievalId: result.retrieval_id,
645
720
  contextEventId: result.context_event_id,
721
+ contextReceiptId: result.context_event_id,
646
722
  });
647
723
  }
648
724
  }
@@ -650,25 +726,68 @@ function register(api) {
650
726
  }
651
727
  catch (error) {
652
728
  // Never block the turn on recall problems.
729
+ recallFailed = true;
653
730
  api.logger.warn(`${TAG} auto-recall skipped: ${error instanceof Error ? error.message : String(error)}`);
654
731
  }
732
+ let result = {};
733
+ let contextLocation = "none";
655
734
  if (cfg.cacheAware.enabled) {
656
- const result = {};
657
735
  const systemParts = [takeoverGuidance, persona].filter(Boolean);
658
736
  if (systemParts.length)
659
737
  result.appendSystemContext = systemParts.join("\n\n");
660
738
  if (recall)
661
739
  result.appendContext = recall;
662
- if (Object.keys(result).length)
663
- return result;
664
- return;
740
+ contextLocation = recall
741
+ ? "appendContext"
742
+ : persona
743
+ ? "appendSystemContext"
744
+ : "none";
745
+ }
746
+ else {
747
+ const parts = [persona, recall].filter(Boolean);
748
+ if (parts.length)
749
+ result.prependContext = parts.join("\n\n") + "\n\n";
750
+ if (takeoverGuidance)
751
+ result.appendSystemContext = takeoverGuidance;
752
+ contextLocation = parts.length ? "prependContext" : "none";
753
+ }
754
+ const memoryContext = [persona, recall].filter(Boolean).join("\n\n");
755
+ const current = pendingPrompts.get(sessionKey);
756
+ const candidateIds = [...new Set([
757
+ ...personaRecordIds,
758
+ ...(current?.injectedRecordIds ?? []),
759
+ ])];
760
+ const componentEventIds = [...new Set([
761
+ personaContextEventId,
762
+ current?.contextEventId,
763
+ ].filter((value) => Boolean(value)))];
764
+ const contextEnvelopeSha256 = digestJson(result);
765
+ const contextReceiptId = componentEventIds.length
766
+ ? `ctxr_${digestJson({ componentEventIds, contextEnvelopeSha256 })}`
767
+ : undefined;
768
+ if (current) {
769
+ pendingPrompts.set(sessionKey, { ...current, contextReceiptId });
665
770
  }
666
- const parts = [persona, recall].filter(Boolean);
667
- const result = {};
668
- if (parts.length)
669
- result.prependContext = parts.join("\n\n") + "\n\n";
670
- if (takeoverGuidance)
671
- result.appendSystemContext = takeoverGuidance;
771
+ await recordBlackbox("context.disposition", undefined, ctx, {
772
+ disposition: memoryContext
773
+ ? "injected"
774
+ : recallFailed
775
+ ? "recall_failed"
776
+ : "no_relevant_memory",
777
+ context_sha256: digestText(memoryContext),
778
+ context_block_sha256: digestText(memoryContext),
779
+ context_envelope_sha256: contextEnvelopeSha256,
780
+ digest_profile: "atmem-context-envelope-canonical-json-v1",
781
+ context_chars: memoryContext.length,
782
+ candidate_ids: candidateIds,
783
+ context_component_event_ids: componentEventIds,
784
+ mode: "direct",
785
+ context_location: contextLocation,
786
+ }, undefined, {
787
+ retrievalId: current?.retrievalId,
788
+ contextEventId: current?.contextEventId ?? personaContextEventId,
789
+ contextReceiptId,
790
+ });
672
791
  if (Object.keys(result).length)
673
792
  return result;
674
793
  });
@@ -676,6 +795,7 @@ function register(api) {
676
795
  api.on("before_tool_call", async (event, ctx) => {
677
796
  await recordBlackbox("tool.requested", event.runId, ctx, {
678
797
  tool_name: event.toolName,
798
+ tool_canonical_name: canonicalToolName(event.toolName),
679
799
  tool_kind: event.toolKind,
680
800
  params_sha256: digestJson(event.params ?? {}),
681
801
  param_keys: Object.keys(event.params ?? {}).sort(),
@@ -690,6 +810,7 @@ function register(api) {
690
810
  "and memory_search or memory_get for recall.";
691
811
  await recordBlackbox("tool.completed", event.runId, ctx, {
692
812
  tool_name: event.toolName,
813
+ tool_canonical_name: canonicalToolName(event.toolName),
693
814
  outcome: "error",
694
815
  error_category: "blocked_by_memory_boundary",
695
816
  result_sha256: digestText(reason),
@@ -701,6 +822,7 @@ function register(api) {
701
822
  api.on("after_tool_call", async (event, ctx) => {
702
823
  await recordBlackbox("tool.completed", event.runId, ctx, {
703
824
  tool_name: event.toolName,
825
+ tool_canonical_name: canonicalToolName(event.toolName),
704
826
  result_sha256: digestJson(event.result ?? null),
705
827
  outcome: event.error ? "error" : "completed",
706
828
  error_category: event.error ? "tool_error" : undefined,
@@ -734,15 +856,19 @@ function register(api) {
734
856
  continue;
735
857
  const responseText = messageText(message.content);
736
858
  if (responseText) {
859
+ const assistantVisibleTextSha256 = cached.assistantVisibleTextSha256 ?? digestText(responseText);
737
860
  await client.callTool("memory_log_action", {
738
861
  action_type: "agent.response_after_memory",
739
862
  payload: {
740
- response_sha256: createHash("sha256")
741
- .update(responseText, "utf8")
742
- .digest("hex"),
863
+ response_sha256: assistantVisibleTextSha256,
864
+ assistant_visible_text_sha256: assistantVisibleTextSha256,
865
+ model_output_bundle_sha256: cached.modelOutputBundleSha256,
866
+ response_digest_profile: "atmem-assistant-visible-text-utf8-v1",
743
867
  injected_record_ids: cached.injectedRecordIds,
744
868
  retrieval_id: cached.retrievalId,
745
869
  context_event_id: cached.contextEventId,
870
+ context_receipt_id: cached.contextReceiptId,
871
+ run_id: flightRunId(event.runId, ctx),
746
872
  response_content_stored: false,
747
873
  success: event.success !== false,
748
874
  },
@@ -788,13 +914,23 @@ function register(api) {
788
914
  api.logger.warn(`${TAG} auto-capture failed: ${error instanceof Error ? error.message : String(error)}`);
789
915
  }
790
916
  finally {
917
+ const cancelled = event.cancelled === true;
918
+ const turnMessagesSha256 = digestJson(event.messages ?? []);
791
919
  await recordBlackbox("turn.ended", event.runId, ctx, {
792
- success: event.success !== false,
793
- cancelled: false,
920
+ success: event.success === true && !cancelled,
921
+ cancelled,
794
922
  error_category: event.error ? "agent_error" : undefined,
923
+ failure_kind: cancelled ? "cancelled" : event.error ? "agent_error" : undefined,
924
+ reason: event.error,
795
925
  duration_ms: event.durationMs ?? 0,
796
- messages_sha256: digestJson(event.messages ?? []),
926
+ messages_sha256: turnMessagesSha256,
927
+ turn_messages_sha256: turnMessagesSha256,
928
+ digest_profile: "atmem-turn-messages-canonical-json-v1",
797
929
  messages_count: Array.isArray(event.messages) ? event.messages.length : 0,
930
+ }, undefined, {
931
+ retrievalId: cached?.retrievalId,
932
+ contextEventId: cached?.contextEventId,
933
+ contextReceiptId: cached?.contextReceiptId,
798
934
  });
799
935
  }
800
936
  });
@@ -128,7 +128,7 @@ export class AtmemClient {
128
128
  capabilities: {},
129
129
  clientInfo: {
130
130
  name: "openclaw-memory-atmem",
131
- version: "1.0.0",
131
+ version: "2.0.0",
132
132
  },
133
133
  });
134
134
  this.notify("notifications/initialized", {});
@@ -2,7 +2,7 @@
2
2
  "id": "memory-atmem",
3
3
  "name": "Memory (atmem)",
4
4
  "description": "OpenClaw bridge installed and managed by the AtMem memory control plane.",
5
- "version": "1.0.0",
5
+ "version": "2.0.0",
6
6
  "commandAliases": ["memory-atmem"],
7
7
  "activation": {
8
8
  "onStartup": true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-memory-atmem",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "OpenClaw Agent Black Box and memory control-plane bridge for AtMem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",