From c2eb86be56ab9a2e49eabf80edbc0f9958e24d71 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Thu, 25 Jun 2026 06:09:37 +0000 Subject: [PATCH 1/3] fix(unit-only): retrieve SUMMARIZATION summaries with actor-scoped namespace Cross-session SUMMARIZATION recall was silently broken: the vended Strands session.py templates retrieved summaries with a per-session namespace (/summaries/{actor_id}/{session_id}), which only prefix-matches the current session and never surfaces prior sessions' summaries. Re-apply PR #1299's one-line fix to use an actor-scoped namespace (/summaries/{actor_id}) in all three templates (http, agui, a2a), regenerate the asset snapshot, update docs/memory.md, and add a regression test to prevent another silent revert (reverted previously by squash release #1547). Fixes #665 --- docs/memory.md | 4 +- .../assets.snapshot.test.ts.snap | 6 +-- .../__tests__/summarization-namespace.test.ts | 43 +++++++++++++++++++ .../strands/capabilities/memory/session.py | 2 +- .../strands/capabilities/memory/session.py | 2 +- .../strands/capabilities/memory/session.py | 2 +- 6 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 src/assets/__tests__/summarization-namespace.test.ts diff --git a/docs/memory.md b/docs/memory.md index 0f38513d2..41064ddb0 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -92,7 +92,7 @@ If you created an Strands agent without memory and want to integrate it with you retrieval_config = { f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.5), - f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5) + f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5) } return AgentCoreMemorySessionManager( @@ -157,7 +157,7 @@ specific memory configuration: | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `none` | No memory resource created | | `shortTerm` | Memory with no strategies (session context via event expiry only, default 30 days) | -| `longAndShortTerm` | Memory with four strategies: `SEMANTIC` (`/users/{actorId}/facts`), `USER_PREFERENCE` (`/users/{actorId}/preferences`), `SUMMARIZATION` (`/summaries/{actorId}/{sessionId}`), `EPISODIC` (`/episodes/{actorId}/{sessionId}`, reflection: `/episodes/{actorId}`) | +| `longAndShortTerm` | Memory with four strategies: `SEMANTIC` (`/users/{actorId}/facts`), `USER_PREFERENCE` (`/users/{actorId}/preferences`), `SUMMARIZATION` (`/summaries/{actorId}`), `EPISODIC` (`/episodes/{actorId}/{sessionId}`, reflection: `/episodes/{actorId}`) | **Short-term memory** provides basic conversation context within a session — events are stored and expire after the configured duration, but no long-term extraction or search is performed. diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index 6cfcc62d2..548365c5f 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -2255,7 +2255,7 @@ def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Opti f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5), {{/if}} {{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}} - f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5), + f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5), {{/if}} } {{/if}} @@ -3277,7 +3277,7 @@ def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Opti f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5), {{/if}} {{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}} - f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5), + f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5), {{/if}} } {{/if}} @@ -6545,7 +6545,7 @@ def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Opti f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5), {{/if}} {{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}} - f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5), + f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5), {{/if}} } {{/if}} diff --git a/src/assets/__tests__/summarization-namespace.test.ts b/src/assets/__tests__/summarization-namespace.test.ts new file mode 100644 index 000000000..cbf61edc0 --- /dev/null +++ b/src/assets/__tests__/summarization-namespace.test.ts @@ -0,0 +1,43 @@ +/** + * Regression test for cross-session SUMMARIZATION recall (issue #665). + * + * The SUMMARIZATION retrieval namespace must be actor-scoped (`/summaries/{actor_id}`), + * not session-scoped. The SDK's namespace_path is a hierarchical prefix match, so a + * per-session prefix (`/summaries/{actor_id}/{session_id}`) only ever matches the current + * session's own summaries and never surfaces summaries written by prior sessions — + * silently breaking cross-session recall. This guards against another silent revert + * (see PR #1299, reverted by squash release #1547). + */ +import Handlebars from 'handlebars'; +import * as fs from 'fs'; +import * as path from 'path'; +import { describe, expect, it } from 'vitest'; +// Importing render registers the `includes` Handlebars helper used by the template. +import '../../cli/templates/render.js'; + +const FLAVORS = ['http', 'agui', 'a2a'] as const; + +function renderSessionTemplate(flavor: string): string { + const templatePath = path.resolve( + __dirname, + '..', + 'python', + flavor, + 'strands', + 'capabilities', + 'memory', + 'session.py' + ); + const content = fs.readFileSync(templatePath, 'utf-8'); + return Handlebars.compile(content)({ + memoryProviders: [{ envVarName: 'MEMORY_TEST_ID', strategies: ['SUMMARIZATION'] }], + }); +} + +describe('SUMMARIZATION retrieval namespace', () => { + it.each(FLAVORS)('%s session.py uses an actor-scoped summary namespace', flavor => { + const rendered = renderSessionTemplate(flavor); + expect(rendered).toContain('f"/summaries/{actor_id}": RetrievalConfig'); + expect(rendered).not.toContain('/summaries/{actor_id}/{session_id}'); + }); +}); diff --git a/src/assets/python/a2a/strands/capabilities/memory/session.py b/src/assets/python/a2a/strands/capabilities/memory/session.py index 1a8b7e5a3..f029d5d1a 100644 --- a/src/assets/python/a2a/strands/capabilities/memory/session.py +++ b/src/assets/python/a2a/strands/capabilities/memory/session.py @@ -28,7 +28,7 @@ def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Opti f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5), {{/if}} {{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}} - f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5), + f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5), {{/if}} } {{/if}} diff --git a/src/assets/python/agui/strands/capabilities/memory/session.py b/src/assets/python/agui/strands/capabilities/memory/session.py index 1a8b7e5a3..f029d5d1a 100644 --- a/src/assets/python/agui/strands/capabilities/memory/session.py +++ b/src/assets/python/agui/strands/capabilities/memory/session.py @@ -28,7 +28,7 @@ def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Opti f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5), {{/if}} {{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}} - f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5), + f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5), {{/if}} } {{/if}} diff --git a/src/assets/python/http/strands/capabilities/memory/session.py b/src/assets/python/http/strands/capabilities/memory/session.py index a00b46666..e9dc85e86 100644 --- a/src/assets/python/http/strands/capabilities/memory/session.py +++ b/src/assets/python/http/strands/capabilities/memory/session.py @@ -28,7 +28,7 @@ def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Opti f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5), {{/if}} {{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}} - f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5), + f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5), {{/if}} } {{/if}} From 6a5b1ffdcdeb2aafee6c184c7b6db1a3bbf1161a Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Mon, 29 Jun 2026 14:36:23 +0000 Subject: [PATCH 2/3] fix(templates): retrieve SUMMARIZATION summaries with an actor-scoped namespace (#665) From fb9127b4e1fa434fca2abb14330aaa0e558476b1 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Mon, 29 Jun 2026 14:44:59 +0000 Subject: [PATCH 3/3] style: format docs/memory.md with prettier --- docs/memory.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/memory.md b/docs/memory.md index 41064ddb0..6e1a0f63f 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -153,10 +153,10 @@ async def invoke(payload, context): The `create` and `add agent` commands accept a `--memory` flag with one of three shorthand values. Each maps to a specific memory configuration: -| Shorthand | Strategies Created | -| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `none` | No memory resource created | -| `shortTerm` | Memory with no strategies (session context via event expiry only, default 30 days) | +| Shorthand | Strategies Created | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `none` | No memory resource created | +| `shortTerm` | Memory with no strategies (session context via event expiry only, default 30 days) | | `longAndShortTerm` | Memory with four strategies: `SEMANTIC` (`/users/{actorId}/facts`), `USER_PREFERENCE` (`/users/{actorId}/preferences`), `SUMMARIZATION` (`/summaries/{actorId}`), `EPISODIC` (`/episodes/{actorId}/{sessionId}`, reflection: `/episodes/{actorId}`) | **Short-term memory** provides basic conversation context within a session — events are stored and expire after the