Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
71 changes: 71 additions & 0 deletions .github/workflows/release-rust-sdk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: release-rust-sdk

# Build/test gate for the Rust SDK on the release branches. Mirrors the shape of
# release-sdk.yml / release-python-sdk.yml, but crates.io publishing is
# intentionally DISABLED for now (WALM-48: publish is out of scope).

on:
push:
branches: [main, staging, dev]
paths:
- "packages/rust-sdk/**"
- ".github/workflows/release-rust-sdk.yml"
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

permissions:
contents: read

jobs:
build-test:
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: packages/rust-sdk
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/rust-sdk/target
key: cargo-rust-sdk-${{ runner.os }}-${{ hashFiles('packages/rust-sdk/Cargo.lock') }}
restore-keys: cargo-rust-sdk-${{ runner.os }}-

- name: Format check
run: cargo fmt --all -- --check

- name: Clippy
run: cargo clippy --all-targets -- -D warnings

- name: Build (release)
run: cargo build --release --verbose

- name: Test
run: cargo test --verbose

- name: Package (dry-run, no publish)
run: cargo package --allow-dirty --no-verify

# ────────────────────────────────────────────────────────────────────
# crates.io publishing is intentionally DISABLED (WALM-48).
# To enable later:
# 1. add a CARGO_REGISTRY_TOKEN repository secret,
# 2. gate on the stable branch (github.ref == 'refs/heads/main'),
# 3. bump the version in Cargo.toml,
# 4. remove `if: ${{ false }}`.
# ────────────────────────────────────────────────────────────────────
- name: Publish to crates.io (disabled)
if: ${{ false }}
run: cargo publish --token "${{ secrets.CARGO_REGISTRY_TOKEN }}"
57 changes: 57 additions & 0 deletions .github/workflows/test-rust-sdk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: test-rust-sdk

on:
pull_request:
paths:
- "packages/rust-sdk/**"
- ".github/workflows/test-rust-sdk.yml"
push:
branches: [main, staging, dev]
paths:
- "packages/rust-sdk/**"
- ".github/workflows/test-rust-sdk.yml"
workflow_dispatch:

concurrency:
group: test-rust-sdk-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
rust-sdk-checks:
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: packages/rust-sdk
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/rust-sdk/target
key: cargo-rust-sdk-${{ runner.os }}-${{ hashFiles('packages/rust-sdk/Cargo.lock') }}
restore-keys: cargo-rust-sdk-${{ runner.os }}-

- name: Format check
run: cargo fmt --all -- --check

- name: Clippy
run: cargo clippy --all-targets -- -D warnings

- name: Build
run: cargo build --verbose

