memwal 0.1.0__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,65 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.so
7
+ *.egg
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ wheels/
12
+ *.whl
13
+ .eggs/
14
+
15
+ # Virtual environments
16
+ .venv/
17
+ venv/
18
+ env/
19
+ ENV/
20
+
21
+ # Test / coverage
22
+ .pytest_cache/
23
+ .coverage
24
+ .coverage.*
25
+ coverage.xml
26
+ htmlcov/
27
+ .tox/
28
+
29
+ # Type checking
30
+ .mypy_cache/
31
+ .dmypy.json
32
+ dmypy.json
33
+ .pyright/
34
+
35
+ # Linting
36
+ .ruff_cache/
37
+
38
+ # Distribution / packaging
39
+ MANIFEST
40
+ pip-wheel-metadata/
41
+ share/python-wheels/
42
+
43
+ # IDE
44
+ .vscode/
45
+ .idea/
46
+ *.swp
47
+ *.swo
48
+ *~
49
+
50
+ # macOS
51
+ .DS_Store
52
+ .AppleDouble
53
+ .LSOverride
54
+
55
+ # Env files
56
+ .env
57
+ .env.*
58
+ !.env.example
59
+
60
+ # Jupyter
61
+ .ipynb_checkpoints/
62
+ *.ipynb
63
+
64
+ # Docs build
65
+ site/
memwal-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,237 @@
1
+ Metadata-Version: 2.4
2
+ Name: memwal
3
+ Version: 0.1.0
4
+ Summary: Python SDK for MemWal — Privacy-first AI memory with Ed25519 signing
5
+ Project-URL: Homepage, https://memwal.ai
6
+ Project-URL: Documentation, https://docs.memwal.ai
7
+ Project-URL: Repository, https://github.com/MystenLabs/MemWal
8
+ Author: MemWal Team
9
+ License-Expression: MIT
10
+ Keywords: ai,ed25519,memory,memwal,privacy,sui,walrus
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.9
23
+ Requires-Dist: httpx>=0.27.0
24
+ Requires-Dist: pynacl>=1.5.0
25
+ Provides-Extra: all
26
+ Requires-Dist: langchain-core>=0.2.0; extra == 'all'
27
+ Requires-Dist: openai>=1.0.0; extra == 'all'
28
+ Provides-Extra: dev
29
+ Requires-Dist: langchain-core>=0.2.0; extra == 'dev'
30
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
31
+ Requires-Dist: pytest>=7.0; extra == 'dev'
32
+ Requires-Dist: respx>=0.21.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
34
+ Provides-Extra: langchain
35
+ Requires-Dist: langchain-core>=0.2.0; extra == 'langchain'
36
+ Provides-Extra: openai
37
+ Requires-Dist: openai>=1.0.0; extra == 'openai'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # memwal
41
+
42
+ Python SDK for [MemWal](https://memwal.ai) — Privacy-first AI memory with Ed25519 signing.
43
+
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
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ pip install memwal
50
+ ```
51
+
52
+ With optional integrations:
53
+
54
+ ```bash
55
+ pip install memwal[langchain] # LangChain support
56
+ pip install memwal[openai] # OpenAI SDK support
57
+ pip install memwal[all] # Everything
58
+ ```
59
+
60
+ ## Quick Start
61
+
62
+ Set your environment variables first:
63
+
64
+ ```bash
65
+ export MEMWAL_KEY="your-ed25519-delegate-key-hex"
66
+ export MEMWAL_ACCOUNT_ID="0x-your-memwal-account-id"
67
+ export MEMWAL_SERVER_URL="https://relayer.memwal.ai"
68
+ ```
69
+
70
+ ### Async (recommended)
71
+
72
+ ```python
73
+ import asyncio
74
+ import os
75
+ from memwal import MemWal
76
+
77
+ async def main():
78
+ memwal = MemWal.create(
79
+ key=os.environ["MEMWAL_KEY"],
80
+ account_id=os.environ["MEMWAL_ACCOUNT_ID"],
81
+ server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
82
+ )
83
+
84
+ # Store a memory
85
+ result = await memwal.remember("I'm allergic to peanuts")
86
+ print(result.blob_id)
87
+
88
+ # Recall memories
89
+ matches = await memwal.recall("food allergies")
90
+ for memory in matches.results:
91
+ print(f"{memory.text} (relevance: {1 - memory.distance:.2f})")
92
+
93
+ # Analyze conversation for facts
94
+ analysis = await memwal.analyze("I love coffee and live in Tokyo")
95
+ for fact in analysis.facts:
96
+ print(fact.text)
97
+
98
+ await memwal.close()
99
+
100
+ asyncio.run(main())
101
+ ```
102
+
103
+ ### Sync
104
+
105
+ ```python
106
+ import os
107
+ from memwal import MemWalSync
108
+
109
+ client = MemWalSync.create(
110
+ key=os.environ["MEMWAL_KEY"],
111
+ account_id=os.environ["MEMWAL_ACCOUNT_ID"],
112
+ server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
113
+ )
114
+
115
+ result = client.remember("I'm allergic to peanuts")
116
+ matches = client.recall("food allergies")
117
+ client.close()
118
+ ```
119
+
120
+ ### Context Manager
121
+
122
+ ```python
123
+ import os
124
+ from memwal import MemWal
125
+
126
+ async with MemWal.create(
127
+ key=os.environ["MEMWAL_KEY"],
128
+ account_id=os.environ["MEMWAL_ACCOUNT_ID"],
129
+ ) as memwal:
130
+ await memwal.remember("I prefer dark mode")
131
+ ```
132
+
133
+ ## Environment Presets
134
+
135
+ Instead of hardcoding a relayer URL, pass `env` to target a hosted relayer.
136
+ Same shorthand as the TypeScript SDK and MCP package.
137
+
138
+ ```python
139
+ from memwal import MemWal
140
+
141
+ memwal = MemWal.create(
142
+ key=os.environ["MEMWAL_KEY"],
143
+ account_id=os.environ["MEMWAL_ACCOUNT_ID"],
144
+ env="prod", # prod | dev | staging | local
145
+ )
146
+ ```
147
+
148
+ | `env` | Relayer URL |
149
+ |-------|-------------|
150
+ | `prod` | `https://relayer.memwal.ai` |
151
+ | `dev` | `https://relayer.dev.memwal.ai` |
152
+ | `staging` | `https://relayer.staging.memwal.ai` |
153
+ | `local` | `http://127.0.0.1:8000` |
154
+
155
+ Precedence: an explicit non-default **`server_url` wins over `env`**, which wins
156
+ over the default. An unknown preset raises `ValueError`. `env` is also accepted
157
+ by `MemWalSync.create`, `with_memwal_langchain`, and `with_memwal_openai`.
158
+
159
+ ## AI Middleware
160
+
161
+ ### LangChain
162
+
163
+ ```python
164
+ import os
165
+ from langchain_openai import ChatOpenAI
166
+ from langchain_core.messages import HumanMessage
167
+ from memwal import with_memwal_langchain
168
+
169
+ llm = ChatOpenAI(model="gpt-4o")
170
+ smart_llm = with_memwal_langchain(
171
+ llm,
172
+ key=os.environ["MEMWAL_KEY"],
173
+ account_id=os.environ["MEMWAL_ACCOUNT_ID"],
174
+ server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
175
+ max_memories=5,
176
+ min_relevance=0.3,
177
+ )
178
+
179
+ # Memories are automatically recalled and injected
180
+ response = await smart_llm.ainvoke([HumanMessage("What are my food allergies?")])
181
+ ```
182
+
183
+ ### OpenAI SDK
184
+
185
+ ```python
186
+ import os
187
+ from openai import AsyncOpenAI
188
+ from memwal import with_memwal_openai
189
+
190
+ client = AsyncOpenAI()
191
+ smart_client = with_memwal_openai(
192
+ client,
193
+ key=os.environ["MEMWAL_KEY"],
194
+ account_id=os.environ["MEMWAL_ACCOUNT_ID"],
195
+ server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
196
+ )
197
+
198
+ # Memories are automatically recalled and injected
199
+ response = await smart_client.chat.completions.create(
200
+ model="gpt-4o",
201
+ messages=[{"role": "user", "content": "What are my food allergies?"}],
202
+ )
203
+ ```
204
+
205
+ ## API Reference
206
+
207
+ ### `MemWal.create(key, account_id, server_url?, namespace?)`
208
+
209
+ Create a new async client.
210
+
211
+ ### Methods
212
+
213
+ | Method | Description |
214
+ |--------|-------------|
215
+ | `await remember(text, namespace?)` | Store a memory |
216
+ | `await recall(query, limit?, namespace?)` | Search memories |
217
+ | `await analyze(text, namespace?)` | Extract and store facts |
218
+ | `await ask(question, limit?, namespace?)` | Ask a question answered using memories |
219
+ | `await restore(namespace, limit?)` | Restore a namespace |
220
+ | `await health()` | Check server health |
221
+ | `await remember_manual(opts)` | Store with pre-computed vector |
222
+ | `await recall_manual(opts)` | Search with pre-computed vector |
223
+ | `await get_public_key_hex()` | Get Ed25519 public key |
224
+
225
+ ## Authentication
226
+
227
+ Every request is signed with Ed25519:
228
+
229
+ ```
230
+ message = f"{timestamp}.{method}.{path}.{sha256(body)}"
231
+ ```
232
+
233
+ Headers sent: `x-public-key`, `x-signature`, `x-timestamp`, `x-delegate-key`, `x-account-id`.
234
+
235
+ ## License
236
+
237
+ MIT
memwal-0.1.0/README.md ADDED
@@ -0,0 +1,198 @@
1
+ # memwal
2
+
3
+ Python SDK for [MemWal](https://memwal.ai) — Privacy-first AI memory with Ed25519 signing.
4
+
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
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install memwal
11
+ ```
12
+
13
+ With optional integrations:
14
+
15
+ ```bash
16
+ pip install memwal[langchain] # LangChain support
17
+ pip install memwal[openai] # OpenAI SDK support
18
+ pip install memwal[all] # Everything
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ Set your environment variables first:
24
+
25
+ ```bash
26
+ export MEMWAL_KEY="your-ed25519-delegate-key-hex"
27
+ export MEMWAL_ACCOUNT_ID="0x-your-memwal-account-id"
28
+ export MEMWAL_SERVER_URL="https://relayer.memwal.ai"
29
+ ```
30
+
31
+ ### Async (recommended)
32
+
33
+ ```python
34
+ import asyncio
35
+ import os
36
+ from memwal import MemWal
37
+
38
+ async def main():
39
+ memwal = MemWal.create(
40
+ key=os.environ["MEMWAL_KEY"],
41
+ account_id=os.environ["MEMWAL_ACCOUNT_ID"],
42
+ server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
43
+ )
44
+
45
+ # Store a memory
46
+ result = await memwal.remember("I'm allergic to peanuts")
47
+ print(result.blob_id)
48
+
49
+ # Recall memories
50
+ matches = await memwal.recall("food allergies")
51
+ for memory in matches.results:
52
+ print(f"{memory.text} (relevance: {1 - memory.distance:.2f})")
53
+
54
+ # Analyze conversation for facts
55
+ analysis = await memwal.analyze("I love coffee and live in Tokyo")
56
+ for fact in analysis.facts:
57
+ print(fact.text)
58
+
59
+ await memwal.close()
60
+
61
+ asyncio.run(main())
62
+ ```
63
+
64
+ ### Sync
65
+
66
+ ```python
67
+ import os
68
+ from memwal import MemWalSync
69
+
70
+ client = MemWalSync.create(
71
+ key=os.environ["MEMWAL_KEY"],
72
+ account_id=os.environ["MEMWAL_ACCOUNT_ID"],
73
+ server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
74
+ )
75
+
76
+ result = client.remember("I'm allergic to peanuts")
77
+ matches = client.recall("food allergies")
78
+ client.close()
79
+ ```
80
+
81
+ ### Context Manager
82
+
83
+ ```python
84
+ import os
85
+ from memwal import MemWal
86
+
87
+ async with MemWal.create(
88
+ key=os.environ["MEMWAL_KEY"],
89
+ account_id=os.environ["MEMWAL_ACCOUNT_ID"],
90
+ ) as memwal:
91
+ await memwal.remember("I prefer dark mode")
92
+ ```
93
+
94
+ ## Environment Presets
95
+
96
+ Instead of hardcoding a relayer URL, pass `env` to target a hosted relayer.
97
+ Same shorthand as the TypeScript SDK and MCP package.
98
+
99
+ ```python
100
+ from memwal import MemWal
101
+
102
+ memwal = MemWal.create(
103
+ key=os.environ["MEMWAL_KEY"],
104
+ account_id=os.environ["MEMWAL_ACCOUNT_ID"],
105
+ env="prod", # prod | dev | staging | local
106
+ )
107
+ ```
108
+
109
+ | `env` | Relayer URL |
110
+ |-------|-------------|
111
+ | `prod` | `https://relayer.memwal.ai` |
112
+ | `dev` | `https://relayer.dev.memwal.ai` |
113
+ | `staging` | `https://relayer.staging.memwal.ai` |
114
+ | `local` | `http://127.0.0.1:8000` |
115
+
116
+ Precedence: an explicit non-default **`server_url` wins over `env`**, which wins
117
+ over the default. An unknown preset raises `ValueError`. `env` is also accepted
118
+ by `MemWalSync.create`, `with_memwal_langchain`, and `with_memwal_openai`.
119
+
120
+ ## AI Middleware
121
+
122
+ ### LangChain
123
+
124
+ ```python
125
+ import os
126
+ from langchain_openai import ChatOpenAI
127
+ from langchain_core.messages import HumanMessage
128
+ from memwal import with_memwal_langchain
129
+
130
+ llm = ChatOpenAI(model="gpt-4o")
131
+ smart_llm = with_memwal_langchain(
132
+ llm,
133
+ key=os.environ["MEMWAL_KEY"],
134
+ account_id=os.environ["MEMWAL_ACCOUNT_ID"],
135
+ server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
136
+ max_memories=5,
137
+ min_relevance=0.3,
138
+ )
139
+
140
+ # Memories are automatically recalled and injected
141
+ response = await smart_llm.ainvoke([HumanMessage("What are my food allergies?")])
142
+ ```
143
+
144
+ ### OpenAI SDK
145
+
146
+ ```python
147
+ import os
148
+ from openai import AsyncOpenAI
149
+ from memwal import with_memwal_openai
150
+
151
+ client = AsyncOpenAI()
152
+ smart_client = with_memwal_openai(
153
+ client,
154
+ key=os.environ["MEMWAL_KEY"],
155
+ account_id=os.environ["MEMWAL_ACCOUNT_ID"],
156
+ server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
157
+ )
158
+
159
+ # Memories are automatically recalled and injected
160
+ response = await smart_client.chat.completions.create(
161
+ model="gpt-4o",
162
+ messages=[{"role": "user", "content": "What are my food allergies?"}],
163
+ )
164
+ ```
165
+
166
+ ## API Reference
167
+
168
+ ### `MemWal.create(key, account_id, server_url?, namespace?)`
169
+
170
+ Create a new async client.
171
+
172
+ ### Methods
173
+
174
+ | Method | Description |
175
+ |--------|-------------|
176
+ | `await remember(text, namespace?)` | Store a memory |
177
+ | `await recall(query, limit?, namespace?)` | Search memories |
178
+ | `await analyze(text, namespace?)` | Extract and store facts |
179
+ | `await ask(question, limit?, namespace?)` | Ask a question answered using memories |
180
+ | `await restore(namespace, limit?)` | Restore a namespace |
181
+ | `await health()` | Check server health |
182
+ | `await remember_manual(opts)` | Store with pre-computed vector |
183
+ | `await recall_manual(opts)` | Search with pre-computed vector |
184
+ | `await get_public_key_hex()` | Get Ed25519 public key |
185
+
186
+ ## Authentication
187
+
188
+ Every request is signed with Ed25519:
189
+
190
+ ```
191
+ message = f"{timestamp}.{method}.{path}.{sha256(body)}"
192
+ ```
193
+
194
+ Headers sent: `x-public-key`, `x-signature`, `x-timestamp`, `x-delegate-key`, `x-account-id`.
195
+
196
+ ## License
197
+
198
+ MIT
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,2 @@
1
+ # Local credentials — never commit. Use .env.example as template.
2
+ .env
@@ -0,0 +1,179 @@
1
+ """End-to-end smoke test for the async remember family (PR #121 / ENG-1406+1408).
2
+
3
+ Loads credentials from ./.env (see ``.env.example``), then walks every new
4
+ method that was added to mirror the TypeScript SDK:
5
+
6
+ 1. health() sanity check
7
+ 2. remember() returns RememberAcceptedResult (~500ms)
8
+ 3. wait_for_remember_job() polls until ``done`` (~upload time)
9
+ 4. remember_and_wait() single call doing both
10
+ 5. recall() verify the new memory came back
11
+ 6. remember_bulk_and_wait() 3 facts in one call
12
+ 7. analyze_and_wait() LLM extracts facts + waits for them all
13
+ 8. embed() raw embedding vector
14
+
15
+ Run::
16
+
17
+ cd packages/python-sdk-memwal
18
+ cp examples/.env.example examples/.env # then fill in real values
19
+ python3 examples/async_remember_demo.py
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import os
25
+ import sys
26
+ import time
27
+ from pathlib import Path
28
+ from typing import Optional
29
+
30
+
31
+ def _load_env() -> None:
32
+ """Tiny .env loader so the demo doesn't need python-dotenv."""
33
+
34
+ env_path = Path(__file__).parent / ".env"
35
+ if not env_path.exists():
36
+ return
37
+ for line in env_path.read_text().splitlines():
38
+ line = line.strip()
39
+ if not line or line.startswith("#") or "=" not in line:
40
+ continue
41
+ key, _, value = line.partition("=")
42
+ os.environ.setdefault(key.strip(), value.strip())
43
+
44
+
45
+ _load_env()
46
+
47
+ # Make ``import memwal`` work when running directly from the repo without
48
+ # `pip install -e .`.
49
+ sys.path.insert(0, str(Path(__file__).parent.parent))
50
+
51
+ from memwal import ( # noqa: E402
52
+ MemWal,
53
+ MemWalRememberJobFailed,
54
+ RememberBulkItem,
55
+ RememberBulkOptions,
56
+ )
57
+
58
+
59
+ def _section(title: str) -> None:
60
+ print(f"\n──────── {title} ────────")
61
+
62
+
63
+ def _ms(start: float) -> int:
64
+ return int((time.monotonic() - start) * 1000)
65
+
66
+
67
+ async def main() -> None:
68
+ server_url = os.environ.get("MEMWAL_SERVER_URL", "http://localhost:3001")
69
+ key = os.environ.get("MEMWAL_KEY")
70
+ account_id = os.environ.get("MEMWAL_ACCOUNT_ID")
71
+ namespace = os.environ.get("MEMWAL_NAMESPACE", "python-sdk-example")
72
+
73
+ if not key or not account_id:
74
+ print("ERROR: set MEMWAL_KEY + MEMWAL_ACCOUNT_ID in examples/.env")
75
+ sys.exit(2)
76
+
77
+ print(f"server : {server_url}")
78
+ print(f"account_id : {account_id[:14]}...")
79
+ print(f"namespace : {namespace}")
80
+
81
+ async with MemWal.create(
82
+ key=key,
83
+ account_id=account_id,
84
+ server_url=server_url,
85
+ namespace=namespace,
86
+ ) as memwal:
87
+ # 1. health
88
+ _section("1. health()")
89
+ h = await memwal.health()
90
+ print(f" status={h.status} version={h.version}")
91
+
92
+ # 2. remember() → 202 + job_id (PR #121: should return in ~500ms)
93
+ _section("2. remember() — returns 202 + job_id")
94
+ t0 = time.monotonic()
95
+ accepted = await memwal.remember(
96
+ "Python SDK demo: I prefer FastAPI over Flask for async HTTP services."
97
+ )
98
+ accept_ms = _ms(t0)
99
+ print(f" accept_ms={accept_ms} job_id={accepted.job_id} status={accepted.status}")
100
+ assert accepted.job_id, "expected job_id"
101
+ assert accept_ms < 5000, f"expected <5s response, got {accept_ms}ms"
102
+
103
+ # 3. wait_for_remember_job → polls until "done"
104
+ _section("3. wait_for_remember_job(job_id)")
105
+ t1 = time.monotonic()
106
+ result = await memwal.wait_for_remember_job(accepted.job_id, timeout_ms=60_000)
107
+ wait_ms = _ms(t1)
108
+ print(f" wait_ms={wait_ms} blob_id={result.blob_id} ns={result.namespace}")
109
+
110
+ # 4. remember_and_wait — convenience
111
+ _section("4. remember_and_wait()")
112
+ t2 = time.monotonic()
113
+ full = await memwal.remember_and_wait(
114
+ "Python SDK demo: I drink black coffee in the morning.",
115
+ timeout_ms=60_000,
116
+ )
117
+ total_ms = _ms(t2)
118
+ print(f" total_ms={total_ms} blob_id={full.blob_id}")
119
+
120
+ # 5. recall — confirm the memories are searchable
121
+ _section("5. recall('coffee')")
122
+ rc = await memwal.recall("coffee", limit=3)
123
+ print(f" found {len(rc.results)} / total={rc.total}")
124
+ for m in rc.results[:3]:
125
+ print(f" [{m.distance:.3f}] {m.text[:80]}")
126
+
127
+ # 6. remember_bulk_and_wait — 3 items in one call
128
+ _section("6. remember_bulk_and_wait() — 3 items")
129
+ t3 = time.monotonic()
130
+ bulk = await memwal.remember_bulk_and_wait(
131
+ [
132
+ RememberBulkItem(text="Bulk demo 1: Trees clean the air."),
133
+ RememberBulkItem(text="Bulk demo 2: Coffee comes from beans."),
134
+ RememberBulkItem(text="Bulk demo 3: Mountains are usually cold."),
135
+ ],
136
+ opts=RememberBulkOptions(poll_interval_ms=2000, timeout_ms=120_000),
137
+ )
138
+ bulk_ms = _ms(t3)
139
+ print(
140
+ f" bulk_ms={bulk_ms} total={bulk.total} ok={bulk.succeeded} "
141
+ f"failed={bulk.failed} timed_out={bulk.timed_out}"
142
+ )
143
+ for r in bulk.results:
144
+ print(f" [{r.status}] id={r.id[:8]}... blob={r.blob_id[:20]}...")
145
+
146
+ # 7. analyze_and_wait — LLM splits text into facts + persists each
147
+ _section("7. analyze_and_wait()")
148
+ t4 = time.monotonic()
149
+ try:
150
+ an = await memwal.analyze_and_wait(
151
+ "Today I learned that the Pacific is the largest ocean and "
152
+ "that octopuses have three hearts.",
153
+ opts=RememberBulkOptions(timeout_ms=120_000),
154
+ )
155
+ ana_ms = _ms(t4)
156
+ print(
157
+ f" analyze_ms={ana_ms} facts={len(an.facts)} "
158
+ f"ok={an.succeeded} failed={an.failed} timed_out={an.timed_out}"
159
+ )
160
+ for f, r in zip(an.facts, an.results):
161
+ print(f" fact='{f.text[:60]}' status={r.status} blob={r.blob_id[:20]}...")
162
+ except Exception as e:
163
+ print(f" skipped (analyze requires server LLM key): {e}")
164
+
165
+ # 8. embed — raw embedding vector
166
+ _section("8. embed('hello world')")
167
+ try:
168
+ emb = await memwal.embed("hello world")
169
+ print(f" vector dims={len(emb.vector)} first 5={emb.vector[:5]}")
170
+ except Exception as e:
171
+ print(f" skipped (server may not expose /api/embed yet): {e}")
172
+
173
+ print("\n✅ all sections completed")
174
+
175
+
176
+ if __name__ == "__main__":
177
+ import asyncio
178
+
179
+ asyncio.run(main())