memwal 0.1.1.dev0__tar.gz → 0.1.2__tar.gz

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.
@@ -0,0 +1,34 @@
1
+ # memwal
2
+
3
+ ## 0.1.2
4
+
5
+ ### Added
6
+
7
+ - Added `max_distance` to async and sync `recall()`.
8
+ - Added credential verification helper.
9
+
10
+ ### Changed
11
+
12
+ - Updated docs/examples to use `MEMWAL_PRIVATE_KEY`.
13
+ - Rebranded package metadata and documentation from MemWal to Walrus Memory.
14
+
15
+ ### Fixed
16
+
17
+ - Made `401` relayer errors more actionable.
18
+
19
+ ## 0.1.1
20
+
21
+ ### Added
22
+
23
+ - Added relayer `env` presets.
24
+ - Added compatibility checks and `compatibility()` helpers.
25
+
26
+ ## 0.1.0
27
+
28
+ ### Initial Release
29
+
30
+ - `MemWal` async client and `MemWalSync` sync wrapper
31
+ - Memory APIs: `remember`, `recall`, `analyze`, `ask`, `restore`, `health`
32
+ - Async job helpers for remember, bulk remember, and analyze
33
+ - LangChain/OpenAI middleware and delegate-key utilities
34
+ - Ed25519 delegate-key auth with namespace-scoped memory isolation
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memwal
3
- Version: 0.1.1.dev0
4
- Summary: Python SDK for MemWal — Privacy-first AI memory with Ed25519 signing
3
+ Version: 0.1.2
4
+ Summary: Python SDK for Walrus Memory — Privacy-first AI memory with Ed25519 signing
5
5
  Project-URL: Homepage, https://memwal.ai
6
6
  Project-URL: Documentation, https://docs.memwal.ai
7
7
  Project-URL: Repository, https://github.com/MystenLabs/MemWal
8
- Author: MemWal Team
8
+ Author: Walrus Memory Team
9
9
  License-Expression: MIT
10
- Keywords: ai,ed25519,memory,memwal,privacy,sui,walrus
10
+ Keywords: ai,ed25519,memory,memwal,privacy,sui,walrus,walrus-memory
11
11
  Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: License :: OSI Approved :: MIT License
@@ -37,9 +37,9 @@ Provides-Extra: openai
37
37
  Requires-Dist: openai>=1.0.0; extra == 'openai'
38
38
  Description-Content-Type: text/markdown
39
39
 
40
- # memwal
40
+ # Walrus Memory Python SDK
41
41
 