- name: Test
run: cargo test --verbose
12 changes: 12 additions & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@
}
]
},
{
"tab": "Rust SDK",
"groups": [
{
"group": "Rust SDK",
"pages": [
"rust-sdk/quick-start",
"rust-sdk/api-reference"
]
}
]
},
{
"tab": "Relayer",
"groups": [
Expand Down
100 changes: 100 additions & 0 deletions docs/rust-sdk/api-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
title: "API Reference"
description: "Builder options, async client methods, and error types for the Walrus Memory Rust SDK."
---

See also:

- [Configuration](/reference/configuration)
- [Relayer API](/relayer/api-reference)

All client methods are `async` and return `memwal::Result<T>` (an alias for `Result<T, memwal::Error>`).

## Constructing a client

```rust
use memwal::{Env, WalrusMemory};

// Builder (recommended)
let client = WalrusMemory::builder(private_key, account_id)
.server_url("https://relayer.memory.walrus.xyz") // or .env(Env::Staging)
.namespace("demo") // optional, defaults to "default"
.build()?;

// One-shot helper (prod defaults)
let client = WalrusMemory::create(private_key, account_id)?;
```

### `WalrusMemoryBuilder`

| Method | Description |
|--------|-------------|
| `WalrusMemory::builder(key, account_id)` | Start a builder from a delegate private key + account ID |
| `.server_url(url)` | Explicit relayer URL (wins over `.env`) |
| `.env(Env)` | Environment preset — `Prod`, `Staging`, `Dev`, `Local` |
| `.namespace(ns)` | Default namespace for reads/writes |
| `.http_client(reqwest::Client)` | Supply a custom `reqwest` client (proxies, timeouts, pools) |
| `.build()` | Validate and construct `WalrusMemory` |

## Client methods

| Method | Description |
|--------|-------------|
| `remember(text, namespace?)` | Submit a memory; returns a `job_id` (async indexing) |
| `remember_and_wait(text, namespace?, opts)` | Submit and wait until indexed |
| `wait_for_remember_job(job_id, opts)` | Poll a job until `done` |
| `get_remember_status(job_id)` | One-shot job status |
| `recall(params)` | Semantic search — `RecallParams::new(query).limit(n).max_distance(d)` |
| `embed(text)` | Embed text to a vector (requires the relayer to expose `/api/embed`) |
| `analyze(text, opts)` | Extract facts and enqueue a remember job per fact |
| `analyze_and_wait(text, opts, wait_opts)` | Analyze and wait for all fact jobs |
| `ask(question, limit?, namespace?)` | Retrieval-augmented answer over memories |
| `restore(namespace, limit?)` | Rebuild the local index from Walrus |
| `recall_manual(opts)` | Search with a pre-computed vector (blob ids + distances) |
| `remember_manual(opts)` | Index a memory you've already embedded/encrypted/uploaded yourself |
| `remember_bulk(items)` / `remember_bulk_and_wait(items, opts)` | Batch up to 20 memories |
| `get_remember_bulk_status(job_ids)` / `wait_for_remember_jobs(...)` | Batch job status |
| `health()` / `version()` | Relayer health & version metadata (unauthenticated) |
| `compatibility()` | Verify the relayer's API version is supported (cached after first success) |
| `public_key_hex()` | The delegate public key (hex) |
| `server_url()` / `namespace()` | Inspect the effective configuration |
| `destroy(self)` | Zero the delegate key's seed material and drop the cached SEAL session |

## Waiting on jobs

`remember` indexes asynchronously and returns a `job_id`. Use the `_and_wait`
variants, or poll manually:

```rust
use memwal::WaitOptions;

let accepted = client.remember("note", None).await?;
let status = client
.wait_for_remember_job(&accepted.job_id, WaitOptions::default())
.await?;
```

`WaitOptions` controls the poll interval and timeout (`WaitOptions::default()` is a
sensible starting point).

## Errors

Methods return `memwal::Error`, a `thiserror` enum:

| Variant | Meaning |
|---------|---------|
| `InvalidKey` | The delegate key or account ID failed to parse |
| `InvalidUrl` | The configured server URL was malformed |
| `AuthRejected` | Relayer rejected the signature/SEAL session (HTTP 401) — usually an unregistered delegate key, clock skew, or a replayed nonce |
| `Incompatible` | Relayer requires a newer SDK (HTTP 426) |
| `Compatibility` | `compatibility()` couldn't reach `/version` or the relayer's API version isn't supported |
| `SealSession` | Building the SEAL session (ephemeral key, Sui signing, `/config` lookup) failed |
| `Server { status, .. }` | Relayer returned another non-success status |
| `JobFailed` / `JobNotFound` / `JobTimeout` | A remember/analyze job failed, was not found, or didn't finish before the wait timeout |
| `InvalidArgument` | A call was made with invalid arguments (empty query, empty vector, …) before any request was sent |
| `Transport` / `Json` | HTTP transport or (de)serialization failure |

## Status / Notes

- **`embed`** mirrors the TS/Python surface and calls `POST /api/embed`; some relayer deployments embed internally and do not expose this route (returns 404).
- **`remember_manual`**'s wire route (`POST /api/remember/manual`) mirrors `recall_manual`'s pattern but hasn't been confirmed against a live relayer yet — verify before relying on it in production.
108 changes: 108 additions & 0 deletions docs/rust-sdk/quick-start.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
title: "Quick Start"
description: "Install the Walrus Memory Rust SDK and store your first memory in under a minute."
---

The Walrus Memory Rust SDK (`memwal` on crates.io) is a native client for Rust-based AI agents and server-side integrations. It mirrors the [TypeScript](/sdk/quick-start) and [Python](/python-sdk/quick-start) SDKs: same relayer, same Ed25519 + SEAL-session auth, same methods.

The SDK talks to the Walrus Memory **relayer** over signed HTTPS. Embedding, SEAL encryption, Walrus upload/download, and vector search all happen server-side; the SDK signs each request with your Ed25519 delegate key and attaches a short-lived SEAL session for decrypt-needing calls.

## Installation

```toml
# Cargo.toml
[dependencies]
memwal = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```

The client is async-first and runs on the [Tokio](https://tokio.rs) runtime. HTTP is handled by [`reqwest`](https://docs.rs/reqwest) (rustls TLS) and signing by [`ed25519-dalek`](https://docs.rs/ed25519-dalek).

### Prerequisites

- A delegate key and account ID, registered on-chain for your Walrus Memory account (generate/register one at [memory.walrus.xyz](https://memory.walrus.xyz)'s dashboard).
- A relayer URL matching where that key was registered — see Environment Presets below. A delegate key only works against the relayer it was registered on.

## Quick Start

```rust
use memwal::{WalrusMemory, RecallParams, WaitOptions};

#[tokio::main]
async fn main() -> memwal::Result<()> {
let client = WalrusMemory::builder(
std::env::var("WALRUS_MEMORY_PRIVATE_KEY").unwrap(),
std::env::var("WALRUS_MEMORY_ACCOUNT_ID").unwrap(),
)
.server_url("https://relayer.memory.walrus.xyz")
.namespace("demo")
.build()?;

client.health().await?;

// Store a memory and wait for it to be indexed.
let stored = client
.remember_and_wait("User prefers dark mode and TypeScript.", None, WaitOptions::default())
.await?;
println!("stored {}", stored.blob_id);

// Recall it.
let hits = client
.recall(RecallParams::new("What are the user's preferences?").limit(5))
.await?;
for m in hits.results {
println!("{:.3} {}", 1.0 - m.distance, m.text);
}

// Ask a question over stored memories (RAG).
let answer = client.ask("Where does the user live?", Some(5), None).await?;
println!("{}", answer.answer);
Ok(())
}
```

Run the bundled end-to-end example from the [package source](https://github.com/MystenLabs/MemWal/tree/dev/packages/rust-sdk):

```bash
export WALRUS_MEMORY_PRIVATE_KEY=... WALRUS_MEMORY_ACCOUNT_ID=0x...
export WALRUS_MEMORY_ENV=dev # match wherever your key was registered
cargo run --example quick_start
```

## Environment Presets

Instead of a full URL, select an environment with `.env(...)`. An explicit `.server_url(...)` always wins.

| Env | Relayer URL |
|-----------------|-----------------------------------------------|
| `Env::Prod` | `https://relayer.memory.walrus.xyz` |
| `Env::Staging` | `https://relayer-staging.memory.walrus.xyz` |
| `Env::Dev` | `https://relayer.dev.memwal.ai` (legacy pre-rebrand domain) |
| `Env::Local` | `http://127.0.0.1:8000` |

```rust
use memwal::{Env, WalrusMemory};

let client = WalrusMemory::builder(key, account_id)
.env(Env::Staging)
.build()?;
```

## Authentication

Each request signs the canonical message

```text
{timestamp}.{method}.{path}.{sha256(body)}.{nonce}.{account_id}
```

with the delegate Ed25519 key and sends these headers:
`x-public-key`, `x-signature`, `x-timestamp`, `x-nonce`, `x-account-id`.

Decrypt-needing calls (`remember`, `recall`, `analyze`, `ask`, `restore`, bulk remember) additionally attach an `x-seal-session` header — a short-lived SEAL session built by signing an ephemeral session key as a Sui personal message with your delegate key. Your raw delegate private key is never sent over the wire.

## Next Steps

- [API Reference](/rust-sdk/api-reference) — full method list, builder options, and error types
- [Configuration](/reference/configuration) — environment variables and relayer settings
- [Relayer API](/relayer/api-reference) — the HTTP surface the SDK signs against
1 change: 1 addition & 0 deletions packages/rust-sdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
Loading
Loading