Durable long-term memory for AI agents, powered by Walrus Memory (memwal) — with an IPFS CID for every memory.
This is the successor to walrus-agent-memory. That project implemented its own memory layer (local embeddings, batching, index snapshots) on top of raw Walrus blob storage. This one delegates the memory layer itself — embedding, SEAL encryption, Walrus upload, and semantic search — to the Walrus Memory relayer, and adds the pieces the relayer doesn't provide:
- 🧾 An IPFS CID for every memory — each memory gets a canonical JSON record that is content-addressed on IPFS (pinned via the Lighthouse x Walrus integration, or computed locally)
- 🏷️ Tags + metadata on top of memwal's plain-text memories
- 🔍 Verification — check any memory byte-for-byte against its CID
- 🌐 Testnet & mainnet — one env var switches everything (relayer, aggregator, dashboard, local index)
- 🔌 MCP server — drop-in memory tools for Claude Code, Claude Desktop, or any MCP-capable agent
remember("user prefers dark mode", tags: ["preference"])
│
├─► Walrus Memory relayer ──► embed ──► SEAL-encrypt ──► Walrus blob (blob_id)
│ ownership anchored on Sui
│
└─► canonical JSON record {id, content, tags, metadata, blobId, ...}
│
├─ LIGHTHOUSE_API_KEY set → pinned via Lighthouse x Walrus
│ (Walrus-backed, IPFS CID, gateway-resolvable)
└─ otherwise → CIDv1 computed locally (deterministic)
Recall goes through the relayer's semantic search (owner + namespace scoped, decrypted server-side in a TEE) and results are enriched with the local record — id, tags, CID, gateway URL.
Create an account and delegate key on the dashboard for the network you want:
| Network | Dashboard | Relayer |
|---|---|---|
| mainnet | https://memory.walrus.xyz | https://relayer.memory.walrus.xyz |
| testnet (staging) | https://staging.memory.walrus.xyz | https://relayer-staging.memory.walrus.xyz |
Credentials are per-network — a mainnet account does not work on testnet and vice versa.
npm install
cp .env.example .env # then fill in:| Variable | Required | Description |
|---|---|---|
MEMWAL_PRIVATE_KEY |
✅ | Ed25519 delegate private key (hex) |
MEMWAL_ACCOUNT_ID |
✅ | Walrus Memory account object id on Sui (0x...) |
MEMWAL_NETWORK |
— | mainnet (default) or testnet |
MEMWAL_SERVER_URL |
— | Override the relayer URL (self-hosted relayers) |
MEMWAL_NAMESPACE |
— | Memory isolation boundary (default default) |
MEMWAL_AGENT |
— | Agent name recorded on each memory |
MEMWAL_MEMORY_DIR |
— | Local index dir (default ~/.memwal-agent-memory) |
LIGHTHOUSE_API_KEY |
— | Pin memory records via the Lighthouse x Walrus integration (create with your Sui wallet at files.lighthouse.storage) |
MEMWAL_IPFS_PIN |
— | Set to off to force local (unpinned) CIDs even when a Lighthouse key is set — for namespaces whose records must stay private |
import { MemwalMemory } from 'memwal-agent-memory'
const memory = await MemwalMemory.fromEnv()
const stored = await memory.remember('User prefers dark mode.', {
tags: ['preference'],
metadata: { source: 'onboarding' },
})
// { id, blobId, cid: 'bafkrei...', pinned, gatewayUrl?, walrusUrl, network, namespace }
const matches = await memory.recall('what are the user's preferences?', { limit: 5 })
// [{ content, score, distance, tags, cid, gatewayUrl?, blobId, indexed }]Or run the demo / tests:
npm test # offline smoke test (no credentials needed)
npm run demo # live demo (needs credentials)
npm run test:live # live end-to-end round tripnpm run mcp # stdio transportClaude Code / Claude Desktop config:
Once the package is published to npm, "args": ["-y", "memwal-agent-memory"] works directly; from a checkout use:
{
"mcpServers": {
"memwal-memory": {
"command": "npx",
"args": ["-y", "tsx", "/path/to/memwal-agent-memory/src/mcp-server.ts"],
"env": {
"MEMWAL_PRIVATE_KEY": "<delegate-key-hex>",
"MEMWAL_ACCOUNT_ID": "0x...",
"MEMWAL_NETWORK": "mainnet",
"LIGHTHOUSE_API_KEY": "<optional>"
}
}
}
}| Tool | Description |
|---|---|
memwal_remember |
Store a memory (encrypted on Walrus) + IPFS CID |
memwal_analyze |
Extract facts from free-form text, one memory + CID each |
memwal_recall |
Semantic search, optional tag filter and max distance |
memwal_list_memories |
List locally indexed memories, newest first |
memwal_get_memory |
Fetch a record by id, CID, or Walrus blob id |
memwal_verify_memory |
Verify a memory against its IPFS CID |
memwal_forget |
Remove a memory from the local index + unpin its IPFS record |
memwal_restore_index |
Rebuild the relayer's vector index from on-chain data |
memwal_repin_pending |
Retry IPFS pins that failed at store time |
memwal_snapshot_index |
Pin the whole local index to IPFS (portability) |
memwal_rebuild_local |
Restore the local index from a snapshot CID on a new machine |
memwal_memory_status |
Network, relayer health, counts (incl. pending pins), pinning mode |
MemwalMemory methods (all async):
| Method | What it does |
|---|---|
remember(content, {tags, metadata}) |
Relayer stores + indexes the memory; record is content-addressed on IPFS |
analyze(text, {occurredAt}) |
Relayer extracts discrete facts; each becomes a memory with its own CID |
recall(query, {tags, limit, maxDistance}) |
Semantic search via relayer, enriched with local records |
list({limit, tags}) |
Locally indexed memories, newest first |
get(ref) |
Full record by id / CID / blob id (falls back to IPFS gateway for pinned CIDs) |
verify(ref) |
Pinned: gateway round-trip byte comparison. Unpinned: local re-hash vs CID |
forget(ref) |
Remove from the local index + unpin the IPFS record (see caveats) |
restore({maxBlobs}) |
Heal the relayer's vector index from on-chain Walrus data |
repinPending() |
Retry pins that failed at store time (memories are never lost to a pin failure — they get a local fallback CID and a pinPending flag) |
snapshotIndex() / rebuildLocal(cid) |
Pin the whole index to IPFS and restore it on another machine — makes tags/CIDs portable, not just the relayer's semantic index |
blobIds(ref) |
Walrus-native blob IDs: the memwal blob + the pinned record's blobs |
status() |
Network, relayer health, counts, pinning mode, index path |
Every memory always has a CID over its canonical record JSON:
- With
LIGHTHOUSE_API_KEY— the record is uploaded through the Lighthouse x Walrus integration: stored as a Walrus blob on Sui, addressed by an IPFS-compatible CID, resolvable atgateway-walrus.lighthouse.storage/ipfs/<cid>.verify()does a full gateway round-trip, andblobIds()maps the CID to its Walrus-native blob IDs.⚠️ The pinned record is plaintext (content, tags, metadata). The memwal memory blob itself stays SEAL-encrypted, but this mirror is public — skip the API key for sensitive memories. - Without it — the CID is computed locally (CIDv1,
rawcodec, sha2-256; verified against themultiformatsreference implementation). It is deterministic and independently checkable:ipfs add --raw-leaves --cid-version=1on the same bytes yields the same CID. It just isn't resolvable until someone pins those bytes.
forgetcannot delete from the relayer. It removes the local record and unpins the IPFS mirror, but the Walrus Memory relayer currently has no delete API; the encrypted blob persists on Walrus until its storage epochs lapse and may still surface inrecall(markedindexed: false).- The local index is per machine. Memories written elsewhere are still recallable semantically (the relayer holds the vector index), but they arrive without tags/CID until you sync the local index — use
snapshotIndex()on the source machine andrebuildLocal(cid)on the new one.restore()heals the relayer's index, not the local one. - Walrus Memory is in beta — free during the launch period with usage limits.
MIT