From 5cbd1cba69ac729579eec66c6a921ff637587fd1 Mon Sep 17 00:00:00 2001 From: harrymove-ctrl Date: Wed, 28 Oct 2026 14:16:29 +0700 Subject: [PATCH 1/7] fix(sdk,server,docs): forward maxTokens in middleware, fix bench error msg, add recall timeout note --- docs/sdk/api-reference.md | 2 ++ packages/sdk/src/ai/middleware.ts | 5 ++++- services/server/scripts/bench-recall-latency.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/sdk/api-reference.md b/docs/sdk/api-reference.md index bb5de32c6..60f882343 100644 --- a/docs/sdk/api-reference.md +++ b/docs/sdk/api-reference.md @@ -136,6 +136,8 @@ Search for memories matching a natural language query, scoped to `owner + namesp recall throws `MemWalRequestTimeout` (status 504). `isTimeoutError(err)` is true for that error and for a preflight `TimeoutError` whose `phase` names the stalled round-trip. +> **Timeout and retry behaviour**: `recall()` times out after `recallTimeoutMs` milliseconds (default: 5 000 ms, configurable in `MemWalConfig`). On network errors, the call throws immediately without internal retries — callers that need retry logic should wrap `recall()` in their own retry loop or configure a higher `recallTimeoutMs` for slow networks. + **Returns:** ```ts diff --git a/packages/sdk/src/ai/middleware.ts b/packages/sdk/src/ai/middleware.ts index e655f2fa5..baf125ac4 100644 --- a/packages/sdk/src/ai/middleware.ts +++ b/packages/sdk/src/ai/middleware.ts @@ -47,6 +47,8 @@ type AnyLanguageModel = any; export interface WithMemWalOptions extends MemWalConfig { /** Max memories to inject per request (default: 5) */ maxMemories?: number; + /** Token budget for recalled memories injected per request. Maps to recall({ maxTokens }). */ + maxTokens?: number; /** Auto-save new facts from conversation (default: true) */ autoSave?: boolean; /** Minimum similarity score to include a memory (0-1, default: 0.3) */ @@ -183,6 +185,7 @@ export function withMemWal( ) { const memwal = MemWal.create(options); const maxMemories = options.maxMemories ?? 5; + const maxTokens = options.maxTokens; const autoSave = options.autoSave ?? true; const minRelevance = options.minRelevance ?? 0.3; const debug = options.debug ?? false; @@ -258,7 +261,7 @@ export function withMemWal( const recallQuery = truncateUtf8Bytes(lastUserMessage, MAX_EMBED_INPUT_BYTES, { strategy: "head-tail", }); - const recallResult = await memwal.recall(recallQuery, maxMemories); + const recallResult = await memwal.recall({ query: recallQuery, limit: maxMemories, ...(maxTokens !== undefined ? { maxTokens } : {}) }); const relevant = recallResult.results.filter((m: RecallMemory) => { const relevance = memoryRelevance(m?.distance); return relevance !== null && relevance >= minRelevance; diff --git a/services/server/scripts/bench-recall-latency.ts b/services/server/scripts/bench-recall-latency.ts index 65906eb4d..037ca6c8c 100644 --- a/services/server/scripts/bench-recall-latency.ts +++ b/services/server/scripts/bench-recall-latency.ts @@ -420,7 +420,7 @@ async function runBatch( console.log(`ok ${ms(result!.latencyMs)} ${formatOk(result!)}`); } else { failCount++; - const errMsg = result!.error ?? `HTTP ${result!.statusCode}`; + const errMsg = result!.error?.trim() || `HTTP ${result!.statusCode}`; errors.push(errMsg); console.log(`FAILED: ${errMsg.slice(0, 120)}`); } From 1f6e42c17ce5e5ea18e67953e622deedb08b7e3c Mon Sep 17 00:00:00 2001 From: harrymove-ctrl Date: Wed, 28 Oct 2026 17:42:21 +0700 Subject: [PATCH 2/7] docs(sdk): fix recall timeout default to match SDK constant --- docs/sdk/api-reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sdk/api-reference.md b/docs/sdk/api-reference.md index 60f882343..f30210e3d 100644 --- a/docs/sdk/api-reference.md +++ b/docs/sdk/api-reference.md @@ -136,7 +136,7 @@ Search for memories matching a natural language query, scoped to `owner + namesp recall throws `MemWalRequestTimeout` (status 504). `isTimeoutError(err)` is true for that error and for a preflight `TimeoutError` whose `phase` names the stalled round-trip. -> **Timeout and retry behaviour**: `recall()` times out after `recallTimeoutMs` milliseconds (default: 5 000 ms, configurable in `MemWalConfig`). On network errors, the call throws immediately without internal retries — callers that need retry logic should wrap `recall()` in their own retry loop or configure a higher `recallTimeoutMs` for slow networks. +> **Timeout and retry behaviour**: `recall()` times out after `recallTimeoutMs` milliseconds (default: 15 000 ms, configurable in `MemWalConfig`). On network errors, the call throws immediately without internal retries — callers that need retry logic should wrap `recall()` in their own retry loop or configure a higher `recallTimeoutMs` for slow networks. **Returns:** From 71cecfe582299c4013e3627df7fdc26b3578487f Mon Sep 17 00:00:00 2001 From: harrymove-ctrl Date: Wed, 28 Oct 2026 20:13:28 +0700 Subject: [PATCH 3/7] docs(sdk): document rememberAndWait retry collapse and withMemWal maxTokens --- docs/sdk/api-reference.md | 3 ++ docs/sdk/production-readiness.md | 2 ++ docs/sdk/usage/with-memwal.md | 16 +++++++-- packages/sdk/src/memwal.ts | 8 +++++ .../sdk/test/withMemWal-recall-error.test.mjs | 34 +++++++++++++++++++ 5 files changed, 60 insertions(+), 3 deletions(-) diff --git a/docs/sdk/api-reference.md b/docs/sdk/api-reference.md index f30210e3d..78939f8a9 100644 --- a/docs/sdk/api-reference.md +++ b/docs/sdk/api-reference.md @@ -84,6 +84,8 @@ HTTP 426 throws `MemWalCompatibilityError`. Submit one memory and poll until the background job completes. Accepts `{ idempotencyKey?: string; dedupe?: boolean; pollIntervalMs?: number; timeoutMs?: number }` in `opts` (or in the `namespace` position when using the client default namespace). +> **Retry and idempotency collapse**: When `idempotencyKey` is omitted, the SDK derives a deterministic key (`r1-`) from `(30-minute UTC bucket, resolvedNamespace, text)` (or a bucket-less `dedupe:` key when `dedupe: true`) and caches it in the instance's in-memory `pendingRememberKeys` map until `waitForRememberJob()` completes terminally or `destroy()` is called. If `rememberAndWait()` times out or hits a transport error during polling, retrying `rememberAndWait(sameText, sameNamespace)` on the same client instance reuses that cached key (even across a 30-minute bucket boundary), and the relayer collapses the submission onto the existing job (`job_id`) without appending a duplicate memory. After a process restart, `pendingRememberKeys` is empty, so retries within the same 30-minute UTC bucket still collapse onto the existing job, while retries across a bucket boundary require an explicit `idempotencyKey` (or `dedupe: true`). + **Returns:** ```ts @@ -462,6 +464,7 @@ Wraps a Vercel AI SDK model with automatic memory recall and save. | Option | Default | Description | |--------|---------|-------------| | `maxMemories` | `5` | Max memories to inject per request | +| `maxTokens` | `undefined` | Optional token budget for recalled memories injected per request (forwarded to `recall({ maxTokens })`) | | `autoSave` | `true` | Auto-save new facts from conversation | | `minRelevance` | `0.3` | Minimum similarity score (0–1) to include a memory | | `debug` | `false` | Enable debug logging | diff --git a/docs/sdk/production-readiness.md b/docs/sdk/production-readiness.md index 8444f61e6..b1d549fee 100644 --- a/docs/sdk/production-readiness.md +++ b/docs/sdk/production-readiness.md @@ -48,6 +48,8 @@ Upgrade from 0.1.8 for bulk idempotency; its bulk methods send no key even if a The bucket is not a rolling 30-minute guarantee: 2 attempts a few seconds apart can cross its boundary. Changing batch order, content, or namespaces also changes the automatic key. +Within a single `MemWal` instance, `rememberAndWait()` also retains the generated key in an in-memory `pendingRememberKeys` map until the job reaches a terminal state (`done` or terminal failure) or `destroy()` is called. If `rememberAndWait(sameText, sameNamespace)` times out while polling, an immediate retry on the same client instance reuses the retained key from `pendingRememberKeys` (even across a 30-minute bucket boundary), so the relayer collapses the retry onto the original `job_id` instead of appending a second fact. A process restart clears `pendingRememberKeys` and falls back to the 30-minute time-bucket calculation. + For a durable queue, or any retry that can cross a bucket boundary or a process restart, persist one operation key with the payload. Reuse it on every submission attempt. ```ts diff --git a/docs/sdk/usage/with-memwal.md b/docs/sdk/usage/with-memwal.md index edbf577c2..f5c784300 100644 --- a/docs/sdk/usage/with-memwal.md +++ b/docs/sdk/usage/with-memwal.md @@ -58,15 +58,25 @@ const result = await generateText({ Before generation: - Reads the last user message -- Runs `recall()` against Walrus Memory -- Filters by relevance +- Runs `recall()` against Walrus Memory (bounded by `maxMemories` and optional `maxTokens`) +- Filters by minimum similarity (`minRelevance`, default `0.3`) - Injects memory context into the prompt After generation: -- Optionally runs `analyze()` on the user message +- Optionally runs `analyze()` on the user message (`autoSave`, default `true`) - Saves extracted facts asynchronously +## Options + +| Option | Default | Description | +| --- | --- | --- | +| `maxMemories` | `5` | Maximum number of memories to recall and inject per request (top-K limit) | +| `maxTokens` | `undefined` | Optional token budget cap on recalled memories injected into the prompt (forwarded to `recall({ maxTokens })`) | +| `minRelevance` | `0.3` | Minimum similarity score (`0`–`1`) required for a recalled memory to be injected | +| `autoSave` | `true` | Automatically run `analyze()` after generation to extract and store new facts | +| `debug` | `false` | Log recall and auto-save activity to `console.warn` | + Set a namespace explicitly for each product surface that uses the middleware. Otherwise recalled and auto-saved memories fall back to `"default"`. diff --git a/packages/sdk/src/memwal.ts b/packages/sdk/src/memwal.ts index 6020ebb54..6a2ead69b 100644 --- a/packages/sdk/src/memwal.ts +++ b/packages/sdk/src/memwal.ts @@ -782,6 +782,14 @@ export class MemWal { /** * Remember something and wait for the background job to complete. + * + * When `idempotencyKey` is omitted, derives a deterministic key (`r1-`) + * from `(30-minute UTC bucket, resolvedNamespace, text)` (or `dedupe:` + * when `dedupe: true`) and retains it in `pendingRememberKeys` until the job + * settles terminally or `destroy()` is called. Retrying `rememberAndWait` with + * the same `(text, namespace)` after a polling timeout reuses the key so the + * relayer collapses the retry onto the existing `job_id` without appending a + * duplicate memory. */ async rememberAndWait( text: string, diff --git a/packages/sdk/test/withMemWal-recall-error.test.mjs b/packages/sdk/test/withMemWal-recall-error.test.mjs index 65e52dccf..f8fa39168 100644 --- a/packages/sdk/test/withMemWal-recall-error.test.mjs +++ b/packages/sdk/test/withMemWal-recall-error.test.mjs @@ -136,3 +136,37 @@ test("if onRecallError throws, generation still proceeds", async () => { console.warn = originalWarn; } }); + +test("withMemWal forwards maxTokens to recall() when configured", async () => { + const originalRecall = MemWal.prototype.recall; + const recallCalls = []; + + MemWal.prototype.recall = async (params) => { + recallCalls.push(params); + return { results: [], total: 0 }; + }; + + try { + const model = withMemWal(fakeLanguageModel(), { + key: TEST_KEY, + accountId: TEST_ACCOUNT_ID, + autoSave: false, + maxMemories: 7, + maxTokens: 256, + }); + + await generateText({ + model, + prompt: "Summarize my preferences", + }); + + assert.equal(recallCalls.length, 1); + assert.deepEqual(recallCalls[0], { + query: "Summarize my preferences", + limit: 7, + maxTokens: 256, + }); + } finally { + MemWal.prototype.recall = originalRecall; + } +}); From faa5557103e9b38f2d7160e113110a88855ffb4a Mon Sep 17 00:00:00 2001 From: harrymove-ctrl Date: Thu, 29 Oct 2026 08:18:53 +0700 Subject: [PATCH 4/7] docs(sdk): clarify terminal success cleanup and failure recovery for pendingRememberKeys --- docs/sdk/api-reference.md | 2 +- docs/sdk/production-readiness.md | 2 +- packages/sdk/src/memwal.ts | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/sdk/api-reference.md b/docs/sdk/api-reference.md index 78939f8a9..02bcdd3bd 100644 --- a/docs/sdk/api-reference.md +++ b/docs/sdk/api-reference.md @@ -84,7 +84,7 @@ HTTP 426 throws `MemWalCompatibilityError`. Submit one memory and poll until the background job completes. Accepts `{ idempotencyKey?: string; dedupe?: boolean; pollIntervalMs?: number; timeoutMs?: number }` in `opts` (or in the `namespace` position when using the client default namespace). -> **Retry and idempotency collapse**: When `idempotencyKey` is omitted, the SDK derives a deterministic key (`r1-`) from `(30-minute UTC bucket, resolvedNamespace, text)` (or a bucket-less `dedupe:` key when `dedupe: true`) and caches it in the instance's in-memory `pendingRememberKeys` map until `waitForRememberJob()` completes terminally or `destroy()` is called. If `rememberAndWait()` times out or hits a transport error during polling, retrying `rememberAndWait(sameText, sameNamespace)` on the same client instance reuses that cached key (even across a 30-minute bucket boundary), and the relayer collapses the submission onto the existing job (`job_id`) without appending a duplicate memory. After a process restart, `pendingRememberKeys` is empty, so retries within the same 30-minute UTC bucket still collapse onto the existing job, while retries across a bucket boundary require an explicit `idempotencyKey` (or `dedupe: true`). +> **Retry and idempotency collapse**: When `idempotencyKey` is omitted, the SDK derives a deterministic key (`r1-`) from `(30-minute UTC bucket, resolvedNamespace, text)` (or a bucket-less `dedupe:` key when `dedupe: true`) and caches it in the instance's in-memory `pendingRememberKeys` map until `waitForRememberJob()` completes with terminal success or `destroy()` is called. If `rememberAndWait()` times out or hits a transport error during polling, retrying `rememberAndWait(sameText, sameNamespace)` on the same client instance reuses that cached key (even across a 30-minute bucket boundary), and the relayer collapses the submission onto the existing job (`job_id`) without appending a duplicate memory. Because `pendingRememberKeys` is only cleared upon terminal success (or via `destroy()`), terminal failures (`MemWalRememberJobFailedError` or `MemWalRememberJobNotFoundError`) leave the key cached on the client instance; to execute a fresh submission after a terminal failure on the same instance, pass an explicit `idempotencyKey` in call options or call `destroy()`. After a process restart, `pendingRememberKeys` is empty, so retries within the same 30-minute UTC bucket still collapse onto the existing job, while retries across a bucket boundary require an explicit `idempotencyKey` (or `dedupe: true`). **Returns:** diff --git a/docs/sdk/production-readiness.md b/docs/sdk/production-readiness.md index b1d549fee..0aa2da88f 100644 --- a/docs/sdk/production-readiness.md +++ b/docs/sdk/production-readiness.md @@ -48,7 +48,7 @@ Upgrade from 0.1.8 for bulk idempotency; its bulk methods send no key even if a The bucket is not a rolling 30-minute guarantee: 2 attempts a few seconds apart can cross its boundary. Changing batch order, content, or namespaces also changes the automatic key. -Within a single `MemWal` instance, `rememberAndWait()` also retains the generated key in an in-memory `pendingRememberKeys` map until the job reaches a terminal state (`done` or terminal failure) or `destroy()` is called. If `rememberAndWait(sameText, sameNamespace)` times out while polling, an immediate retry on the same client instance reuses the retained key from `pendingRememberKeys` (even across a 30-minute bucket boundary), so the relayer collapses the retry onto the original `job_id` instead of appending a second fact. A process restart clears `pendingRememberKeys` and falls back to the 30-minute time-bucket calculation. +Within a single `MemWal` instance, `rememberAndWait()` also retains the generated key in an in-memory `pendingRememberKeys` map until the job reaches terminal success (`done`) or `destroy()` is called. If `rememberAndWait(sameText, sameNamespace)` times out while polling or encounters transient network errors, an immediate retry on the same client instance reuses the retained key from `pendingRememberKeys` (even across a 30-minute bucket boundary), so the relayer collapses the retry onto the original `job_id` instead of appending a second fact. On terminal failure (`failed` or `not_found`), the key remains cached on the instance; callers recovering from terminal failures on that client should provide an explicit `idempotencyKey` in call options or call `destroy()`. A process restart clears `pendingRememberKeys` and falls back to the 30-minute time-bucket calculation. For a durable queue, or any retry that can cross a bucket boundary or a process restart, persist one operation key with the payload. Reuse it on every submission attempt. diff --git a/packages/sdk/src/memwal.ts b/packages/sdk/src/memwal.ts index 6a2ead69b..b10e3c75f 100644 --- a/packages/sdk/src/memwal.ts +++ b/packages/sdk/src/memwal.ts @@ -786,10 +786,11 @@ export class MemWal { * When `idempotencyKey` is omitted, derives a deterministic key (`r1-`) * from `(30-minute UTC bucket, resolvedNamespace, text)` (or `dedupe:` * when `dedupe: true`) and retains it in `pendingRememberKeys` until the job - * settles terminally or `destroy()` is called. Retrying `rememberAndWait` with - * the same `(text, namespace)` after a polling timeout reuses the key so the + * succeeds terminally (`done`) or `destroy()` is called. Retrying `rememberAndWait` + * with the same `(text, namespace)` after a polling timeout reuses the key so the * relayer collapses the retry onto the existing `job_id` without appending a - * duplicate memory. + * duplicate memory. After a terminal failure, provide an explicit `idempotencyKey` + * or call `destroy()` to trigger a fresh operation on the same instance. */ async rememberAndWait( text: string, From b67c3ba7823dcb9dc71f60892c0679adca127f1b Mon Sep 17 00:00:00 2001 From: harrymove-ctrl Date: Thu, 29 Oct 2026 11:14:37 +0700 Subject: [PATCH 5/7] docs(sdk): clarify client recreation and idempotency key recovery after destroy --- docs/sdk/api-reference.md | 2 +- packages/sdk/src/ai/middleware.ts | 5 ++++- packages/sdk/src/memwal.ts | 11 ++++++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/sdk/api-reference.md b/docs/sdk/api-reference.md index 02bcdd3bd..8473cc469 100644 --- a/docs/sdk/api-reference.md +++ b/docs/sdk/api-reference.md @@ -84,7 +84,7 @@ HTTP 426 throws `MemWalCompatibilityError`. Submit one memory and poll until the background job completes. Accepts `{ idempotencyKey?: string; dedupe?: boolean; pollIntervalMs?: number; timeoutMs?: number }` in `opts` (or in the `namespace` position when using the client default namespace). -> **Retry and idempotency collapse**: When `idempotencyKey` is omitted, the SDK derives a deterministic key (`r1-`) from `(30-minute UTC bucket, resolvedNamespace, text)` (or a bucket-less `dedupe:` key when `dedupe: true`) and caches it in the instance's in-memory `pendingRememberKeys` map until `waitForRememberJob()` completes with terminal success or `destroy()` is called. If `rememberAndWait()` times out or hits a transport error during polling, retrying `rememberAndWait(sameText, sameNamespace)` on the same client instance reuses that cached key (even across a 30-minute bucket boundary), and the relayer collapses the submission onto the existing job (`job_id`) without appending a duplicate memory. Because `pendingRememberKeys` is only cleared upon terminal success (or via `destroy()`), terminal failures (`MemWalRememberJobFailedError` or `MemWalRememberJobNotFoundError`) leave the key cached on the client instance; to execute a fresh submission after a terminal failure on the same instance, pass an explicit `idempotencyKey` in call options or call `destroy()`. After a process restart, `pendingRememberKeys` is empty, so retries within the same 30-minute UTC bucket still collapse onto the existing job, while retries across a bucket boundary require an explicit `idempotencyKey` (or `dedupe: true`). +> **Retry and idempotency collapse**: When `idempotencyKey` is omitted, the SDK derives a deterministic key (`r1-`) from `(30-minute UTC bucket, resolvedNamespace, text)` (or a bucket-less `dedupe:` key when `dedupe: true`) and caches it in the instance's in-memory `pendingRememberKeys` map until `waitForRememberJob()` completes with terminal success. If `rememberAndWait()` times out or hits a transport error during polling, retrying `rememberAndWait(sameText, sameNamespace)` on the same client instance reuses that cached key (even across a 30-minute bucket boundary), and the relayer collapses the submission onto the existing job (`job_id`) without appending a duplicate memory. Because `pendingRememberKeys` is only cleared upon terminal success, terminal failures (`MemWalRememberJobFailedError` or `MemWalRememberJobNotFoundError`) leave the key cached on the client instance; to execute a fresh submission after a terminal failure on the live instance, pass an explicit fresh `idempotencyKey` in call options, or explicitly destroy and create a new client with credentials and a fresh operation key (recreating alone can still derive the same bucket key). After a process restart, `pendingRememberKeys` is empty, so retries within the same 30-minute UTC bucket still collapse onto the existing job, while retries across a bucket boundary require an explicit `idempotencyKey` (or `dedupe: true`). **Returns:** diff --git a/packages/sdk/src/ai/middleware.ts b/packages/sdk/src/ai/middleware.ts index baf125ac4..aaed62d68 100644 --- a/packages/sdk/src/ai/middleware.ts +++ b/packages/sdk/src/ai/middleware.ts @@ -261,7 +261,10 @@ export function withMemWal( const recallQuery = truncateUtf8Bytes(lastUserMessage, MAX_EMBED_INPUT_BYTES, { strategy: "head-tail", }); - const recallResult = await memwal.recall({ query: recallQuery, limit: maxMemories, ...(maxTokens !== undefined ? { maxTokens } : {}) }); + const recallResult = + maxTokens !== undefined + ? await memwal.recall({ query: recallQuery, limit: maxMemories, maxTokens }) + : await memwal.recall(recallQuery, maxMemories); const relevant = recallResult.results.filter((m: RecallMemory) => { const relevance = memoryRelevance(m?.distance); return relevance !== null && relevance >= minRelevance; diff --git a/packages/sdk/src/memwal.ts b/packages/sdk/src/memwal.ts index b10e3c75f..45402e559 100644 --- a/packages/sdk/src/memwal.ts +++ b/packages/sdk/src/memwal.ts @@ -786,11 +786,12 @@ export class MemWal { * When `idempotencyKey` is omitted, derives a deterministic key (`r1-`) * from `(30-minute UTC bucket, resolvedNamespace, text)` (or `dedupe:` * when `dedupe: true`) and retains it in `pendingRememberKeys` until the job - * succeeds terminally (`done`) or `destroy()` is called. Retrying `rememberAndWait` - * with the same `(text, namespace)` after a polling timeout reuses the key so the - * relayer collapses the retry onto the existing `job_id` without appending a - * duplicate memory. After a terminal failure, provide an explicit `idempotencyKey` - * or call `destroy()` to trigger a fresh operation on the same instance. + * succeeds terminally (`done`). Retrying `rememberAndWait` with the same + * `(text, namespace)` after a polling timeout reuses the key so the relayer + * collapses the retry onto the existing `job_id` without appending a + * duplicate memory. After a terminal failure, provide an explicit fresh + * `idempotencyKey` on the live instance, or explicitly destroy and create + * a new client with credentials and a fresh operation key. */ async rememberAndWait( text: string, From ede2e3140538fccab4bbb950afb846fca844563b Mon Sep 17 00:00:00 2001 From: harrymove-ctrl Date: Thu, 29 Oct 2026 13:22:17 +0700 Subject: [PATCH 6/7] docs(sdk): align production-readiness terminal failure recovery wording --- docs/sdk/production-readiness.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sdk/production-readiness.md b/docs/sdk/production-readiness.md index 0aa2da88f..911e50ed3 100644 --- a/docs/sdk/production-readiness.md +++ b/docs/sdk/production-readiness.md @@ -48,7 +48,7 @@ Upgrade from 0.1.8 for bulk idempotency; its bulk methods send no key even if a The bucket is not a rolling 30-minute guarantee: 2 attempts a few seconds apart can cross its boundary. Changing batch order, content, or namespaces also changes the automatic key. -Within a single `MemWal` instance, `rememberAndWait()` also retains the generated key in an in-memory `pendingRememberKeys` map until the job reaches terminal success (`done`) or `destroy()` is called. If `rememberAndWait(sameText, sameNamespace)` times out while polling or encounters transient network errors, an immediate retry on the same client instance reuses the retained key from `pendingRememberKeys` (even across a 30-minute bucket boundary), so the relayer collapses the retry onto the original `job_id` instead of appending a second fact. On terminal failure (`failed` or `not_found`), the key remains cached on the instance; callers recovering from terminal failures on that client should provide an explicit `idempotencyKey` in call options or call `destroy()`. A process restart clears `pendingRememberKeys` and falls back to the 30-minute time-bucket calculation. +Within a single `MemWal` instance, `rememberAndWait()` also retains the generated key in an in-memory `pendingRememberKeys` map until the job reaches terminal success (`done`). If `rememberAndWait(sameText, sameNamespace)` times out while polling or encounters transient network errors, an immediate retry on the same client instance reuses the retained key from `pendingRememberKeys` (even across a 30-minute bucket boundary), so the relayer collapses the retry onto the original `job_id` instead of appending a second fact. On terminal failure (`failed` or `not_found`), the key remains cached on the instance; callers recovering from terminal failures on the live instance should provide an explicit fresh `idempotencyKey` in call options, or explicitly destroy and create a new client with credentials and a fresh operation key (recreating alone can still derive the same bucket key). A process restart clears `pendingRememberKeys` and falls back to the 30-minute time-bucket calculation. For a durable queue, or any retry that can cross a bucket boundary or a process restart, persist one operation key with the payload. Reuse it on every submission attempt. From a199c186e514cedeb9b99f3d6c076ca2eba352d9 Mon Sep 17 00:00:00 2001 From: harrymove-ctrl Date: Sun, 1 Nov 2026 11:59:55 +0700 Subject: [PATCH 7/7] chore(sdk): bump version to 0.1.13 for withMemWal maxTokens fix --- docs/sdk/api-reference.md | 2 +- docs/sdk/changelog.mdx | 4 +++- packages/sdk/CHANGELOG.md | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/sdk/api-reference.md b/docs/sdk/api-reference.md index 8473cc469..4861f75ff 100644 --- a/docs/sdk/api-reference.md +++ b/docs/sdk/api-reference.md @@ -138,7 +138,7 @@ Search for memories matching a natural language query, scoped to `owner + namesp recall throws `MemWalRequestTimeout` (status 504). `isTimeoutError(err)` is true for that error and for a preflight `TimeoutError` whose `phase` names the stalled round-trip. -> **Timeout and retry behaviour**: `recall()` times out after `recallTimeoutMs` milliseconds (default: 15 000 ms, configurable in `MemWalConfig`). On network errors, the call throws immediately without internal retries — callers that need retry logic should wrap `recall()` in their own retry loop or configure a higher `recallTimeoutMs` for slow networks. +> **Timeout and retry behaviour**: `recall()` times out after `recallTimeoutMs` milliseconds (default: 15 000 ms, configurable in `MemWalConfig` and overridable per call via `timeoutMs`). On network errors, the call throws immediately without internal retries; callers that need retry logic should wrap `recall()` in their own retry loop or configure a higher `recallTimeoutMs` for slow networks. **Returns:** diff --git a/docs/sdk/changelog.mdx b/docs/sdk/changelog.mdx index 7960f95a8..82b475cfb 100644 --- a/docs/sdk/changelog.mdx +++ b/docs/sdk/changelog.mdx @@ -28,7 +28,8 @@ questions: - When was bulk remember added to the Walrus Memory SDK? - What security improvements have been made to the MemWal SDK? answer: >- - Version 0.1.13 aligns MemWal and MemWalMock input validation, rejects invalid idempotencyKey values before signing and non-positive limits consistently, and preserves access to whitespace namespaces on read paths while rejecting empty namespace strings. Version 0.1.12 bounds withMemWal recall queries (preserving both head context and tail question/instructions via strategy: "head-tail") to 16,384 UTF-8 bytes and auto-save analyze inputs to 65,536 UTF-8 bytes without splitting multi-byte UTF-8 codepoints across boundaries, preventing HTTP 400 errors on long turns. Version 0.1.11 adds opt-in content-hash deduplication (dedupe: true) for remember(), copies caller-supplied Uint8Array keys on client creation, hardens MemWalManual.destroy() secret cleanup and client invalidation, validates and clamps recalled memory distance before similarity relevance filtering and formatting, validates listNamespaces and listMemories limits before signing, adds AbortSignal cancellation support to analyze() via AnalyzeOptions.signal, and validates manual-mode vector and encryptedData inputs before signing. + answer: >- + Version 0.1.13 aligns MemWal and MemWalMock input validation, rejects invalid idempotencyKey values before signing and non-positive limits consistently, and preserves access to whitespace namespaces on read paths while rejecting empty namespace strings. It also updates withMemWal to accept maxTokens in WithMemWalOptions and forward it to recall({ maxTokens }) before calling the underlying language model. Version 0.1.12 bounds withMemWal recall queries (preserving both head context and tail question/instructions via strategy: "head-tail") to 16,384 UTF-8 bytes and auto-save analyze inputs to 65,536 UTF-8 bytes without splitting multi-byte UTF-8 codepoints across boundaries, preventing HTTP 400 errors on long turns. Version 0.1.11 adds opt-in content-hash deduplication (dedupe: true) for remember(), copies caller-supplied Uint8Array keys on client creation, hardens MemWalManual.destroy() secret cleanup and client invalidation, validates and clamps recalled memory distance before similarity relevance filtering and formatting, validates listNamespaces and listMemories limits before signing, adds AbortSignal cancellation support to analyze() via AnalyzeOptions.signal, and validates manual-mode vector and encryptedData inputs before signing. --- ## 0.1.13 @@ -36,6 +37,7 @@ answer: >- ### Fixed - `MemWal` and `MemWalMock` share input validation on `remember*`, `rememberBulk*`, `analyze*`, `embed`, `recall`, `restore`, and `listNamespaces`, rejecting invalid `idempotencyKey` values before signing and rejecting `limit: 0` consistently. On read paths (`recall`, `recallManual`, `restore`, and `listMemories`), empty string `""` is rejected while whitespace namespaces remain readable to match the server's namespace validation. (Refs #978) +- `withMemWal` accepts `maxTokens` in `WithMemWalOptions` and forwards it to `recall({ maxTokens })` before calling the underlying language model, capping the token budget of recalled memories injected into each prompt. ## 0.1.12 diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index 96cb5aa22..4f46a5514 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - `MemWal` and `MemWalMock` share input validation on `remember*`, `rememberBulk*`, `analyze*`, `embed`, `recall`, `restore`, and `listNamespaces`, rejecting invalid `idempotencyKey` values before signing and rejecting `limit: 0` consistently. On read paths (`recall`, `recallManual`, `restore`, and `listMemories`), empty string `""` is rejected while whitespace namespaces remain readable to match the server's namespace validation. (Refs #978) +- `withMemWal` accepts `maxTokens` in `WithMemWalOptions` and forwards it to `recall({ maxTokens })` before calling the underlying language model, capping the token budget of recalled memories injected into each prompt. ## 0.1.12