Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions .changeset/walm-55-occurred-at-openclaw.md

This file was deleted.

11 changes: 0 additions & 11 deletions .changeset/walm-55-occurred-at-sdk.md

This file was deleted.

1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"group": "Python SDK",
"pages": [
"python-sdk/quick-start",
"python-sdk/colab",
{
"group": "Usage",
"root": "python-sdk/usage",
Expand Down
4 changes: 2 additions & 2 deletions docs/python-sdk/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ MemWal.create(
| Argument | Type | Required | Default | Notes |
| --- | --- | --- | --- | --- |
| `key` | `str` | Yes | — | Ed25519 delegate private key in hex |
| `account_id` | `str` | Yes | — | MemWalAccount object ID on Sui |
| `account_id` | `str` | Yes | — | Walrus Memory account object ID on Sui |
| `server_url` | `str` | No | `http://localhost:8000` | Explicit relayer URL — wins over `env` |
| `namespace` | `str` | No | `"default"` | Default namespace for memory isolation |
| `env` | `str` | No | — | Preset: `prod` / `dev` / `staging` / `local`. Unknown → `ValueError` |
| `env` | `str` | No | — | Hosted preset: `staging` for testing or `prod` for production. Unknown → `ValueError` |

You may also build a `MemWalConfig` and call `MemWal(config)` directly; `env` resolution happens in `MemWalConfig.__post_init__`.

Expand Down
24 changes: 23 additions & 1 deletion docs/python-sdk/changelog.mdx
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
---
title: "Changelog"
description: "Release history for the MemWal Python SDK."
description: "Release history for the Walrus Memory Python SDK."
---

Track what's new, changed, and fixed in `memwal` (Python).

For the latest version, see the [PyPI project page](https://pypi.org/project/memwal/).

## Unreleased

### Added

- Added a runnable [Walrus Memory Python SDK Colab](https://colab.research.google.com/drive/1SaKjkSp0DXnM_nktWSiEC-l9qGtVr6ph) covering installation, secure `staging` configuration, optional `prod`, `MemWalSync`, health/compatibility checks, delegate public-key/address derivation, `remember`, `remember_async`, async job waiting, `recall`, bulk remember, `remember_bulk_async`, `remember_bulk_and_wait`, optional `ask`, `analyze`, `analyze_and_wait`, `embed`, manual methods with scoring weights, `restore`, optional OpenAI/LangChain middleware, OpenAI-compatible provider settings such as `OPENAI_BASE_URL`, and troubleshooting.

### Fixed

- Fixed `MemWalSync` reuse inside notebooks so repeated calls do not reuse an HTTP transport from a closed event loop.

## 0.1.4

### Added

- Added optional `occurred_at` to `analyze()` and `analyze_and_wait()` (both async and sync) for temporal anchoring of extracted facts. When supplied, the server resolves in-turn relative references ("last Friday", "yesterday") into absolute dates inside the extracted fact text before embedding and encryption.
- Accepts `datetime` or RFC-3339 string. Wire format is RFC-3339 UTC with millisecond precision (e.g. `"2023-05-25T17:50:00.000Z"`) — byte-identical to the TypeScript SDK.
- Field is omitted from the request body when not supplied.

### Changed

- `occurred_at` validates input at the SDK boundary rather than forwarding malformed values to the server: naive `datetime` instances raise `ValueError` (silently assuming UTC would mis-anchor by N hours for callers outside UTC), and malformed RFC-3339 strings raise `ValueError` with a diagnostic message instead of surfacing as opaque 400s.

## 0.1.3

### Added
Expand Down
24 changes: 24 additions & 0 deletions docs/python-sdk/colab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
title: "Colab Notebook"
description: "Run the Walrus Memory Python SDK from Google Colab."
---

Use the runnable [Walrus Memory Python SDK Colab](https://colab.research.google.com/drive/1SaKjkSp0DXnM_nktWSiEC-l9qGtVr6ph) when you want a notebook-first walkthrough.

The notebook covers:

- installing `memwal`
- loading credentials through Colab Secrets or hidden prompts
- configuring the SDK without exposing private keys, defaulting to `staging`
- switching to `prod` when you have production credentials
- creating `MemWalSync` for notebook-friendly calls
- checking relayer `health`, compatibility, and delegate public key derivation
- storing memory with `remember`
- waiting for async remember jobs to persist on Walrus
- retrieving memory with `recall`
- using `remember_async`, `remember_and_wait`, bulk remember, `remember_bulk_async`, `remember_bulk_and_wait`, `ask`, `analyze`, `analyze_and_wait`, `embed`, manual search/register with scoring weights, and `restore`
- optionally wrapping OpenAI and LangChain clients with Walrus Memory middleware
- using `OPENAI_BASE_URL` for OpenAI-compatible providers such as OpenRouter
- basic troubleshooting for auth, namespaces, and async remember jobs

The repo copy lives in [`packages/python-sdk-memwal/notebooks/walrus_memory_python_sdk.ipynb`](https://github.com/MystenLabs/MemWal/blob/main/packages/python-sdk-memwal/notebooks/walrus_memory_python_sdk.ipynb).
12 changes: 7 additions & 5 deletions docs/python-sdk/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ The Walrus Memory Python SDK (`memwal` on PyPI) gives your agents portable memor
pip install memwal
```

## Try It In Colab

Open the runnable [Walrus Memory Python SDK Colab](https://colab.research.google.com/drive/1SaKjkSp0DXnM_nktWSiEC-l9qGtVr6ph) for a notebook walkthrough covering installation, secure configuration, health checks, `remember`, `remember_async`, async job waiting, `recall`, bulk remember, `remember_bulk_async`, `remember_bulk_and_wait`, optional SDK utilities, OpenAI/LangChain middleware, OpenAI-compatible provider settings such as `OPENAI_BASE_URL`, and basic troubleshooting. It defaults to `staging` for test credentials and can switch to `prod` for production credentials.

Optional integrations:

<CodeGroup>
Expand Down Expand Up @@ -53,21 +57,19 @@ Before wiring the SDK into your app:
| Argument | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `key` | `str` | Yes | — | Ed25519 delegate private key in hex |
| `account_id` | `str` | Yes | — | MemWalAccount object ID on Sui |
| `account_id` | `str` | Yes | — | Walrus Memory account object ID on Sui |
| `server_url` | `str` | No | `http://localhost:8000` | Explicit relayer URL — wins over `env` |
| `namespace` | `str` | No | `"default"` | Default namespace for memory isolation |
| `env` | `str` | No | — | Relayer preset: `prod` / `dev` / `staging` / `local` |
| `env` | `str` | No | — | Hosted relayer preset: `staging` for testing or `prod` for production |

### Environment presets

Instead of hardcoding a URL, pass `env`. Same shorthand as the TypeScript SDK and MCP package.
Instead of hardcoding a URL, pass `env`. The public docs and Colab example use `staging` for testing and `prod` for production credentials.

| `env` | Relayer URL |
| --- | --- |
| `prod` | `https://relayer.memwal.ai` |
| `dev` | `https://relayer.dev.memwal.ai` |
| `staging` | `https://relayer.staging.memwal.ai` |
| `local` | `http://127.0.0.1:8000` |

Precedence: an explicit non-default `server_url` > `env` > the default. An unknown preset raises `ValueError`.

Expand Down
4 changes: 2 additions & 2 deletions docs/python-sdk/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Detailed pages:
from memwal import MemWal, RecallParams

memwal = MemWal.create(key="...", account_id="0x...", env="prod")
job = await memwal.remember("User prefers dark mode.")
done = await memwal.remember_and_wait("User prefers dark mode.")
result = await memwal.recall(RecallParams(query="preferences"))
await memwal.close()
```
Expand All @@ -36,7 +36,7 @@ await memwal.close()
from memwal import MemWalSync, RecallParams

client = MemWalSync.create(key="...", account_id="0x...", env="prod")
client.remember("User prefers dark mode.")
client.remember_and_wait("User prefers dark mode.")
result = client.recall(RecallParams(query="preferences"))
client.close()
```
Expand Down
2 changes: 1 addition & 1 deletion docs/python-sdk/usage/with-memwal.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Both wrappers accept the same keyword arguments:
| Option | Default | Description |
| --- | --- | --- |
| `server_url` | `http://localhost:8000` | Explicit relayer URL (wins over `env`) |
| `env` | — | Relayer preset: `prod` / `dev` / `staging` / `local` |
| `env` | — | Hosted relayer preset: `staging` for testing or `prod` for production |
| `namespace` | `"default"` | Memory namespace |
| `max_memories` | `5` | Max memories injected per request |
| `auto_save` | `True` | Auto-save new facts from the conversation |
Expand Down
18 changes: 18 additions & 0 deletions packages/openclaw-memory-memwal/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# @mysten-incubation/oc-memwal

## 0.0.4

### Patch Changes

- [#218](https://github.com/MystenLabs/MemWal/pull/218) [`333d327`](https://github.com/MystenLabs/MemWal/commit/333d3279f59c2a033225bc99238b7586474333fb) Thanks [@hungtranphamminh](https://github.com/hungtranphamminh)! - Wire temporal anchoring through the agent-side memory tools.

### Added

- `memory_store` tool now accepts an optional `occurredAt` argument (RFC-3339 / ISO-8601 string) so agents can anchor recounted past events to the date they actually occurred. Description tells the LLM to omit it when unknown rather than guess.

### Changed

- Auto-capture hook (`agent_end`) now passes `new Date()` as `occurredAt` to `analyze()`. Every captured conversation now gets temporal anchoring automatically — the server extractor resolves in-turn relative references ("yesterday", "last Friday") into absolute dates inside the stored fact text. Facts captured by this version now carry resolved dates.
- SDK dependency bumped from published `^0.0.2` to `workspace:*` to consume the new `AnalyzeOptions` signature.

- Updated dependencies [[`333d327`](https://github.com/MystenLabs/MemWal/commit/333d3279f59c2a033225bc99238b7586474333fb)]:
- @mysten-incubation/memwal@0.0.7

## 0.0.3

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/openclaw-memory-memwal/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mysten-incubation/oc-memwal",
"version": "0.0.3",
"version": "0.0.4",
"type": "module",
"description": "NemoClaw/OpenClaw memory plugin — encrypted, decentralized long-term memory via Walrus Memory",
"license": "Apache-2.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/python-sdk-memwal/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ share/python-wheels/
# Jupyter
.ipynb_checkpoints/
*.ipynb
!notebooks/
!notebooks/walrus_memory_python_sdk.ipynb

# Docs build
site/
10 changes: 10 additions & 0 deletions packages/python-sdk-memwal/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# memwal

## Unreleased

### Added

- Added a runnable [Walrus Memory Python SDK Colab](https://colab.research.google.com/drive/1SaKjkSp0DXnM_nktWSiEC-l9qGtVr6ph) covering installation, secure `staging` configuration, optional `prod`, `MemWalSync`, health/compatibility checks, delegate public-key/address derivation, `remember`, `remember_async`, async job waiting, `recall`, bulk remember, `remember_bulk_async`, `remember_bulk_and_wait`, optional `ask`, `analyze`, `analyze_and_wait`, `embed`, manual methods with scoring weights, `restore`, optional OpenAI/LangChain middleware, OpenAI-compatible provider settings such as `OPENAI_BASE_URL`, and troubleshooting.

### Fixed

- Fixed `MemWalSync` reuse inside notebooks so repeated calls do not reuse an HTTP transport from a closed event loop.

## 0.1.4

### Added
Expand Down
27 changes: 17 additions & 10 deletions packages/python-sdk-memwal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ pip install memwal[openai] # OpenAI SDK support
pip install memwal[all] # Everything
```

## Try It In Colab

Open the runnable [Walrus Memory Python SDK Colab](https://colab.research.google.com/drive/1SaKjkSp0DXnM_nktWSiEC-l9qGtVr6ph) for a notebook walkthrough covering installation, secure `staging` configuration, optional `prod`, health checks, `remember`, `remember_async`, async job waiting, `recall`, bulk remember, `remember_bulk_async`, `remember_bulk_and_wait`, optional SDK utilities, OpenAI/LangChain middleware, OpenAI-compatible provider settings such as `OPENAI_BASE_URL`, and troubleshooting.

## Quick Start

Set your environment variables first:
Expand Down Expand Up @@ -45,17 +49,17 @@ async def main():
server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
)

# Store a memory
result = await memwal.remember("I'm allergic to peanuts")
# Store a memory and wait until the background job is searchable
result = await memwal.remember_and_wait("I'm allergic to peanuts")
print(result.blob_id)

# Recall memories
matches = await memwal.recall(RecallParams(query="food allergies", limit=10, max_distance=0.7))
for memory in matches.results:
print(f"{memory.text} (relevance: {1 - memory.distance:.2f})")

# Analyze conversation for facts
analysis = await memwal.analyze("I love coffee and live in Tokyo")
# Analyze conversation for facts and wait until extracted facts are searchable
analysis = await memwal.analyze_and_wait("I love coffee and live in Tokyo")
for fact in analysis.facts:
print(fact.text)

Expand All @@ -76,7 +80,7 @@ client = MemWalSync.create(
server_url=os.environ.get("MEMWAL_SERVER_URL", "https://relayer.memwal.ai"),
)

result = client.remember("I'm allergic to peanuts")
result = client.remember_and_wait("I'm allergic to peanuts")
matches = client.recall(RecallParams(query="food allergies"))
client.close()
```
Expand All @@ -91,7 +95,7 @@ async with MemWal.create(
key=os.environ["MEMWAL_PRIVATE_KEY"],
account_id=os.environ["MEMWAL_ACCOUNT_ID"],
) as memwal:
await memwal.remember("I prefer dark mode")
await memwal.remember_and_wait("I prefer dark mode")
```

## Environment Presets
Expand All @@ -105,16 +109,14 @@ from memwal import MemWal
memwal = MemWal.create(
key=os.environ["MEMWAL_PRIVATE_KEY"],
account_id=os.environ["MEMWAL_ACCOUNT_ID"],
env="prod", # prod | dev | staging | local
env="staging", # staging for testing, prod for production
)
```

| `env` | Relayer URL |
|-------|-------------|
| `prod` | `https://relayer.memwal.ai` |
| `dev` | `https://relayer.dev.memwal.ai` |
| `staging` | `https://relayer.staging.memwal.ai` |
| `local` | `http://127.0.0.1:8000` |

Precedence: an explicit non-default **`server_url` wins over `env`**, which wins
over the default. An unknown preset raises `ValueError`. `env` is also accepted
Expand Down Expand Up @@ -176,7 +178,12 @@ Create a new async client.

| Method | Description |
|--------|-------------|
| `await remember(text, namespace?)` | Store a memory |
| `await remember(text, namespace?)` | Accept a background remember job and return `job_id` |
| `await wait_for_remember_job(job_id, ...)` | Poll one remember job until it is searchable |
| `await remember_and_wait(text, namespace?, ...)` | Store a memory and wait until it is searchable |
| `await remember_bulk(items)` | Accept several background remember jobs |
| `await wait_for_remember_jobs(job_ids, opts?)` | Poll several remember jobs together |
| `await remember_bulk_and_wait(items, opts?)` | Store several memories and wait for completion |
| `await recall(RecallParams(query, limit?, namespace?, max_distance?))` | Search memories, optionally filtering by distance |
| `await analyze(text, namespace?)` | Extract and store facts |
| `await ask(question, limit?, namespace?)` | Ask a question answered using memories |
Expand Down
4 changes: 2 additions & 2 deletions packages/python-sdk-memwal/examples/.env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Local server (default) or remote relayer
MEMWAL_SERVER_URL=http://localhost:8000
# Ed25519 delegate private key (64-hex). Get from Walrus Memory dashboard.
MEMWAL_PRIVATE_KEY=21b423e72282dcc47805de48ef9130331b642667b7b2a5cd621767928205e360
MEMWAL_PRIVATE_KEY=<your-ed25519-delegate-private-key-hex>
# Optional: paste the dashboard delegate public key so verification can catch mismatches.
MEMWAL_DELEGATE_PUBLIC_KEY=
# Walrus Memory account object ID on Sui (the wallet's account)
MEMWAL_ACCOUNT_ID=0x8a1121b8f95d79e68bd07efaf71689ce6fd832b369cdb1b2a943ec7beb822392
MEMWAL_ACCOUNT_ID=0x-your-walrus-memory-account-id
# Namespace for these test memories
MEMWAL_NAMESPACE=python-sdk-example
4 changes: 2 additions & 2 deletions packages/python-sdk-memwal/memwal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
)

# Async
result = await memwal.remember("I love coffee")
result = await memwal.remember_and_wait("I love coffee")
matches = await memwal.recall(RecallParams(query="beverage preferences"))

# Sync wrapper
from memwal import MemWalSync
client = MemWalSync.create(key="...", account_id="0x...")
result = client.remember("I love coffee")
result = client.remember_and_wait("I love coffee")
"""

from .client import (
Expand Down
18 changes: 11 additions & 7 deletions packages/python-sdk-memwal/memwal/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,11 @@ def _occurred_at_to_wire(
# shorthand; `fromisoformat` only accepts it on Python 3.11+,
# so we normalise to "+00:00" before parsing for 3.9/3.10
# compatibility.
normalised = occurred_at.replace("Z", "+00:00", 1) if occurred_at.endswith("Z") else occurred_at
normalised = (
occurred_at.replace("Z", "+00:00", 1)
if occurred_at.endswith("Z")
else occurred_at
)
try:
parsed = datetime.fromisoformat(normalised)
except ValueError as exc:
Expand Down Expand Up @@ -1251,6 +1255,12 @@ def _run(self, coro: Any) -> Any:
except RuntimeError:
loop = None

# Reset the httpx client before every asyncio.run() path so it is
# recreated inside the loop that will use it. This matters in
# notebooks/Jupyter where the sync wrapper runs coroutines in worker
# threads with short-lived event loops.
self._inner._client = None

if loop is not None and loop.is_running():
# Already inside an event loop (e.g. Jupyter).
# Create a new loop in a thread.
Expand All @@ -1259,12 +1269,6 @@ def _run(self, coro: Any) -> Any:
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
return pool.submit(asyncio.run, coro).result()
else:
# Reset the httpx client before each asyncio.run() so it is
# recreated fresh inside the new event loop. Without this,
# reusing a MemWalSync instance across multiple calls raises
# "RuntimeError: Event loop is closed" because the client's
# transport is still bound to the previous (now-closed) loop.
self._inner._client = None
return asyncio.run(coro)

def remember(
Expand Down
Loading
Loading