Skip to content

Releases: MystenLabs/walrus-memory

memwal-python@0.2.0

Choose a tag to compare

@github-actions github-actions released this 23 Oct 10:31
4155179

Release memwal Python SDK v0.2.0

memwal-python@0.1.12

Choose a tag to compare

@github-actions github-actions released this 21 Oct 07:50
5e9de16

Release memwal Python SDK v0.1.12

@mysten-incubation/memwal@0.1.9

Choose a tag to compare

@github-actions github-actions released this 21 Oct 07:50
5e9de16

0.1.9

Fixed

  • rememberBulk and rememberBulkAsync send an idempotency key derived from the batch (a 30-minute bucket plus each item's namespace and text). A retry inside that window returns the same jobs instead of storing the batch again. An explicit idempotencyKey still wins. (#1080)
  • listMemories() pages every stored memory without a similarity query. It returns metadata only: no blob download and no decrypt. An optional namespace filter runs on the client, so a filtered page can be empty while has_more is true. (WALM-388)
  • withMemWal does not recall or auto-save on a tool-execution step. A later tool step reuses the recall cached for that user message. Auto-save still runs only on a user turn. (#1079)
  • flush() raises when a background auto-save failed, and onSaveError runs. The chat turn itself still does not fail. (#1117)
  • withMemWal warns when recall fails and accepts onRecallError. (WALM-764)
  • MemWal.create and MemWalManual.create reject a blank accountId, an empty namespace string, and a private key that is not a 32-byte Ed25519 seed. An omitted namespace still defaults to "default". (#1155, #1200, #1204)
  • recall, recallManual, and restore reject a limit that is not a positive integer before signing. recall also rejects a blank query, a non-finite scoring weight, and a non-finite or negative maxDistance. A bad maxDistance used to drop every hit and look like an empty namespace. recall also rejects a maxTokens that is not a number (TypeError) or is negative (RangeError). (#1086, #1068, #1227, #1228)
  • A maxDistance filter keeps the relayer's matching total. total is no longer replaced by the filtered row count, including when every row is dropped. (WALM-840)
  • When a token budget keeps only high-relevance hits, the lowest-distance hits are kept and the caller's sort order is preserved. (WALM-756)
  • waitForRememberJob and waitForRememberJobs do not sleep past the deadline. If the budget runs out during the sleep, they make one more status read. (#1081)
  • Package exports include a default condition and list types first, so Next.js, Turbopack, and Node require(esm) resolve the subpaths. (WALM-721)
  • A success response with an empty or non-JSON body raises an SDK error that names the HTTP status, instead of a raw SyntaxError. MemWalManual.restore sends the SEAL session and keeps its deadline armed across the body read. (#1201, #1137)
  • getRememberBulkStatus rejects a job id list that is not an array before any request. MemWalMock.analyze rejects an invalid date, and MemWalMock.listNamespaces rejects a cursor that is not {updated_at, namespace} instead of ending the page early. (#1226, #1229, #1203)
  • createAccount, addDelegateKey, removeDelegateKey, and MemWalManual work again without a caller-supplied suiClient. They looked for SuiClient in @mysten/sui/client, which current @mysten/sui no longer exports, and threw SuiClient not found. The default is now SuiGrpcClient against the public fullnode for suiNetwork, because those fullnodes no longer serve JSON-RPC. An explicit suiClient still wins. (WALM-164, #300)
  • remember, rememberAsync, and rememberAndWait reject a non-string text argument with TypeError before signing. An object used to be sent as text and the relayer answered 422. (#1058)
  • getRememberStatus and waitForRememberJob reject a job id outside [A-Za-z0-9_-]+ before signing. ., .., \, and %2e%2e used to pass a denylist and be rewritten by fetch after the path was signed. Valid UUID job ids are unchanged. (#1087, #1216)
  • /manual no longer imports @mysten/walrus. getWalrusClient() and walrusUpload() had no callers, but the dynamic import still pulled the WASM client into Turbopack and Vite. Downloads stay on the HTTP aggregator. (WALM-145, #330)
  • MemWal.recallManual() is typed as RecallManualHitResult (blob id and distance, no text) and MemWalManual.recallManual() as RecallManualMemoryResult (decrypted text). RecallManualResult remains a deprecated alias of those two result types, not a mixed-item array. (WALM-163, #298)
  • MemWalMock.listNamespaces rejects a non-positive or non-integer limit with limit must be positive, matching the relayer. limit: 0 used to return has_more: true and next_cursor: null, so the documented pagination loop never finished, and limit: -1 silently dropped the last namespace. (#1144, #1145)
  • MemWalMock.rememberBulkAsync rejects an empty or non-array batch with the same error as MemWal. (#1146)
  • MemWalMock honors idempotencyKey. A retry of the same text and namespace returns the original job, a different payload throws, and forget / clear release the key so the text can be written again. (#1147)
  • MemWalMock scopes an idempotency key to its namespace, like the relayer. (WALM-781)
  • MemWalMock.recall({ sort: "recent" }) orders a bounded semantic window (5× limit, at least limit, at most 50) by write time before truncating. Relevance order is unchanged. (#1124)
  • Positional recall(query, options, namespace) uses that namespace when options.namespace is omitted, on both MemWal and MemWalMock. An explicit options.namespace still wins. (#1038)
  • remember(text, { idempotencyKey }) and rememberAndWait(text, { timeoutMs }, { idempotencyKey }) treat a plain object in the namespace slot as options. A third options argument is merged, and its fields win. That call used to send the object as the namespace, or drop the third argument and mint a second blob. An explicit string namespace still wins. (#1142)
  • getRememberStatus("") returns not_found without a request, and waitForRememberJob("") fails immediately with status 404. GET /api/remember/ is not a route; the empty 404 body used to throw SyntaxError, and the wait treated that as transient until timeout. (#1087)
  • waitForRememberJobs settles every slot when the same job id is listed more than once. indexOf plus a Set of ids left the later copies as timeout. (#1121, #1084)
  • rememberBulkAsync rejects more than 20 items before signing, matching the relayer's MAX_BULK_ITEMS. (#1104)
  • Remember-job status reads (getRememberStatus, waitForRememberJob, getRememberBulkStatus) no longer attach x-seal-session. Status is metadata and does not decrypt. (#1135)
  • Bulk status includes each job's namespace. waitForRememberJobs(jobIds) uses that value when the caller did not pass one, instead of the client default. A caller-supplied namespace still wins. (#1136)
  • getPublicKeyHex() and any request after destroy() throw, including a read already in flight. getPublicKeyHex() used to resolve to the zeroed key, or to the well-known key of the wiped seed, and remember() still sent GET /version before noticing the client was destroyed. (#1090)
  • normalizeServerUrl no longer warns about plaintext HTTP for IPv6 loopback. URL.hostname keeps the brackets on [::1], so the ::1 exemption never matched http://[::1]:8000. (#1128)
  • remember, rememberBulk, analyze, and embed reject whitespace-only text immediately, with the same messages the relayer returns. A blank body used to be accepted and then sit until the client timed out. (#1129)
  • A 401 AUTH_REJECTED now says a wallet address is not a MemWal account ID. The relayer still returns one 401 for every credential failure, so the message names the mix-up instead of revealing which check failed. (#1132)
  • rememberBulkAndWait / waitForRememberJobs no longer resolve a batch they could not read as if it were still uploading. The status endpoint counts against the delegate-key budget, and a 429 on every poll used to be retried silently until the wait ran out, leaving each item as timeout with "polling timed out", even when nothing had been stored. When no poll got through, the wait now makes one confirming read. If that is rate-limited too, it throws MemWalRateLimited (status: 429, jobIds, retryAfterSeconds). An item still unsettled at the deadline names its last known status, or says no read got through. (WALM-671, #967)
  • A rememberAndWait / waitForRememberJob that runs out of time now raises MemWalRememberJobTimeout (still status: 504) carrying lastStatus and serverError, the last state the relayer reported. Under upload congestion the relayer keeps a job running for up to ~25 minutes and says so, but the 504 used to drop that, so a job that was still retrying looked dead and callers dropped their idempotency key to force a new paid write. When no poll got through, the message says the state is unknown instead. (WALM-670, #966)
  • Grow waitForRememberJob / waitForRememberJobs poll delay 1.5× from the caller interval (floor 100ms) toward a 5s cap, with an immediate first poll. Backoff is disabled above the cap. (WALM-623)
  • A 429 during a remember-job wait is reported on the timeout. Retry-After is still clamped to the caller's remaining budget, and that remainder still buys one status read. (WALM-623)

@mysten-incubation/memwal-mcp@0.0.15

Choose a tag to compare

@github-actions github-actions released this 21 Oct 08:08
5e9de16

0.0.15

Security

  • A project-local .memwal/credentials.json no longer decides where memory goes on presence alone. That file lives inside the repository, so anyone who could commit to a repo — or get a clone opened — could silently repoint the account and relayer every memory written from that directory went to, including from a subfolder, with nothing said and no delete path once written. A project file is now inert until the user approves that exact project path, account, delegate key and relayer with memwal-mcp approve-project; the approval record is kept in ~/.memwal/project-approvals.json, outside the repository, so a repository cannot carry its own approval, and any later change to the destination requires approving again. Until approved the global credentials are used — an unapproved, altered or malformed project file is a fallback, never a failure — and one stderr line names the file that was skipped, the destination it wanted, and the command that approves it. MEMWAL_CREDS_DIR still overrides both files without approval. memwal_health now reports account= beside relayer=, so the active destination is visible where the user is. (WALM-639)

  • MEMWAL_CREDS_DIR must now be an absolute path outside the current project, and a value that is not is refused with an error naming it rather than followed. The override decides the credentials before any approval is looked up, so it was a way around the gate above rather than through it: an empty value made the approval store the bare relative name project-approvals.json, which resolves against the working directory — a repository that committed that one file at its root approved its own credentials — and a relative value such as .memwal put both the credentials and the approval record inside the repository. Neither had to be typed by the user: an MCP client reads .cursor/mcp.json, .vscode/mcp.json or .claude/settings.json out of the checkout and passes on the env block it finds, and editors expand ${workspaceFolder} there, so an absolute path inside the project is refused too. An empty value now means unset, matching every other reader of the variable. Tests and CI pointing at an absolute temp directory are unaffected. (WALM-639)

  • The login write-ahead record no longer goes into the repository. It sat beside whichever credentials file resolution chose, so once a project file was approved every sign-in wrote a plaintext 64-hex Ed25519 delegate seed to <repo>/.memwal/login-pending.json — in a directory an attacker who planted the credentials file had already created, so tracked rather than ignored, and staged by git add -A. It now lives in ~/.memwal/login-pending/, keyed by project so a sign-in is still reclaimable only by the project that started it. The credentials file itself cannot move — it is the file the user approved — so approve-project and the pre-sign-in warning now state plainly that signing in from this project writes a delegate private key inside the repository, and suggest adding .memwal/ to .gitignore. (WALM-639)

Fixed

  • The plugin launcher installs @mysten-incubation/memwal-mcp@0.0.15 instead of 0.0.14-dev.6.
  • memwal_login with freshKey: true closes the listener already bound. The replaced listener cannot time out into a failure notice, and a late callback cannot save credentials or clear the new write-ahead record. The tool result names the abandoned public key and address so it can be removed from the dashboard. (WALM-645)

@mysten-incubation/oc-memwal@0.0.7

Choose a tag to compare

@github-actions github-actions released this 02 Oct 04:18
1ee7801

0.0.7

Fixed

  • Only privateKey interpolates ${ENV_VAR}. Other fields stay literal so a secret cannot land in serverUrl.
  • memory_search and memory_store are pinned to the calling agent's namespace. An omitted namespace uses that agent, not the main namespace, and a model-supplied namespace is rejected unless it is the agent's namespace or its legacy namespace.

memwal-python@0.1.11

Choose a tag to compare

@github-actions github-actions released this 22 Sep 12:25
f248bd3

Release memwal Python SDK v0.1.11

@mysten-incubation/memwal@0.1.8

Choose a tag to compare

@github-actions github-actions released this 22 Sep 12:25
f248bd3

0.1.8

Added

  • recall() sends its own deadline as deadline_ms (14000, a second under the 15s it aborts at, so the relayer's answer beats the abort even on a slow connect) in the request body. A relayer that reads it answers a recall about to miss that deadline with a 504 whose serverCode is RECALL_TIMEOUT and whose body names the stuck step (auth, embed, vector_search, walrus_download, seal_decrypt), instead of the request aborting with no cause. Relayers that predate the field ignore it. (WALM-396)

Fixed

  • Every relayer request now carries a deadline. fetch has none of its own and the SDK passed an abort signal on exactly one method (recall, 15s), so the accept POST, every job-status read, and the /version and /config handshake calls could stay pending for as long as the socket stayed open. A poll loop checks its budget at the top of each iteration, which bounds when the next request starts rather than how long one takes — so a single stalled read ran straight past timeoutMs, and a memwal_remember documented as capping at 90s was observed by an MCP client still running after 120s. Requests default to 30s, matching the relayer's own outbound client; set it with requestTimeoutMs on MemWal.create, where a non-positive or non-finite value falls back to the default rather than disabling the bound. restore (60s) and analyze (60s) carry their own, since the route self-bounds at 55s and the extractor LLM runs inline respectively. Inside the wait loops each poll is bounded by the client deadline clamped to the remaining budget, so one stalled poll can neither outlive the wait nor swallow it. An expired request raises MemWalRequestTimeout with status: 504, which the existing transient-poll handling already retries; a caller's own abort and every other transport error propagate unchanged.
  • Generated idempotency keys are derived from the content (a 30-minute bucket plus namespace and text) instead of crypto.randomUUID(). The per-instance key map only ever deduped retries that reused one client, and callers such as the MCP sidecar build a fresh client per session — so a replay after a reconnect read as a brand-new write and the relayer minted a second paid Walrus blob for one already in flight. The bucket bounds the collapse, since remember_jobs rows are never pruned. Callers passing an explicit idempotencyKey are unaffected, and distinct text or namespaces still derive distinct keys.

@mysten-incubation/memwal-mcp@0.0.14

Choose a tag to compare

@github-actions github-actions released this 22 Sep 12:25
f248bd3

0.0.14

Unreleased. The plugin's launcher installs @mysten-incubation/memwal-mcp@0.0.14-dev.0, named by mcpPackageVersion in plugin/plugin.json, until 0.0.14 is on npm; drop that key in that release.

Added

  • Opt-in Streamable HTTP transport for the stdio bridge. Set MEMWAL_MCP_TRANSPORT=http (aliases streamable, streamable-http) to dial the relayer's single /api/mcp endpoint, where a call is answered on the same request instead of being split across a POST and an SSE stream, so there is no idle watchdog. The default stays sse and unrecognised values fall back to it. Reconnect replay is not transport-aware yet: on Streamable a disconnect mid-send can look sent, so a replayed write may duplicate.

Fixed

  • The plugin no longer starts the MCP server through npx @mysten-incubation/memwal-mcp@<version>. npx resolves a package name against the directory the MCP client was started in — the project the user has open — so a project carrying an installed package of that name, claiming the pinned version, was run instead of the published one; a pinned npx …@0.0.14 command was reproduced running a project's own binary while offline. The version pin was no defence, because the planted package simply claims that version. Every launch site (.mcp.json, the Cursor and Codex copies, and the Codex fallback installer) now runs plugin/scripts/launch_mcp.mjs, which installs the pinned version once under ~/.memwal/runtime/memwal-mcp@<version> and launches that absolute entry point with the current node binary. Nothing on that path consults the project's node_modules, a PATH-relative bin shim, or the project's .npmrc, and the launcher fails instead of falling back to the name when the trusted install cannot be produced. Exploiting the old behaviour required write access to installed package and bin files inside the project, so a plain repository clone or a package.json alone was never enough. MEMWAL_MCP_RUNTIME_DIR relocates the trusted directory and is rejected unless it is an absolute path. (WALM-640)
  • The trusted launcher installs 0.0.14-dev.1, not dev.0. dev.0 predates the relayer change that returns memwal_remember at accept, so its initialize instructions still call the job_id reply "the exception, not the normal result" and say a blob_id means the fact is stored — the opposite of what the relayer now does on every write. An agent on dev.0 against a current relayer therefore reports a fact as saved while the write is still in flight. dev.1 carries the corrected text and differs from dev.0 in only auth-required, bridge and instructions.
  • An unfinished write is no longer reported with a blob_id. persist_uploaded_state stamps a real blob_id and status uploaded the moment the blob is on Walrus, but the job is not done — SetMetadataAndTransfer has not handed the blob object to the user and insert_vector has not indexed it, so the fact is not recallable and a later failure can still lose it. uploaded was already counted as in flight; the render was not, and printed the blob_id of any row that had one. A live dev batch read 0/6 saved, 6 still uploading with three of those rows carrying a blob_id=, contradicting the same reply's rule that only a blob_id means the fact is stored — an agent that believes it tells the user a fact is saved while it is still in flight. Only a done row shows a blob_id now, which is what the single-job path always did. Same guard in memwal_remember_bulk and memwal_analyze, whose timeout rows render from the same template. (#921 follow-up)
  • The cold-start tool list no longer advertises a tool the relayer may not serve. The bridge ships on npm and updates itself while a relayer ships per environment, so 0.0.14-dev.0 dialled prod and staging still on 0.0.13: cold start named memwal_remember_status, which neither registers, and the pending-write wording sent the agent to go call it — one live run spent 90.67s there before erroring. Cold start is now a floor rather than a forecast (BASELINE_RELAYER_TOOLS): it carries only what the oldest supported relayer serves and its descriptions name nothing outside it, while newer tools still reach the client a beat later on the relayer's own tools/list. Initialize instructions use the same floor (they used to name memwal_remember_status before any tools/list). And a call for a tool outside that floor — including during the cold-start window, before any upstream tools/list has been seen — is now answered locally and at once — naming the tools that do exist and saying plainly that nothing ran — instead of being forwarded into a wait that only ends at the orphan deadline. (#928)
  • A tool call that times out or cannot reach the relayer now says why and what to do, in three lines: Cause, Relayer health (checked against /health on the spot) and Next step. It used to read Tool error: This operation was aborted. A recall the relayer cut short names the step it was stuck in: credential check, embedding, vector search, Walrus download or SEAL decrypt. Writes are never told a retry is safe. (WALM-396)
  • A call whose reply never arrives is answered with the relayer's health. The bridge checks /health first and says whether the relayer is up with this one call stuck, unhealthy, down, or not resolvable at the configured URL, instead of a bare "did not answer". A call being answered this way is never re-sent, so a reconnect landing while the check runs cannot run a write a second time. A lost memwal_recall reply is now answered after 2 minutes instead of 4. (WALM-396)
  • Bound every relayer call the tools make. The pinned SDK aborts a request only when the caller passes a signal, which none of the memory methods do, so a stalled socket kept a tool running with no ceiling — memwal_remember was observed still going past 120s against a 90s budget. Accepts are bounded at 15s (MEMWAL_MCP_ACCEPT_DEADLINE_MS), waits at their own budget plus grace. The request is not cancelled — the SDK exposes no way to pass a signal — but the agent is no longer held by it.
  • Honour the relayer's retry_after instead of dropping the write. Once the per-delegate-key budget (60 weighted requests/minute) is spent the relayer answers 429 with a cooldown, and nothing backed off: the fact was never written and the agent saw only an opaque tool error. A short cooldown is now absorbed; a long one is reported with the wait named, stating plainly that the fact was NOT saved and pointing at the cheaper shape — one memwal_remember_bulk rather than N single calls, one memwal_remember_status(job_ids) rather than N status calls. Only rejections that provably never reached the handler retry, so /api/remember/bulk, which carries no idempotency key, cannot be duplicated by a retry.
  • memwal_remember sends a content-derived idempotency key, so the retry its own timeout message invites really does attach to the job already in flight instead of storing a second paid copy. The key is computed by the tool rather than relied on from the SDK, whose published build mints a random UUID per client instance.
  • memwal_remember_status accepts job_ids to settle a whole batch in one call, and reports a mixed batch honestly — a still-uploading row no longer renders the poll timeout as error=, which read as a failed write. Settling in one request also matters against the rate limit: 20 ids cost one request, not twenty.
  • The bridge's cold-start tool list no longer disagrees with the sidecar's. memwal_remember_status advertised only job_id, required, under additionalProperties: false, so the batch call the tools themselves instruct was rejected until tools/list_changed arrived; the waitMs ceiling advertised 60000 after the sidecar lowered it to 45000, which came back as an MCP validation error; and memwal_remember_bulk still carried its pre-queue description. Tests now pin the parts an agent acts on.
  • Persist the delegate keypair before sign-in hands the URL to the browser, and reclaim it on the next start. The browser's onchain add_delegate_key costs gas and is irreversible, and it happens before the callback that saves the private half, so a client that died in that window destroyed the only copy of a key the user had already paid for and left an orphaned registration nobody could use. (#793) Signing out discards the pending record along with the credentials, a second sign-in against the same relayer reuses the stranded key rather than minting over it, and a sign-in that cannot write the record fails instead of publishing a URL it cannot back. Reclaiming works on Mainnet; Testnet requires an account-id hint the recovering client does not have.

Changed

  • memwal_remember / memwal_remember_bulk return at accept by default (MEMWAL_MCP_REMEMBER_WAIT_MS=0, ~1s, job_id). The Walrus write continues in the background; do not treat that reply as stored. Set MEMWAL_MCP_REMEMBER_WAIT_MS=90000 to restore wait-for-blob_id (90s ceiling). Do not use a value between 0 and the real completion time — that pays the wait and still returns pending. The MCP TypeScript SDK's default tools/call timeout is 60s, so a 90s wait loses on hosts that do not raise it.

memwal-python@0.1.10

Choose a tag to compare

@github-actions github-actions released this 15 Sep 08:47
bfe3036

Release memwal Python SDK v0.1.10

@mysten-incubation/memwal@0.1.7

Choose a tag to compare

@github-actions github-actions released this 15 Sep 08:47
bfe3036

0.1.7

Added

  • restore() results include failed (required like truncated; SDK defaults omitted to 0) for permanent decrypt/UTF-8 failures instead of folding them into skipped or dropping them silently.

Fixed

  • Hash request bodies with @noble/hashes instead of WebCrypto-or-node:crypto, so the package no longer imports a Node builtin on a browser-reachable path. Vite externalises such an import without warning: the app builds clean and the browser crashes the first time the path runs. The fallback could never have helped a browser anyway — crypto.subtle is absent precisely when the page is not a secure context, where node:crypto is absent too — so it only served Node <19 while being the sole source of the exposure. sha256hex sits on the signed-request path, so every remember and recall reached it. (#322, WALM-136)
  • Declare engines.node >= 20.0.0, matching memwal-mcp and openclaw-memory-memwal. The SDK was the only published package without a floor. (WALM-599)
  • Empty-body 401s now use the same AUTH_REJECTED troubleshooting message as credential 401s instead of telling callers to run memwal_login. Headless SDK clients do not have that MCP tool.
  • account.ts and manual.ts PTBs use typed tx.pure helpers instead of the legacy untyped moveCall argument syntax that fails under modern @mysten/sui.