openclaw-memory-atmem 2.2.5 → 2.2.6-beta.2

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,18 @@ 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 --upgrade atmem==2.2.5
8
+ python -m pip install --pre --upgrade atmem==2.2.6b2
9
9
  atmem openclaw install
10
10
  ```
11
11
 
12
- The installer pins `openclaw-memory-atmem@2.2.5`, 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 the matching OpenClaw bridge, 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
+
14
+ The bridge also supports AtMem's optional delegated context-provider mode. It
15
+ supplies authenticated owner/workspace bindings, contributes a verified result
16
+ as exactly one `prependContext` segment, confirms that segment at `llm_input`,
17
+ and suppresses native AtMem recall for the turn. Delegation is enabled only by
18
+ an AtMem registration; the bridge's `delegatedContext.userId` is an identity
19
+ mapping, not an activation switch.
13
20
 
14
21
  Existing AtMem 2.1 users run `atmem openclaw upgrade` after upgrading the Python
15
22
  package. This preserves the current memory mode and migration, verifies the new
package/dist/index.js CHANGED
@@ -61,6 +61,10 @@ function parseConfig(raw) {
61
61
  statePath: expandHome(String(cfg.controlPlane?.statePath ?? "~/.atmem/control-plane.json")),
62
62
  blackboxEnabled: cfg.controlPlane?.enabled === true || cfg.controlPlane?.blackboxEnabled === true,
63
63
  };
64
+ const delegatedContext = {
65
+ userId: String(cfg.delegatedContext?.userId ?? "").trim(),
66
+ requireOwner: cfg.delegatedContext?.requireOwner !== false,
67
+ };
64
68
  return {
65
69
  command: String(cfg.command ?? "atmem"),
66
70
  commandArgs: controlPlane.enabled
@@ -98,6 +102,7 @@ function parseConfig(raw) {
98
102
  },
99
103
  tools: { enabled: cfg.tools?.enabled !== false },
100
104
  controlPlane,
105
+ delegatedContext,
101
106
  };
102
107
  }
103
108
  const FILE_TOOL_HINTS = [
@@ -347,6 +352,17 @@ function register(api) {
347
352
  }
348
353
  return cfg.subject;
349
354
  };
355
+ const workspaceIdFor = (ctx) => {
356
+ const workspace = cfg.agentWorkspaces[agentIdFor(ctx)];
357
+ return workspace ? `ws_${digestText(workspace).slice(0, 16)}` : undefined;
358
+ };
359
+ const delegatedUserIdFor = (ctx) => {
360
+ if (!cfg.delegatedContext.userId)
361
+ return undefined;
362
+ if (cfg.delegatedContext.requireOwner && ctx.senderIsOwner !== true)
363
+ return undefined;
364
+ return cfg.delegatedContext.userId;
365
+ };
350
366
  const scopedKey = (value, ctx) => `${agentIdFor(ctx)}:${value}`;
351
367
  const contextIds = (ctx) => [...new Set([ctx.runId, ctx.sessionKey, ctx.sessionId].filter((value) => Boolean(value)).map((value) => scopedKey(value, ctx)))];
352
368
  const callFor = (ctx, name, args, timeoutMs = cfg.recall.timeoutMs) => {
@@ -389,6 +405,7 @@ function register(api) {
389
405
  event_type: eventType,
390
406
  run_id: flightRunId(eventRunId, ctx),
391
407
  agent_id: agentIdFor(ctx),
408
+ workspace_id: workspaceIdFor(ctx),
392
409
  subject_id: Object.keys(cfg.agentSubjects).length ? subjectFor(ctx) : undefined,
393
410
  session_id: ctx.sessionId ?? ctx.sessionKey,
394
411
  tool_call_id: toolCallId,
@@ -548,6 +565,37 @@ function register(api) {
548
565
  await observeTurnInput(event.prompt, ctx, "before_model_resolve", Array.isArray(event.attachments) ? event.attachments.length : 0);
549
566
  });
550
567
  api.on("llm_input", async (event, ctx) => {
568
+ const sessionKey = scopedKey(ctx.sessionKey ?? ctx.sessionId ?? event.sessionId ?? "default-session", ctx);
569
+ const pending = pendingPrompts.get(sessionKey);
570
+ if (pending?.delegatedContext !== undefined) {
571
+ const exact = pending.delegatedContext;
572
+ const promptOccurrences = exact ? event.prompt.split(exact).length - 1 : 0;
573
+ const systemOccurrences = exact
574
+ ? (event.systemPrompt ?? "").split(exact).length - 1
575
+ : 0;
576
+ const occurrences = promptOccurrences + systemOccurrences;
577
+ const deliveredLocation = promptOccurrences === 1
578
+ ? "prompt"
579
+ : systemOccurrences === 1
580
+ ? "systemPrompt"
581
+ : "none";
582
+ const delivered = occurrences === 1 && digestText(exact) === pending.delegatedContextSha256;
583
+ await recordBlackbox("context.injected", event.runId, ctx, {
584
+ disposition: delivered ? "injected" : "recall_failed",
585
+ provider: pending.delegatedAuthority,
586
+ result_sha256: pending.delegatedResultSha256,
587
+ context_sha256: pending.delegatedContextSha256,
588
+ context_chars: exact.length,
589
+ context_byte_length: Buffer.byteLength(exact, "utf8"),
590
+ context_location: deliveredLocation,
591
+ success: delivered,
592
+ reason: delivered ? undefined : `expected one exact delegated segment; observed ${occurrences}`,
593
+ }, undefined, { contextReceiptId: pending.contextReceiptId });
594
+ if (delivered && pending.exposureId) {
595
+ await callFor(ctx, "control_exposure_shown", { exposure_id: pending.exposureId }, cfg.recall.timeoutMs);
596
+ }
597
+ pendingPrompts.set(sessionKey, { ...pending, delegatedContext: undefined });
598
+ }
551
599
  await recordBlackbox("model.input", event.runId, ctx, {
552
600
  provider: event.provider,
553
601
  model: event.model,
@@ -690,13 +738,47 @@ function register(api) {
690
738
  sweep();
691
739
  if (cfg.controlPlane.enabled) {
692
740
  try {
693
- const prepared = (await callFor(ctx, "control_prepare", { query: userText, session_id: sessionKey, agent_id: agentIdFor(ctx) }, cfg.recall.timeoutMs));
741
+ const prepared = (await callFor(ctx, "control_prepare", {
742
+ query: userText,
743
+ session_id: sessionKey,
744
+ host_run_id: ctx.runId,
745
+ turn_id: ctx.runId,
746
+ agent_id: agentIdFor(ctx),
747
+ user_id: delegatedUserIdFor(ctx),
748
+ workspace_id: workspaceIdFor(ctx),
749
+ }, cfg.recall.timeoutMs));
694
750
  pendingPrompts.set(sessionKey, {
695
751
  text: userText,
696
752
  ts: Date.now(),
697
753
  exposureId: prepared.exposure_id,
698
754
  contextReceiptId: prepared.context_receipt_id,
755
+ delegatedContext: prepared.authority === "delegated" && prepared.inject
756
+ ? prepared.context ?? ""
757
+ : undefined,
758
+ delegatedContextSha256: prepared.authority === "delegated" ? prepared.context_sha256 : undefined,
759
+ delegatedAuthority: prepared.authority,
760
+ delegatedResultSha256: prepared.result_sha256,
699
761
  });
762
+ if (prepared.authority === "delegated" &&
763
+ prepared.inject &&
764
+ (!prepared.context ||
765
+ !prepared.context_sha256 ||
766
+ digestText(prepared.context) !== prepared.context_sha256)) {
767
+ throw new Error("delegated context failed exact handoff digest validation");
768
+ }
769
+ if (prepared.authority === "delegated" || prepared.authority === "atmem_fallback") {
770
+ await recordBlackbox("context.provider_authorization", undefined, ctx, {
771
+ disposition: prepared.decision ?? "provider_failure",
772
+ provider: prepared.provider?.id,
773
+ mode: prepared.authority,
774
+ result_sha256: prepared.result_sha256,
775
+ context_sha256: prepared.context_sha256,
776
+ context_byte_length: Buffer.byteLength(prepared.context ?? "", "utf8"),
777
+ context_receipt_sha256: prepared.receipt?.sha256,
778
+ context_chars: (prepared.context ?? "").length,
779
+ success: prepared.decision === "inject" || prepared.decision === "withhold",
780
+ }, undefined, { contextReceiptId: prepared.context_receipt_id });
781
+ }
700
782
  await recordBlackbox("context.disposition", undefined, ctx, {
701
783
  disposition: prepared.inject && prepared.context
702
784
  ? "injected"
@@ -705,20 +787,26 @@ function register(api) {
705
787
  : "no_relevant_memory",
706
788
  context_sha256: digestText(prepared.context ?? ""),
707
789
  context_block_sha256: digestText(prepared.context ?? ""),
708
- context_envelope_sha256: digestJson({ appendContext: prepared.context ?? "" }),
790
+ context_envelope_sha256: digestJson(prepared.authority === "delegated"
791
+ ? { prependContext: prepared.context ?? "" }
792
+ : { appendContext: prepared.context ?? "" }),
709
793
  context_receipt_sha256: prepared.manifest_sha256,
710
794
  digest_profile: "atmem-context-envelope-canonical-json-v1",
711
795
  context_chars: (prepared.context ?? "").length,
712
796
  candidate_ids: prepared.candidate_ids ?? [],
713
797
  exposure_id: prepared.exposure_id,
714
798
  mode: prepared.mode,
715
- context_location: prepared.inject ? "appendContext" : "none",
799
+ context_location: prepared.inject
800
+ ? prepared.authority === "delegated" ? "prependContext" : "appendContext"
801
+ : "none",
716
802
  }, undefined, {
717
803
  contextReceiptId: prepared.context_receipt_id,
718
804
  });
719
805
  if (prepared.inject && prepared.context) {
720
806
  api.logger.info(`${TAG} memory control plane ${prepared.mode ?? "active"} context exposed`);
721
- return { appendContext: prepared.context };
807
+ return prepared.authority === "delegated"
808
+ ? { prependContext: prepared.context }
809
+ : { appendContext: prepared.context };
722
810
  }
723
811
  return;
724
812
  }
@@ -128,7 +128,7 @@ export class AtmemClient {
128
128
  capabilities: {},
129
129
  clientInfo: {
130
130
  name: "openclaw-memory-atmem",
131
- version: "2.2.5",
131
+ version: "2.2.6-beta.2",
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": "2.2.5",
5
+ "version": "2.2.6-beta.2",
6
6
  "commandAliases": ["memory-atmem"],
7
7
  "activation": {
8
8
  "onStartup": true,
@@ -135,6 +135,22 @@
135
135
  "description": "Record content-minimizing agent flights through the private control protocol. Managed automatically by the AtMem installer."
136
136
  }
137
137
  }
138
+ },
139
+ "delegatedContext": {
140
+ "type": "object",
141
+ "description": "Identity mapping used only when AtMem has an explicitly enabled delegated context-provider registration. This does not enable delegation.",
142
+ "properties": {
143
+ "userId": {
144
+ "type": "string",
145
+ "default": "",
146
+ "description": "Opaque authenticated user ID registered with AtMem. Leave empty unless delegated authority is configured."
147
+ },
148
+ "requireOwner": {
149
+ "type": "boolean",
150
+ "default": true,
151
+ "description": "Send the mapped user ID only when OpenClaw marks the sender as owner."
152
+ }
153
+ }
138
154
  }
139
155
  }
140
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-memory-atmem",
3
- "version": "2.2.5",
3
+ "version": "2.2.6-beta.2",
4
4
  "description": "OpenClaw Agent Black Box and memory control-plane bridge for AtMem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -40,7 +40,7 @@
40
40
  "scripts": {
41
41
  "build": "tsc -p tsconfig.json",
42
42
  "typecheck": "tsc -p tsconfig.json --noEmit",
43
- "test": "node test/setup.mjs && node test/hooks.mjs",
43
+ "test": "node test/setup.mjs && node test/hooks.mjs && node test/delegated-context-contract.mjs",
44
44
  "smoke": "node test/smoke.mjs",
45
45
  "prepack": "npm run build && npm run typecheck && npm test && npm run smoke"
46
46
  },