42
- Python SDK for [MemWal](https://memwal.ai) — Privacy-first AI memory with Ed25519 signing.
42
+ Python SDK for [Walrus Memory](https://memwal.ai) — Privacy-first AI memory with Ed25519 signing.
43
43
 
44
44
  All data processing (encryption, embedding, Walrus storage) happens server-side in a TEE. The SDK signs requests with your Ed25519 delegate key and sends text over HTTPS.
45
45
 
@@ -62,11 +62,14 @@ pip install memwal[all] # Everything
62
62
  Set your environment variables first:
63
63
 
64
64
  ```bash
65
- export MEMWAL_KEY="your-ed25519-delegate-key-hex"
66
- export MEMWAL_ACCOUNT_ID="0x-your-memwal-account-id"
65
+ export MEMWAL_PRIVATE_KEY="your-ed25519-delegate-private-key-hex"
66
+ export MEMWAL_ACCOUNT_ID="0x-your-walrus-memory-account-id"
67
67
  export MEMWAL_SERVER_URL="https://relayer.memwal.ai"
68
68
  ```
69
69
 
70
+ `MEMWAL_PRIVATE_KEY` is the delegate private key from the Walrus Memory dashboard and
71
+ must stay server-side.
72
+
70
73
  ### Async (recommended)
71
74
 
72
75
  ```python
@@ -76,7 +79,7 @@ from memwal import MemWal
76
79
 
77
80
  async def main():
78
81
  memwal = MemWal.create(
79
- key=os.environ["MEMWAL_KEY"],
82
+ key=os.environ["MEMWAL_PRIVATE_KEY"],
80
83
  account_id=os.environ["MEMWAL_ACCOUNT_ID"],
81
84
  server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
82
85
  )
@@ -86,7 +89,7 @@ async def main():
86
89
  print(result.blob_id)
87
90
 
88
91
  # Recall memories
89
- matches = await memwal.recall("food allergies")
92
+ matches = await memwal.recall("food allergies", limit=10, max_distance=0.7)
90
93
  for memory in matches.results:
91
94
  print(f"{memory.text} (relevance: {1 - memory.distance:.2f})")
92
95
 
@@ -107,7 +110,7 @@ import os
107
110
  from memwal import MemWalSync
108
111
 
109
112
  client = MemWalSync.create(
110
- key=os.environ["MEMWAL_KEY"],
113
+ key=os.environ["MEMWAL_PRIVATE_KEY"],
111
114
  account_id=os.environ["MEMWAL_ACCOUNT_ID"],
112
115
  server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
113
116
  )
@@ -124,7 +127,7 @@ import os
124
127
  from memwal import MemWal
125
128
 
126
129
  async with MemWal.create(
127
- key=os.environ["MEMWAL_KEY"],
130
+ key=os.environ["MEMWAL_PRIVATE_KEY"],
128
131
  account_id=os.environ["MEMWAL_ACCOUNT_ID"],
129
132
  ) as memwal:
130
133
  await memwal.remember("I prefer dark mode")
@@ -139,7 +142,7 @@ Same shorthand as the TypeScript SDK and MCP package.
139
142
  from memwal import MemWal
140
143
 
141
144
  memwal = MemWal.create(
142
- key=os.environ["MEMWAL_KEY"],
145
+ key=os.environ["MEMWAL_PRIVATE_KEY"],
143
146
  account_id=os.environ["MEMWAL_ACCOUNT_ID"],
144
147
  env="prod", # prod | dev | staging | local
145
148
  )
@@ -169,7 +172,7 @@ from memwal import with_memwal_langchain
169
172
  llm = ChatOpenAI(model="gpt-4o")
170
173
  smart_llm = with_memwal_langchain(
171
174
  llm,
172
- key=os.environ["MEMWAL_KEY"],
175
+ key=os.environ["MEMWAL_PRIVATE_KEY"],
173
176
  account_id=os.environ["MEMWAL_ACCOUNT_ID"],
174
177
  server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
175
178
  max_memories=5,
@@ -190,7 +193,7 @@ from memwal import with_memwal_openai
190
193
  client = AsyncOpenAI()
191
194
  smart_client = with_memwal_openai(
192
195
  client,
193
- key=os.environ["MEMWAL_KEY"],
196
+ key=os.environ["MEMWAL_PRIVATE_KEY"],
194
197
  account_id=os.environ["MEMWAL_ACCOUNT_ID"],
195
198
  server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
196
199
  )
@@ -213,7 +216,7 @@ Create a new async client.
213
216
  | Method | Description |
214
217
  |--------|-------------|
215
218
  | `await remember(text, namespace?)` | Store a memory |
216
- | `await recall(query, limit?, namespace?)` | Search memories |
219
+ | `await recall(query, limit?, namespace?, max_distance?)` | Search memories, optionally filtering by distance |
217
220
  | `await analyze(text, namespace?)` | Extract and store facts |
218
221
  | `await ask(question, limit?, namespace?)` | Ask a question answered using memories |
219
222
  | `await restore(namespace, limit?)` | Restore a namespace |
@@ -1,6 +1,6 @@
1
- # memwal
1
+ # Walrus Memory Python SDK
2
2
 
3
- Python SDK for [MemWal](https://memwal.ai) — Privacy-first AI memory with Ed25519 signing.
3
+ Python SDK for [Walrus Memory](https://memwal.ai) — Privacy-first AI memory with Ed25519 signing.
4
4
 
5
5
  All data processing (encryption, embedding, Walrus storage) happens server-side in a TEE. The SDK signs requests with your Ed25519 delegate key and sends text over HTTPS.
6
6
 
@@ -23,11 +23,14 @@ pip install memwal[all] # Everything
23
23
  Set your environment variables first:
24
24
 
25
25
  ```bash
26
- export MEMWAL_KEY="your-ed25519-delegate-key-hex"
27
- export MEMWAL_ACCOUNT_ID="0x-your-memwal-account-id"
26
+ export MEMWAL_PRIVATE_KEY="your-ed25519-delegate-private-key-hex"
27
+ export MEMWAL_ACCOUNT_ID="0x-your-walrus-memory-account-id"
28
28
  export MEMWAL_SERVER_URL="https://relayer.memwal.ai"
29
29
  ```
30
30
 
31
+ `MEMWAL_PRIVATE_KEY` is the delegate private key from the Walrus Memory dashboard and
32
+ must stay server-side.
33
+
31
34
  ### Async (recommended)
32
35
 
33
36
  ```python
@@ -37,7 +40,7 @@ from memwal import MemWal
37
40
 
38
41
  async def main():
39
42
  memwal = MemWal.create(
40
- key=os.environ["MEMWAL_KEY"],
43
+ key=os.environ["MEMWAL_PRIVATE_KEY"],
41
44
  account_id=os.environ["MEMWAL_ACCOUNT_ID"],
42
45
  server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
43
46
  )
@@ -47,7 +50,7 @@ async def main():
47
50
  print(result.blob_id)
48
51
 
49
52
  # Recall memories
50
- matches = await memwal.recall("food allergies")
53
+ matches = await memwal.recall("food allergies", limit=10, max_distance=0.7)
51
54
  for memory in matches.results:
52
55
  print(f"{memory.text} (relevance: {1 - memory.distance:.2f})")
53
56
 
@@ -68,7 +71,7 @@ import os
68
71
  from memwal import MemWalSync
69
72
 
70
73
  client = MemWalSync.create(
71
- key=os.environ["MEMWAL_KEY"],
74
+ key=os.environ["MEMWAL_PRIVATE_KEY"],
72
75
  account_id=os.environ["MEMWAL_ACCOUNT_ID"],
73
76
  server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
74
77
  )
@@ -85,7 +88,7 @@ import os
85
88
  from memwal import MemWal
86
89
 
87
90
  async with MemWal.create(
88
- key=os.environ["MEMWAL_KEY"],
91
+ key=os.environ["MEMWAL_PRIVATE_KEY"],
89
92
  account_id=os.environ["MEMWAL_ACCOUNT_ID"],
90
93
  ) as memwal:
91
94
  await memwal.remember("I prefer dark mode")
@@ -100,7 +103,7 @@ Same shorthand as the TypeScript SDK and MCP package.
100
103
  from memwal import MemWal
101
104
 
102
105
  memwal = MemWal.create(
103
- key=os.environ["MEMWAL_KEY"],
106
+ key=os.environ["MEMWAL_PRIVATE_KEY"],
104
107
  account_id=os.environ["MEMWAL_ACCOUNT_ID"],
105
108
  env="prod", # prod | dev | staging | local
106
109
  )
@@ -130,7 +133,7 @@ from memwal import with_memwal_langchain
130
133
  llm = ChatOpenAI(model="gpt-4o")
131
134
  smart_llm = with_memwal_langchain(
132
135
  llm,
133
- key=os.environ["MEMWAL_KEY"],
136
+ key=os.environ["MEMWAL_PRIVATE_KEY"],
134
137
  account_id=os.environ["MEMWAL_ACCOUNT_ID"],
135
138
  server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
136
139
  max_memories=5,
@@ -151,7 +154,7 @@ from memwal import with_memwal_openai
151
154
  client = AsyncOpenAI()
152
155
  smart_client = with_memwal_openai(
153
156
  client,
154
- key=os.environ["MEMWAL_KEY"],
157
+ key=os.environ["MEMWAL_PRIVATE_KEY"],
155
158
  account_id=os.environ["MEMWAL_ACCOUNT_ID"],
156
159
  server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
157
160
  )
@@ -174,7 +177,7 @@ Create a new async client.
174
177
  | Method | Description |
175
178
  |--------|-------------|
176
179
  | `await remember(text, namespace?)` | Store a memory |
177
- | `await recall(query, limit?, namespace?)` | Search memories |
180
+ | `await recall(query, limit?, namespace?, max_distance?)` | Search memories, optionally filtering by distance |
178
181
  | `await analyze(text, namespace?)` | Extract and store facts |
179
182
  | `await ask(question, limit?, namespace?)` | Ask a question answered using memories |
180
183
  | `await restore(namespace, limit?)` | Restore a namespace |
@@ -0,0 +1,10 @@
1
+ # Local server (default) or remote relayer
2
+ MEMWAL_SERVER_URL=http://localhost:8000
3
+ # Ed25519 delegate private key (64-hex). Get from Walrus Memory dashboard.
4
+ MEMWAL_PRIVATE_KEY=21b423e72282dcc47805de48ef9130331b642667b7b2a5cd621767928205e360
5
+ # Optional: paste the dashboard delegate public key so verification can catch mismatches.
6
+ MEMWAL_DELEGATE_PUBLIC_KEY=
7
+ # Walrus Memory account object ID on Sui (the wallet's account)
8
+ MEMWAL_ACCOUNT_ID=0x8a1121b8f95d79e68bd07efaf71689ce6fd832b369cdb1b2a943ec7beb822392
9
+ # Namespace for these test memories
10
+ MEMWAL_NAMESPACE=python-sdk-example
@@ -65,13 +65,13 @@ def _ms(start: float) -> int:
65
65
 
66
66
 
67
67
  async def main() -> None:
68
- server_url = os.environ.get("MEMWAL_SERVER_URL", "http://localhost:3001")
69
- key = os.environ.get("MEMWAL_KEY")
68
+ server_url = os.environ.get("MEMWAL_SERVER_URL", "http://localhost:8000")
69
+ key = os.environ.get("MEMWAL_PRIVATE_KEY")
70
70
  account_id = os.environ.get("MEMWAL_ACCOUNT_ID")
71
71
  namespace = os.environ.get("MEMWAL_NAMESPACE", "python-sdk-example")
72
72
 
73
73
  if not key or not account_id:
74
- print("ERROR: set MEMWAL_KEY + MEMWAL_ACCOUNT_ID in examples/.env")
74
+ print("ERROR: set MEMWAL_PRIVATE_KEY + MEMWAL_ACCOUNT_ID in examples/.env")
75
75
  sys.exit(2)
76
76
 
77
77
  print(f"server : {server_url}")
@@ -94,13 +94,13 @@ def _log_offset() -> int:
94
94
 
95
95
 
96
96
  async def main() -> None:
97
- server_url = os.environ.get("MEMWAL_SERVER_URL", "http://localhost:3001")
98
- key = os.environ.get("MEMWAL_KEY")
97
+ server_url = os.environ.get("MEMWAL_SERVER_URL", "http://localhost:8000")
98
+ key = os.environ.get("MEMWAL_PRIVATE_KEY")
99
99
  account_id = os.environ.get("MEMWAL_ACCOUNT_ID")
100
100
  namespace = os.environ.get("MEMWAL_NAMESPACE", "python-sdk-example")
101
101
 
102
102
  if not key or not account_id:
103
- print("ERROR: set MEMWAL_KEY + MEMWAL_ACCOUNT_ID in examples/.env")
103
+ print("ERROR: set MEMWAL_PRIVATE_KEY + MEMWAL_ACCOUNT_ID in examples/.env")
104
104
  sys.exit(2)
105
105
 
106
106
  print(f"server : {server_url}")
@@ -0,0 +1,67 @@
1
+ """Verify MemWal example credentials before calling the relayer.
2
+
3
+ Usage:
4
+ MEMWAL_PRIVATE_KEY=<hex> MEMWAL_ACCOUNT_ID=0x... python examples/verify_credentials.py
5
+
6
+ Optional:
7
+ Set MEMWAL_DELEGATE_PUBLIC_KEY to the dashboard public key to fail on
8
+ public/private key mismatch.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import re
15
+ import sys
16
+
17
+ import nacl.signing
18
+
19
+ HEX_32_BYTES = re.compile(r"^(0x)?[0-9a-fA-F]{64}$")
20
+ ACCOUNT_ID = re.compile(r"^0x[0-9a-fA-F]{64}$")
21
+
22
+
23
+ def normalize_hex(value: str) -> str:
24
+ value = value.strip()
25
+ return value[2:] if value.lower().startswith("0x") else value
26
+
27
+
28
+ def main() -> None:
29
+ private_key = os.environ.get("MEMWAL_PRIVATE_KEY") or ""
30
+ account_id = os.environ.get("MEMWAL_ACCOUNT_ID") or ""
31
+ expected_public_key = os.environ.get("MEMWAL_DELEGATE_PUBLIC_KEY") or ""
32
+ server_url = os.environ.get("MEMWAL_SERVER_URL") or ""
33
+
34
+ if not private_key:
35
+ raise SystemExit("MEMWAL_PRIVATE_KEY is required")
36
+ if not HEX_32_BYTES.match(private_key):
37
+ raise SystemExit("MEMWAL_PRIVATE_KEY must be a 64-character Ed25519 private key hex string")
38
+ if account_id and not ACCOUNT_ID.match(account_id):
39
+ raise SystemExit("MEMWAL_ACCOUNT_ID must be a 0x-prefixed 32-byte Sui object ID")
40
+
41
+ signing_key = nacl.signing.SigningKey(bytes.fromhex(normalize_hex(private_key)))
42
+ derived_public_key = signing_key.verify_key.encode().hex()
43
+
44
+ if expected_public_key and derived_public_key != normalize_hex(expected_public_key).lower():
45
+ raise SystemExit(
46
+ "MEMWAL_PRIVATE_KEY does not derive MEMWAL_DELEGATE_PUBLIC_KEY. "
47
+ "You may have pasted a public key or a key from another account."
48
+ )
49
+
50
+ print("Walrus Memory credentials look parseable.")
51
+ print(f"Derived delegate public key: {derived_public_key}")
52
+ if account_id:
53
+ print(f"Account ID: {account_id}")
54
+ if server_url:
55
+ print(f"Relayer URL: {server_url}")
56
+ if not expected_public_key:
57
+ print("Set MEMWAL_DELEGATE_PUBLIC_KEY to fail on public/private key mismatch.")
58
+
59
+
60
+ if __name__ == "__main__":
61
+ try:
62
+ main()
63
+ except SystemExit:
64
+ raise
65
+ except Exception as exc:
66
+ print(exc, file=sys.stderr)
67
+ raise SystemExit(1) from exc
@@ -1,5 +1,5 @@
1
1
  """
2
- memwal — Privacy-first AI memory SDK
2
+ Walrus Memory — Privacy-first AI memory SDK
3
3
 
4
4
  Ed25519 delegate key auth + server-side TEE processing.
5
5
 
@@ -32,7 +32,6 @@ from .client import (
32
32
  MemWalSync,
33
33
  )
34
34
  from .middleware import with_memwal_langchain, with_memwal_openai
35
- from .utils import delegate_key_to_sui_address, delegate_key_to_public_key
36
35
  from .types import (
37
36
  ENV_PRESETS,
38
37
  AnalyzedFact,
@@ -61,7 +60,9 @@ from .types import (
61
60
  RememberManualResult,
62
61
  RememberResult,
63
62
  RestoreResult,
63
+ ScoringWeights,
64
64
  )
65
+ from .utils import delegate_key_to_public_key, delegate_key_to_sui_address
65
66
 
66
67
  # JS-style alias for developers coming from the TypeScript SDK
67
68
  withMemWal = with_memwal_langchain
@@ -105,6 +106,7 @@ __all__ = [
105
106
  "AnalyzedFact",
106
107
  "HealthResult",
107
108
  "RestoreResult",
109
+ "ScoringWeights",
108
110
  "RememberManualOptions",
109
111
  "RememberManualResult",
110
112
  "RecallManualOptions",
@@ -112,4 +114,4 @@ __all__ = [
112
114
  "RecallManualResult",
113
115
  ]
114
116
 
115
- __version__ = "0.1.1.dev0"
117
+ __version__ = "0.1.2"
@@ -1,7 +1,7 @@
1
1
  """
2
- memwal — SDK Client
2
+ Walrus Memory — SDK Client
3
3
 
4
- Ed25519 delegate key based client that communicates with the MemWal
4
+ Ed25519 delegate key based client that communicates with the Walrus Memory
5
5
  Rust server (TEE). All data processing (encryption, embedding, Walrus)
6
6
  happens server-side -- the SDK just signs requests and sends text.
7
7
 
@@ -58,7 +58,6 @@ from .types import (
58
58
  RememberBulkResult,
59
59
  RememberBulkStatusItem,
60
60
  RememberBulkStatusResult,
61
- RememberJobStatus,
62
61
  RememberManualOptions,
63
62
  RememberManualResult,
64
63
  RememberResult,
@@ -79,6 +78,11 @@ from .utils import (
79
78
  T = TypeVar("T")
80
79
  SEAL_SESSION_TTL_MIN = 5
81
80
  SEAL_SESSION_SAFETY_MARGIN_MS = 30_000
81
+ AUTH_REJECTED_MESSAGE = (
82
+ "401 from relayer: typically wrong private key, key not registered on this "
83
+ "account, account ID mismatch, or staging/mainnet mismatch. Check .env.local "
84
+ "and dashboard credentials."
85
+ )
82
86
 
83
87
 
84
88
  # ============================================================
@@ -119,7 +123,7 @@ def _is_transient_polling_status(status: int) -> bool:
119
123
 
120
124
 
121
125
  class MemWal:
122
- """Async-native MemWal client.
126
+ """Async-native Walrus Memory client.
123
127
 
124
128
  All API methods are ``async``. For synchronous usage, wrap calls with
125
129
  ``asyncio.run()`` or use the :class:`MemWalSync` convenience wrapper.
@@ -147,11 +151,11 @@ class MemWal:
147
151
  namespace: str = "default",
148
152
  env: Optional[str] = None,
149
153
  ) -> "MemWal":
150
- """Create a new MemWal client instance.
154
+ """Create a new Walrus Memory client instance.
151
155
 
152
156
  Args:
153
157
  key: Ed25519 private key hex string (the delegate key).
154
- account_id: MemWalAccount object ID on Sui.
158
+ account_id: Walrus Memory account object ID on Sui.
155
159
  server_url: Server URL (default: ``http://localhost:8000``).
156
160
  namespace: Default namespace for memory isolation (default: ``"default"``).
157
161
  env: Optional relayer preset — ``"prod"``, ``"dev"``, ``"staging"``,
@@ -472,6 +476,7 @@ class MemWal:
472
476
  query: str,
473
477
  limit: int = 10,
474
478
  namespace: Optional[str] = None,
479
+ max_distance: Optional[float] = None,
475
480
  ) -> RecallResult:
476
481
  """Recall memories similar to a query.
477
482
 
@@ -481,6 +486,8 @@ class MemWal:
481
486
  query: Search query.
482
487
  limit: Max number of results (default: 10).
483
488
  namespace: Override the default namespace.
489
+ max_distance: Optional client-side relevance threshold. Memories with
490
+ ``distance >= max_distance`` are dropped.
484
491
 
485
492
  Returns:
486
493
  :class:`RecallResult` with decrypted text results.
@@ -498,6 +505,9 @@ class MemWal:
498
505
  )
499
506
  for m in data.get("results", [])
500
507
  ]
508
+ if max_distance is not None:
509
+ memories = [m for m in memories if m.distance < max_distance]
510
+ return RecallResult(results=memories, total=len(memories))
501
511
  return RecallResult(results=memories, total=data.get("total", len(memories)))
502
512
 
503
513
  async def analyze(self, text: str, namespace: Optional[str] = None) -> AnalyzeResult:
@@ -717,14 +727,18 @@ class MemWal:
717
727
  Returns:
718
728
  :class:`RecallManualResult` with blob_id + distance pairs.
719
729
  """
730
+ body: Dict[str, Any] = {
731
+ "vector": opts.vector,
732
+ "limit": opts.limit,
733
+ "namespace": opts.namespace or self._namespace,
734
+ }
735
+ if opts.scoring_weights is not None:
736
+ body["scoring_weights"] = opts.scoring_weights.to_wire()
737
+
720
738
  data = await self._signed_request(
721
739
  "POST",
722
740
  "/api/recall/manual",
723
- {
724
- "vector": opts.vector,
725
- "limit": opts.limit,
726
- "namespace": opts.namespace or self._namespace,
727
- },
741
+ body,
728
742
  include_seal_session=False,
729
743
  )
730
744
  hits = [
@@ -763,14 +777,14 @@ class MemWal:
763
777
  health_response = await self._http.get(f"{self._server_url}/health")
764
778
  if health_response.status_code != 200:
765
779
  raise MemWalError(
766
- "MemWal compatibility check failed: "
780
+ "Walrus Memory compatibility check failed: "
767
781
  f"GET /version returned {version_response.status_code}, "
768
782
  f"and GET /health returned {health_response.status_code}"
769
783
  )
770
784
  metadata = health_response.json()
771
785
  else:
772
786
  raise MemWalError(
773
- "MemWal compatibility check failed: "
787
+ "Walrus Memory compatibility check failed: "
774
788
  f"GET /version returned {version_response.status_code}"
775
789
  )
776
790
 
@@ -829,7 +843,8 @@ class MemWal:
829
843
  version = obj.get("version")
830
844
  if str(version) != "1":
831
845
  raise MemWalError(
832
- f"SEAL package {package_id} must be at version 1 to build x-seal-session, got {version!r}"
846
+ f"SEAL package {package_id} must be at version 1 to build "
847
+ f"x-seal-session, got {version!r}"
833
848
  )
834
849
 
835
850
  async def _build_seal_session_inner(self) -> str:
@@ -909,7 +924,7 @@ class MemWal:
909
924
  - ``x-timestamp``: Unix seconds string
910
925
  - ``x-nonce``: UUID v4 replay-protection nonce
911
926
  - ``x-seal-session``: Base64-encoded exported session envelope
912
- - ``x-account-id``: MemWalAccount object ID
927
+ - ``x-account-id``: Walrus Memory account object ID
913
928
  - ``Content-Type``: application/json
914
929
  """
915
930
  import uuid
@@ -958,7 +973,7 @@ class MemWal:
958
973
  err_text = response.text
959
974
  if response.status_code == 426:
960
975
  raise MemWalCompatibilityError(
961
- "MemWal relayer rejected this SDK as unsupported "
976
+ "Walrus Memory relayer rejected this SDK as unsupported "
962
977
  f"(HTTP 426 Upgrade Required). Relayer response: "
963
978
  f"{err_text[:300] or 'upgrade required'}"
964
979
  )
@@ -971,7 +986,7 @@ class MemWal:
971
986
 
972
987
 
973
988
  class MemWalError(Exception):
974
- """Exception raised for MemWal API errors."""
989
+ """Exception raised for Walrus Memory API errors."""
975
990
 
976
991
  pass
977
992
 
@@ -991,7 +1006,10 @@ class _HttpStatusError(MemWalError):
991
1006
  """
992
1007
 
993
1008
  def __init__(self, status: int, body: str) -> None:
994
- super().__init__(f"MemWal API error ({status}): {body}")
1009
+ if status == 401:
1010
+ super().__init__(AUTH_REJECTED_MESSAGE)
1011
+ else:
1012
+ super().__init__(f"Walrus Memory API error ({status}): {body}")
995
1013
  self.status = status
996
1014
  self.body = body
997
1015
 
@@ -1054,7 +1072,7 @@ class MemWalSync:
1054
1072
  namespace: str = "default",
1055
1073
  env: Optional[str] = None,
1056
1074
  ) -> "MemWalSync":
1057
- """Create a synchronous MemWal client.
1075
+ """Create a synchronous Walrus Memory client.
1058
1076
 
1059
1077
  Same parameters as :meth:`MemWal.create` (including the ``env``
1060
1078
  relayer preset).
@@ -1170,10 +1188,14 @@ class MemWalSync:
1170
1188
  return self._run(self._inner.remember_bulk_and_wait(items, opts))
1171
1189
 
1172
1190
  def recall(
1173
- self, query: str, limit: int = 10, namespace: Optional[str] = None
1191
+ self,
1192
+ query: str,
1193
+ limit: int = 10,
1194
+ namespace: Optional[str] = None,
1195
+ max_distance: Optional[float] = None,
1174
1196
  ) -> RecallResult:
1175
1197
  """Synchronous version of :meth:`MemWal.recall`."""
1176
- return self._run(self._inner.recall(query, limit, namespace))
1198
+ return self._run(self._inner.recall(query, limit, namespace, max_distance))
1177
1199
 
1178
1200
  def analyze(self, text: str, namespace: Optional[str] = None) -> AnalyzeResult:
1179
1201
  """Synchronous version of :meth:`MemWal.analyze`."""
@@ -18,33 +18,33 @@ def compatibility_error(metadata: Dict[str, Any], server_url: str) -> Optional[s
18
18
 
19
19
  if not api_version or not relayer_version or not isinstance(min_supported, dict):
20
20
  return (
21
- f"MemWal relayer at {server_url} does not expose compatibility metadata. "
21
+ f"Walrus Memory relayer at {server_url} does not expose compatibility metadata. "
22
22
  "Upgrade the relayer to a version that serves GET /version, or use an older SDK."
23
23
  )
24
24
 
25
25
  api_major = _semver_major(str(api_version))
26
26
  if api_major is None:
27
- return f'MemWal relayer at {server_url} returned invalid apiVersion "{api_version}".'
27
+ return f'Walrus Memory relayer at {server_url} returned invalid apiVersion "{api_version}".'
28
28
 
29
29
  if api_major != SUPPORTED_RELAYER_API_MAJOR:
30
30
  return (
31
- "This MemWal Python SDK supports relayer API "
31
+ "This Walrus Memory Python SDK supports relayer API "
32
32
  f"{SUPPORTED_RELAYER_API_MAJOR}.x, but {server_url} reports apiVersion "
33
33
  f"{api_version}. Upgrade or downgrade the SDK/relayer pair."
34
34
  )
35
35
 
36
36
  min_python = min_supported.get("python")
37
37
  if not isinstance(min_python, str):
38
- return f"MemWal relayer at {server_url} did not report minSupportedSdk.python."
38
+ return f"Walrus Memory relayer at {server_url} did not report minSupportedSdk.python."
39
39
  if _parse_semver(min_python) is None:
40
40
  return (
41
- f'MemWal relayer at {server_url} returned invalid '
41
+ f'Walrus Memory relayer at {server_url} returned invalid '
42
42
  f'minSupportedSdk.python "{min_python}".'
43
43
  )
44
44
 
45
45
  if _compare_semver(MEMWAL_PYTHON_COMPATIBILITY_VERSION, min_python) < 0:
46
46
  return (
47
- f"MemWal relayer at {server_url} requires Python SDK >= {min_python}, "
47
+ f"Walrus Memory relayer at {server_url} requires Python SDK >= {min_python}, "
48
48
  f"but this package supports the {MEMWAL_PYTHON_COMPATIBILITY_VERSION} "
49
49
  "compatibility baseline. Upgrade memwal or use an older compatible relayer."
50
50
  )
@@ -1,5 +1,5 @@
1
1
  """
2
- memwal — AI Middleware
2
+ Walrus Memory — AI Middleware
3
3
 
4
4
  Wraps LangChain and OpenAI SDK clients with automatic memory management.
5
5
  Before each LLM call, relevant memories are recalled and injected.
@@ -145,7 +145,7 @@ def with_memwal_langchain(
145
145
  debug: bool = False,
146
146
  env: Optional[str] = None,
147
147
  ) -> "BaseChatModel":
148
- """Wrap a LangChain ``BaseChatModel`` with MemWal memory management.
148
+ """Wrap a LangChain ``BaseChatModel`` with Walrus Memory management.
149
149
 
150
150
  Before each call:
151
151
  - Recall relevant memories for the last user message
@@ -157,8 +157,8 @@ def with_memwal_langchain(
157
157
  Args:
158
158
  llm: A LangChain ``BaseChatModel`` instance.
159
159
  key: Ed25519 delegate key (hex).
160
- account_id: MemWalAccount object ID.
161
- server_url: MemWal server URL.
160
+ account_id: Walrus Memory account object ID.
161
+ server_url: Walrus Memory server URL.
162
162
  namespace: Default namespace.
163
163
  max_memories: Max memories to inject per request.
164
164
  auto_save: Auto-save new facts from conversation.
@@ -168,7 +168,7 @@ def with_memwal_langchain(
168
168
  ``"local"``). Same precedence as :meth:`MemWal.create`.
169
169
 
170
170
  Returns:
171
- A wrapped ``BaseChatModel`` that automatically uses MemWal memory.
171
+ A wrapped ``BaseChatModel`` that automatically uses Walrus Memory.
172
172
  """
173
173
  try:
174
174
  from langchain_core.messages import HumanMessage, SystemMessage # noqa: F811
@@ -208,7 +208,7 @@ def with_memwal_langchain(
208
208
  return messages
209
209
 
210
210
  memory_context = _format_memories(relevant)
211
- log(f"[MemWal] Found {len(relevant)} relevant memories")
211
+ log(f"[Walrus Memory] Found {len(relevant)} relevant memories")
212
212
 
213
213
  # Insert memory system message before the last user message
214
214
  result = list(messages)
@@ -226,7 +226,7 @@ def with_memwal_langchain(
226
226
 
227
227
  return result
228
228
  except Exception as e:
229
- log(f"[MemWal] Memory search failed: {e}")
229
+ log(f"[Walrus Memory] Memory search failed: {e}")
230
230
  return messages
231
231
 
232
232
  async def _post_analyze(messages: List[BaseMessage]) -> None:
@@ -238,7 +238,7 @@ def with_memwal_langchain(
238
238
  try:
239
239
  await memwal.analyze(user_text, namespace)
240
240
  except Exception as e:
241
- log(f"[MemWal] Auto-save failed: {e}")
241
+ log(f"[Walrus Memory] Auto-save failed: {e}")
242
242
 
243
243
  async def patched_agenerate(
244
244
  messages: List[List[BaseMessage]], *args: Any, **kwargs: Any
@@ -304,7 +304,7 @@ def with_memwal_openai(
304
304
  debug: bool = False,
305
305
  env: Optional[str] = None,
306
306
  ) -> Any:
307
- """Wrap an OpenAI client with MemWal memory management.
307
+ """Wrap an OpenAI client with Walrus Memory management.
308
308
 
309
309
  Works with both ``openai.OpenAI`` (sync) and ``openai.AsyncOpenAI`` (async).
310
310
 
@@ -318,8 +318,8 @@ def with_memwal_openai(
318
318
  Args:
319
319
  client: An ``openai.OpenAI`` or ``openai.AsyncOpenAI`` instance.
320
320
  key: Ed25519 delegate key (hex).
321
- account_id: MemWalAccount object ID.
322
- server_url: MemWal server URL.
321
+ account_id: Walrus Memory account object ID.
322
+ server_url: Walrus Memory server URL.
323
323
  namespace: Default namespace.
324
324
  max_memories: Max memories to inject per request.
325
325
  auto_save: Auto-save new facts from conversation.
@@ -329,7 +329,7 @@ def with_memwal_openai(
329
329
  ``"local"``). Same precedence as :meth:`MemWal.create`.
330
330
 
331
331
  Returns:
332
- The same client, with ``chat.completions.create`` wrapped to use MemWal.
332
+ The same client, with ``chat.completions.create`` wrapped to use Walrus Memory.
333
333
  """
334
334
  memwal = MemWal.create(
335
335
  key=key,
@@ -379,14 +379,14 @@ def _wrap_async_openai(
379
379
  ]
380
380
  if relevant:
381
381
  memory_context = _format_memories(relevant)
382
- log(f"[MemWal] Found {len(relevant)} relevant memories")
382
+ log(f"[Walrus Memory] Found {len(relevant)} relevant memories")
383
383
  messages = _inject_openai_memory(list(messages), memory_context)
384
384
  if "messages" in kwargs:
385
385
  kwargs["messages"] = messages
386
386
  elif args:
387
387
  args = (messages,) + args[1:]
388
388
  except Exception as e:
389
- log(f"[MemWal] Memory search failed: {e}")
389
+ log(f"[Walrus Memory] Memory search failed: {e}")
390
390
 
391
391
  result = await original_create(*args, **kwargs)
392
392
 
@@ -396,7 +396,7 @@ def _wrap_async_openai(
396
396
  try:
397
397
  await memwal.analyze(user_text, namespace)
398
398
  except Exception as e:
399
- log(f"[MemWal] Auto-save failed: {e}")
399
+ log(f"[Walrus Memory] Auto-save failed: {e}")
400
400
 
401
401
  _fire_and_forget(_analyze())
402
402
 
@@ -437,14 +437,14 @@ def _wrap_sync_openai(
437
437
  ]
438
438
  if relevant:
439
439
  memory_context = _format_memories(relevant)
440
- log(f"[MemWal] Found {len(relevant)} relevant memories")
440
+ log(f"[Walrus Memory] Found {len(relevant)} relevant memories")
441
441
  messages = _inject_openai_memory(list(messages), memory_context)
442
442
  if "messages" in kwargs:
443
443
  kwargs["messages"] = messages
444
444
  elif args:
445
445
  args = (messages,) + args[1:]
446
446
  except Exception as e:
447
- log(f"[MemWal] Memory search failed: {e}")
447
+ log(f"[Walrus Memory] Memory search failed: {e}")
448
448
 
449
449
  result = original_create(*args, **kwargs)
450
450
 
@@ -454,7 +454,7 @@ def _wrap_sync_openai(
454
454
  try:
455
455
  await memwal.analyze(user_text, namespace)
456
456
  except Exception as e:
457
- log(f"[MemWal] Auto-save failed: {e}")
457
+ log(f"[Walrus Memory] Auto-save failed: {e}")
458
458
 
459
459
  _fire_and_forget(_analyze())
460
460
 
@@ -1,9 +1,9 @@
1
1
  """
2
- memwal — Core Types
2
+ Walrus Memory — Core Types
3
3
 
4
4
  Dataclasses for all API request options and response types.
5
5
  Ed25519 delegate key based SDK that communicates with
6
- the MemWal Rust server (TEE).
6
+ the Walrus Memory Rust server (TEE).
7
7
  """
8
8
 
9
9
  from __future__ import annotations
@@ -22,7 +22,7 @@ DEFAULT_SERVER_URL = "http://localhost:8000"
22
22
 
23
23
  #: Named relayer environments. Mirrors the TypeScript SDK / MCP package
24
24
  #: ``--prod`` / ``--dev`` / ``--staging`` / ``--local`` presets so the same
25
- #: shorthand works across every MemWal client.
25
+ #: shorthand works across every Walrus Memory client.
26
26
  ENV_PRESETS = {
27
27
  "prod": "https://relayer.memwal.ai",
28
28
  "dev": "https://relayer.dev.memwal.ai",
@@ -33,11 +33,12 @@ ENV_PRESETS = {
33
33
 
34
34
  @dataclass
35
35
  class MemWalConfig:
36
- """Configuration for creating a MemWal client.
36
+ """Configuration for creating a Walrus Memory client.
37
37
 
38
38
  Attributes:
39
- key: Ed25519 private key (hex string). This is the delegate key from app.memwal.com.
40
- account_id: MemWalAccount object ID on Sui.
39
+ key: Ed25519 private key (hex string). This is the delegate key from the
40
+ Walrus Memory dashboard.
41
+ account_id: Walrus Memory account object ID on Sui.
41
42
  server_url: Server URL (default: http://localhost:8000). An explicit
42
43
  non-default value always wins over ``env``.
43
44
  namespace: Default namespace for memory isolation (default: "default").
@@ -99,6 +100,37 @@ class RecallResult:
99
100
  total: int
100
101
 
101
102
 
103
+ @dataclass
104
+ class ScoringWeights:
105
+ """Optional composite-scoring weights for recall ranking.
106
+
107
+ Attributes:
108
+ semantic: Weight applied to semantic similarity (default: 1).
109
+ recency: Weight applied to recency decay (default: 0).
110
+ recency_half_life_days: Half-life for the recency term, in days (default: 30).
111
+ importance: Weight applied to memory importance (default: 0).
112
+ """
113
+
114
+ semantic: Optional[float] = None
115
+ recency: Optional[float] = None
116
+ recency_half_life_days: Optional[float] = None
117
+ importance: Optional[float] = None
118
+
119
+ def to_wire(self) -> Dict[str, float]:
120
+ """Return the snake_case payload expected by the relayer."""
121
+
122
+ payload: Dict[str, float] = {}
123
+ if self.semantic is not None:
124
+ payload["semantic"] = self.semantic
125
+ if self.recency is not None:
126
+ payload["recency"] = self.recency
127
+ if self.recency_half_life_days is not None:
128
+ payload["recency_half_life_days"] = self.recency_half_life_days
129
+ if self.importance is not None:
130
+ payload["importance"] = self.importance
131
+ return payload
132
+
133
+
102
134
  @dataclass
103
135
  class AnalyzedFact:
104
136
  """A single extracted fact."""
@@ -212,11 +244,13 @@ class RecallManualOptions:
212
244
  vector: Pre-computed query embedding vector.
213
245
  limit: Max number of results (default: 10).
214
246
  namespace: Namespace (default: config namespace or "default").
247
+ scoring_weights: Optional composite-scoring weights applied before returning hits.
215
248
  """
216
249
 
217
250
  vector: List[float]
218
251
  limit: int = 10
219
252
  namespace: Optional[str] = None
253
+ scoring_weights: Optional[ScoringWeights] = None
220
254
 
221
255
 
222
256
  @dataclass
@@ -1,5 +1,5 @@
1
1
  """
2
- memwal — Shared Utilities
2
+ Walrus Memory — Shared Utilities
3
3
 
4
4
  Crypto and encoding helpers for Ed25519 signing and SHA-256 hashing.
5
5
  Uses PyNaCl (nacl.signing) as the primary Ed25519 implementation.
@@ -119,7 +119,7 @@ def build_signature_message(
119
119
  path: URL path with query (e.g. ``"/api/remember"``).
120
120
  body_sha256: SHA-256 hex digest of the JSON body string.
121
121
  nonce: UUID v4 sent as the ``x-nonce`` header (required).
122
- account_id: MemWalAccount object ID sent as ``x-account-id``
122
+ account_id: Walrus Memory account object ID sent as ``x-account-id``
123
123
  (required; empty string here will mismatch on server).
124
124
  """
125
125
  return f"{timestamp}.{method}.{path}.{body_sha256}.{nonce}.{account_id}"
@@ -4,15 +4,15 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "memwal"
7
- version = "0.1.1.dev0"
8
- description = "Python SDK for MemWal — Privacy-first AI memory with Ed25519 signing"
7
+ version = "0.1.2"
8
+ description = "Python SDK for Walrus Memory — Privacy-first AI memory with Ed25519 signing"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  requires-python = ">=3.9"
12
12
  authors = [
13
- { name = "MemWal Team" },
13
+ { name = "Walrus Memory Team" },
14
14
  ]
15
- keywords = ["memwal", "ai", "memory", "ed25519", "privacy", "walrus", "sui"]
15
+ keywords = ["memwal", "walrus-memory", "ai", "memory", "ed25519", "privacy", "walrus", "sui"]
16
16
  classifiers = [
17
17
  "Development Status :: 4 - Beta",
18
18
  "Intended Audience :: Developers",
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- MemWal Python SDK — Test Runner
3
+ Walrus Memory Python SDK — Test Runner
4
4
 
5
5
  Runs all tests grouped by category and prints a clean summary.
6
6
 
@@ -8,7 +8,7 @@ Usage:
8
8
  python3 run_tests.py
9
9
 
10
10
  With dev server (integration tests):
11
- MEMWAL_KEY=<hex> MEMWAL_ACCOUNT_ID=0x... MEMWAL_SERVER_URL=https://... python3 run_tests.py
11
+ MEMWAL_PRIVATE_KEY=<hex> MEMWAL_ACCOUNT_ID=0x... MEMWAL_SERVER_URL=https://... python3 run_tests.py
12
12
  """
13
13
 
14
14
  from __future__ import annotations
@@ -27,7 +27,7 @@ DIM = "\033[2m"
27
27
  RESET = "\033[0m"
28
28
 
29
29
  SERVER_URL = os.environ.get("MEMWAL_SERVER_URL", "https://relayer.dev.memwal.ai")
30
- PRIVATE_KEY = os.environ.get("MEMWAL_KEY", "")
30
+ PRIVATE_KEY = os.environ.get("MEMWAL_PRIVATE_KEY", "")
31
31
  ACCOUNT_ID = os.environ.get("MEMWAL_ACCOUNT_ID", "")
32
32
  HAS_KEY = bool(PRIVATE_KEY and ACCOUNT_ID)
33
33
 
@@ -77,7 +77,7 @@ def status(passed: int, failed: int) -> str:
77
77
 
78
78
  def print_header() -> None:
79
79
  print()
80
- print(f"{BOLD}{CYAN} MemWal Python SDK — Test Suite{RESET}")
80
+ print(f"{BOLD}{CYAN} Walrus Memory Python SDK — Test Suite{RESET}")
81
81
  print(f" {DIM}Server: {SERVER_URL}{RESET}")
82
82
  print(f" {DIM}Auth: {'✓ credentials set' if HAS_KEY else '✗ no key (integration tests skipped)'}{RESET}")
83
83
  print()
@@ -192,7 +192,7 @@ def main() -> None:
192
192
  print(f" {'─' * 78}")
193
193
 
194
194
  if not HAS_KEY:
195
- print(f"\n {YELLOW}Integration tests skipped — set MEMWAL_KEY + MEMWAL_ACCOUNT_ID to run them{RESET}")
195
+ print(f"\n {YELLOW}Integration tests skipped — set MEMWAL_PRIVATE_KEY + MEMWAL_ACCOUNT_ID to run them{RESET}")
196
196
  print_totals(total_passed, total_failed, all_failures)
197
197
  return
198
198
 
@@ -17,7 +17,7 @@ import pytest
17
17
  import respx
18
18
 
19
19
  from memwal.client import MemWal, MemWalCompatibilityError, MemWalError
20
- from memwal.types import RecallManualOptions, RememberManualOptions
20
+ from memwal.types import RecallManualOptions, RememberManualOptions, ScoringWeights
21
21
  from memwal.utils import build_signature_message, bytes_to_hex, sha256_hex
22
22
 
23
23
  # ============================================================
@@ -95,7 +95,7 @@ def decode_seal_session_header(request: httpx.Request) -> dict[str, Any]:
95
95
 
96
96
  @pytest.fixture
97
97
  def memwal_client() -> MemWal:
98
- """Create a MemWal client with a test key."""
98
+ """Create a Walrus Memory client with a test key."""
99
99
  return MemWal.create(
100
100
  key=_TEST_KEY_HEX,
101
101
  account_id=_TEST_ACCOUNT_ID,
@@ -290,6 +290,31 @@ class TestRecall:
290
290
  assert "x-seal-session" in headers
291
291
  assert "x-delegate-key" not in headers
292
292
 
293
+ @respx.mock
294
+ async def test_max_distance_filters_results(self, memwal_client: MemWal) -> None:
295
+ """recall() should filter weak matches when max_distance is provided."""
296
+ mock_seal_session_prereqs()
297
+ route = respx.post(f"{_TEST_SERVER}/api/recall").mock(
298
+ return_value=httpx.Response(
299
+ 200,
300
+ json={
301
+ "results": [
302
+ {"blob_id": "b1", "text": "I love coffee", "distance": 0.2},
303
+ {"blob_id": "b2", "text": "I live in Tokyo", "distance": 0.7},
304
+ ],
305
+ "total": 2,
306
+ },
307
+ )
308
+ )
309
+
310
+ result = await memwal_client.recall("coffee", limit=10, max_distance=0.7)
311
+
312
+ body = json.loads(route.calls[0].request.content)
313
+ assert body["limit"] == 10
314
+ assert len(result.results) == 1
315
+ assert result.total == 1
316
+ assert result.results[0].blob_id == "b1"
317
+
293
318
  @respx.mock
294
319
  async def test_get_signed_request_uses_empty_body_hash_and_no_wire_body(
295
320
  self, memwal_client: MemWal
@@ -302,7 +327,11 @@ class TestRecall:
302
327
  )
303
328
  )
304
329
 
305
- result = await memwal_client.wait_for_remember_job("job-1", poll_interval_ms=0, timeout_ms=100)
330
+ result = await memwal_client.wait_for_remember_job(
331
+ "job-1",
332
+ poll_interval_ms=0,
333
+ timeout_ms=100,
334
+ )
306
335
 
307
336
  request = route.calls[0].request
308
337
  assert request.content == b""
@@ -341,6 +370,22 @@ class TestErrorHandling:
341
370
  with pytest.raises(MemWalError, match="401"):
342
371
  await memwal_client.remember("test")
343
372
 
373
+ @respx.mock
374
+ async def test_empty_401_uses_workshop_friendly_message(
375
+ self, memwal_client: MemWal
376
+ ) -> None:
377
+ """Empty-body auth failures should still give actionable guidance."""
378
+ mock_seal_session_prereqs()
379
+ respx.post(f"{_TEST_SERVER}/api/recall").mock(
380
+ return_value=httpx.Response(401, text="")
381
+ )
382
+
383
+ with pytest.raises(
384
+ MemWalError,
385
+ match="wrong private key.*account ID mismatch.*staging/mainnet mismatch",
386
+ ):
387
+ await memwal_client.recall("test")
388
+
344
389
  @respx.mock
345
390
  async def test_500_raises_memwal_error(self, memwal_client: MemWal) -> None:
346
391
  """Server errors should raise MemWalError."""
@@ -542,6 +587,33 @@ class TestManualAPI:
542
587
  assert len(result.results) == 1
543
588
  assert result.results[0].blob_id == "b1"
544
589
 
590
+ @respx.mock
591
+ async def test_recall_manual_forwards_scoring_weights(self, memwal_client: MemWal) -> None:
592
+ _mock_version()
593
+ route = respx.post(f"{_TEST_SERVER}/api/recall/manual").mock(
594
+ return_value=httpx.Response(200, json={"results": [], "total": 0})
595
+ )
596
+
597
+ opts = RecallManualOptions(
598
+ vector=[0.1, 0.2, 0.3],
599
+ limit=5,
600
+ scoring_weights=ScoringWeights(
601
+ semantic=1.0,
602
+ recency=0.5,
603
+ recency_half_life_days=7.0,
604
+ importance=2.0,
605
+ ),
606
+ )
607
+ await memwal_client.recall_manual(opts)
608
+
609
+ body = json.loads(route.calls[0].request.content)
610
+ assert body["scoring_weights"] == {
611
+ "semantic": 1.0,
612
+ "recency": 0.5,
613
+ "recency_half_life_days": 7.0,
614
+ "importance": 2.0,
615
+ }
616
+
545
617
 
546
618
  class TestPublicKey:
547
619
  async def test_get_public_key_hex(self, memwal_client: MemWal) -> None:
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- Integration tests for the MemWal Python SDK against a live server.
3
+ Integration tests for the Walrus Memory Python SDK against a live server.
4
4
 
5
5
  Targets MEMWAL_SERVER_URL (default: https://relayer.dev.memwal.ai).
6
6
 
@@ -12,7 +12,7 @@ No-auth tests (always run, no env vars needed):
12
12
  - Future timestamp → 401
13
13
  - Unregistered key → SDK raises MemWalError
14
14
 
15
- Authenticated tests (require MEMWAL_KEY + MEMWAL_ACCOUNT_ID):
15
+ Authenticated tests (require MEMWAL_PRIVATE_KEY + MEMWAL_ACCOUNT_ID):
16
16
  - remember() acceptance
17
17
  - remember_and_wait()
18
18
  - recall()
@@ -26,10 +26,10 @@ Usage:
26
26
  python -m pytest tests/test_integration.py -v -m "not requires_key"
27
27
 
28
28
  # Run full suite with real credentials
29
- MEMWAL_KEY=<hex> MEMWAL_ACCOUNT_ID=0x... python -m pytest tests/test_integration.py -v
29
+ MEMWAL_PRIVATE_KEY=<hex> MEMWAL_ACCOUNT_ID=0x... python -m pytest tests/test_integration.py -v
30
30
 
31
31
  # Run against dev server using env vars
32
- export MEMWAL_KEY="944aa24c09d8b6d6cc6a8fbedc6dc0942a46e49db7d36596e1b6af6061ec9261"
32
+ export MEMWAL_PRIVATE_KEY="944aa24c09d8b6d6cc6a8fbedc6dc0942a46e49db7d36596e1b6af6061ec9261"
33
33
  export MEMWAL_ACCOUNT_ID="0x70f9a6ff2df0ef6a9ecbfdc3f44c27c289ec3eb0cab5e10a5c07ca6165528565"
34
34
  export MEMWAL_SERVER_URL="https://relayer.dev.memwal.ai"
35
35
  python -m pytest tests/test_integration.py -v
@@ -53,14 +53,14 @@ from memwal.utils import build_signature_message, bytes_to_hex
53
53
  # ── Config ───────────────────────────────────────────────────────────────────
54
54
 
55
55
  SERVER_URL = os.environ.get("MEMWAL_SERVER_URL", "https://relayer.dev.memwal.ai")
56
- PRIVATE_KEY_HEX = os.environ.get("MEMWAL_KEY", "")
56
+ PRIVATE_KEY_HEX = os.environ.get("MEMWAL_PRIVATE_KEY", "")
57
57
  ACCOUNT_ID = os.environ.get("MEMWAL_ACCOUNT_ID", "")
58
58
 
59
59
  HAS_KEY = bool(PRIVATE_KEY_HEX and ACCOUNT_ID)
60
60
 
61
61
  requires_key = pytest.mark.skipif(
62
62
  not HAS_KEY,
63
- reason="MEMWAL_KEY and MEMWAL_ACCOUNT_ID not set",
63
+ reason="MEMWAL_PRIVATE_KEY and MEMWAL_ACCOUNT_ID not set",
64
64
  )
65
65
 
66
66
  # ── Helpers ───────────────────────────────────────────────────────────────────
@@ -9,7 +9,7 @@ Covers:
9
9
  - _format_memories: output format
10
10
  - _inject_openai_memory: insertion position
11
11
 
12
- No actual LLM or MemWal server calls are made — all network traffic is mocked
12
+ No actual LLM or Walrus Memory server calls are made — all network traffic is mocked
13
13
  with ``respx`` and all LLM responses are mocked with ``unittest.mock``.
14
14
  """
15
15
 
@@ -1,8 +0,0 @@
1
- # Local server (default) or remote relayer
2
- MEMWAL_SERVER_URL=http://localhost:3001
3
- # Ed25519 delegate private key (64-hex). Get from MemWal dashboard.
4
- MEMWAL_KEY=21b423e72282dcc47805de48ef9130331b642667b7b2a5cd621767928205e360
5
- # MemWalAccount object ID on Sui (the wallet's account)
6
- MEMWAL_ACCOUNT_ID=0x8a1121b8f95d79e68bd07efaf71689ce6fd832b369cdb1b2a943ec7beb822392
7
- # Namespace for these test memories
8
- MEMWAL_NAMESPACE=python-sdk-example
File without changes
File without changes
File without changes