From f44c5a3ffe80e888c3fce462f30c801c7405c6be Mon Sep 17 00:00:00 2001 From: gitikavj Date: Tue, 3 Nov 2026 05:51:57 +0000 Subject: [PATCH 1/2] fix(memory): use semantic and episodic defaults without score filtering --- docs/harness-project-configuration.md | 11 +-- .../templates/a2a-python-strands/README.md | 7 ++ .../a2a-python-strands/memory/session.py | 6 +- .../templates/agent-python-strands/README.md | 7 ++ .../agent-python-strands/memory/session.py | 6 +- .../agent-typescript-strands/README.md | 5 ++ .../agent-typescript-strands/memory/memory.ts | 2 - .../templates/agui-python-strands/README.md | 7 ++ .../agui-python-strands/memory/session.py | 6 +- .../export-harness-python/memory/session.py | 8 +-- .../__snapshots__/manager.test.ts.snap | 36 ---------- .../bedrockAgentImport/strandsTranslator.ts | 6 +- .../bedrockAgentImport/translator.test.ts | 16 +++++ src/core/project/manager.export.test.ts | 70 ++++++++++++------- .../__snapshots__/harness.test.ts.snap | 7 +- src/core/project/templates/harness.test.ts | 5 +- src/core/project/templates/harness.ts | 6 +- src/core/project/templates/runtime.test.ts | 39 +++++++++-- .../project/add/harness/index.test.ts | 4 +- .../project/add/runtime/index.test.ts | 2 - .../project/create/create.screen.test.tsx | 4 +- src/handlers/project/project.test.ts | 2 - src/handlers/project/shortcuts.ts | 15 ++-- src/projectSchemas/memory.ts | 1 + 24 files changed, 164 insertions(+), 114 deletions(-) diff --git a/docs/harness-project-configuration.md b/docs/harness-project-configuration.md index 477783da80..4f597a0d12 100644 --- a/docs/harness-project-configuration.md +++ b/docs/harness-project-configuration.md @@ -48,7 +48,10 @@ model: bedrockModelConfig: modelId: global.anthropic.claude-sonnet-5-5 memory: - managedMemoryConfiguration: {} + managedMemoryConfiguration: + strategies: + - SEMANTIC + - EPISODIC ``` Edit the file, then run `agentcore deploy` from the project directory to @@ -190,8 +193,8 @@ Memory persists conversation events and can extract information for later retrieval. It is separate from the conversation truncation settings that control what fits in a model request. -Newly scaffolded harnesses use `memory: { managedMemoryConfiguration: {} }` unless another memory -configuration was supplied. Removing `memory` from an existing YAML file disables +Newly scaffolded harnesses use managed memory with `SEMANTIC` and `EPISODIC` strategies unless +another memory configuration was supplied. Removing `memory` from an existing YAML file disables memory. Reading the file never adds the scaffold default. ### Managed Memory @@ -203,8 +206,6 @@ memory: managedMemoryConfiguration: strategies: - SEMANTIC - - SUMMARIZATION - - USER_PREFERENCE - EPISODIC eventExpiryDuration: 30 ``` diff --git a/src/assets/templates/a2a-python-strands/README.md b/src/assets/templates/a2a-python-strands/README.md index 7da24eb69a..feef497dc6 100644 --- a/src/assets/templates/a2a-python-strands/README.md +++ b/src/assets/templates/a2a-python-strands/README.md @@ -8,6 +8,13 @@ This agent speaks the A2A protocol. Other agents discover it via its agent card `/.well-known/agent-card.json` and invoke it with JSON-RPC (`message/send`, `message/stream`) at the server root, matching the AgentCore Runtime A2A service contract. +## Memory + +The template provisions semantic and episodic memory. `memory/session.py` retrieves up to three +semantic facts for the actor and five episodes for the current session. The default +`relevance_score=0.0` selects results by `top_k` without score filtering. To opt into score +filtering, increase `relevance_score` in the retrieval configuration. + ## Adding Tools Define tools with the `@tool` decorator in `main.py` and add them to the `tools` list: diff --git a/src/assets/templates/a2a-python-strands/memory/session.py b/src/assets/templates/a2a-python-strands/memory/session.py index 6372a2f685..cd4c56aa82 100644 --- a/src/assets/templates/a2a-python-strands/memory/session.py +++ b/src/assets/templates/a2a-python-strands/memory/session.py @@ -18,10 +18,8 @@ def get_memory_session_manager( session_id = session_id or uuid.uuid4().hex retrieval_config = { - f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.5), - f"/users/{actor_id}/preferences": RetrievalConfig(top_k=3, relevance_score=0.5), - f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5), - f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5), + f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.0), + f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.0), } return AgentCoreMemorySessionManager( diff --git a/src/assets/templates/agent-python-strands/README.md b/src/assets/templates/agent-python-strands/README.md index 0e60591533..214088ffcb 100644 --- a/src/assets/templates/agent-python-strands/README.md +++ b/src/assets/templates/agent-python-strands/README.md @@ -13,6 +13,13 @@ file defines a Starlette ASGI app with the Strands Agent SDK running within. `model/load.py` instantiates the chosen model provider. +## Memory + +The template provisions semantic and episodic memory. `memory/session.py` retrieves up to three +semantic facts for the actor and five episodes for the current session. The default +`relevance_score=0.0` selects results by `top_k` without score filtering. To opt into score +filtering, increase `relevance_score` in the retrieval configuration. + ## Input Validation Validate invocation input before forwarding it to Strands. Keep plain prompts typed as strings. If the app accepts a diff --git a/src/assets/templates/agent-python-strands/memory/session.py b/src/assets/templates/agent-python-strands/memory/session.py index aa8e46e5a0..baa569eb90 100644 --- a/src/assets/templates/agent-python-strands/memory/session.py +++ b/src/assets/templates/agent-python-strands/memory/session.py @@ -14,10 +14,8 @@ def get_memory_session_manager( return None retrieval_config = { - f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.5), - f"/users/{actor_id}/preferences": RetrievalConfig(top_k=3, relevance_score=0.5), - f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5), - f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5), + f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.0), + f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.0), } return AgentCoreMemorySessionManager( diff --git a/src/assets/templates/agent-typescript-strands/README.md b/src/assets/templates/agent-typescript-strands/README.md index edeaed4ca3..cda7ede527 100644 --- a/src/assets/templates/agent-typescript-strands/README.md +++ b/src/assets/templates/agent-typescript-strands/README.md @@ -17,6 +17,11 @@ from `.env.local` under `agentcore dev`; the `openai_compatible` provider is the pointed at the base URL you gave the `agentcore create` wizard, baked into `load.ts` as `clientConfig.baseURL`. +## Memory + +The template provisions semantic and episodic memory. `memory/memory.ts` retrieves semantic +facts for the actor and episodes for the current session, with score filtering disabled by default. + ## Input Validation The generated Zod request schema keeps the prompt and actor ID typed as strings before forwarding them to Strands. diff --git a/src/assets/templates/agent-typescript-strands/memory/memory.ts b/src/assets/templates/agent-typescript-strands/memory/memory.ts index b5252932ac..abb1060a27 100644 --- a/src/assets/templates/agent-typescript-strands/memory/memory.ts +++ b/src/assets/templates/agent-typescript-strands/memory/memory.ts @@ -18,9 +18,7 @@ export function getOrCreateMemoryManager(sessionId: string, actorId: string): Me sessionId, namespaces: [ { namespace: '/users/{actorId}/facts' }, - { namespace: '/users/{actorId}/preferences' }, { namespace: '/episodes/{actorId}/{sessionId}' }, - { namespace: '/summaries/{actorId}' }, ], // readMode defaults to 'per-namespace' (one retrieve call per namespace). // Switch to 'subtree' to consolidate to a single hierarchical recall call. diff --git a/src/assets/templates/agui-python-strands/README.md b/src/assets/templates/agui-python-strands/README.md index b1662331f5..5a0918afe0 100644 --- a/src/assets/templates/agui-python-strands/README.md +++ b/src/assets/templates/agui-python-strands/README.md @@ -9,6 +9,13 @@ streams agent-to-UI events over HTTP. It exposes an `/invocations` endpoint that an AG-UI `RunAgentInput` body and streams the response back as AG-UI server-sent events, matching the AgentCore Runtime HTTP service contract. +## Memory + +The template provisions semantic and episodic memory. `memory/session.py` retrieves up to three +semantic facts for the actor and five episodes for the current session. The default +`relevance_score=0.0` selects results by `top_k` without score filtering. To opt into score +filtering, increase `relevance_score` in the retrieval configuration. + ## Adding Tools Define tools with the `@tool` decorator in `main.py` and add them to the agent's `tools` list: diff --git a/src/assets/templates/agui-python-strands/memory/session.py b/src/assets/templates/agui-python-strands/memory/session.py index 6372a2f685..cd4c56aa82 100644 --- a/src/assets/templates/agui-python-strands/memory/session.py +++ b/src/assets/templates/agui-python-strands/memory/session.py @@ -18,10 +18,8 @@ def get_memory_session_manager( session_id = session_id or uuid.uuid4().hex retrieval_config = { - f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.5), - f"/users/{actor_id}/preferences": RetrievalConfig(top_k=3, relevance_score=0.5), - f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5), - f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5), + f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.0), + f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.0), } return AgentCoreMemorySessionManager( diff --git a/src/assets/templates/export-harness-python/memory/session.py b/src/assets/templates/export-harness-python/memory/session.py index 38bcf49f93..e9764a3fcd 100644 --- a/src/assets/templates/export-harness-python/memory/session.py +++ b/src/assets/templates/export-harness-python/memory/session.py @@ -20,16 +20,16 @@ def get_memory_session_manager( {{#if memoryStrategies.length}} retrieval_config = { {{#if (includes memoryStrategies "SEMANTIC")}} - f"/users/{actor_id}/facts": RetrievalConfig(top_k={{#if memoryRetrievalTopK}}{{memoryRetrievalTopK}}{{else}}3{{/if}}, relevance_score={{#if memoryRetrievalRelevanceScore}}{{memoryRetrievalRelevanceScore}}{{else}}0.5{{/if}}), + f"/users/{actor_id}/facts": RetrievalConfig(top_k={{#if memoryRetrievalTopK}}{{memoryRetrievalTopK}}{{else}}3{{/if}}, relevance_score={{#if memoryRetrievalRelevanceScore}}{{memoryRetrievalRelevanceScore}}{{else}}0.0{{/if}}), {{/if}} {{#if (includes memoryStrategies "USER_PREFERENCE")}} - f"/users/{actor_id}/preferences": RetrievalConfig(top_k={{#if memoryRetrievalTopK}}{{memoryRetrievalTopK}}{{else}}3{{/if}}, relevance_score={{#if memoryRetrievalRelevanceScore}}{{memoryRetrievalRelevanceScore}}{{else}}0.5{{/if}}), + f"/users/{actor_id}/preferences": RetrievalConfig(top_k={{#if memoryRetrievalTopK}}{{memoryRetrievalTopK}}{{else}}3{{/if}}, relevance_score={{#if memoryRetrievalRelevanceScore}}{{memoryRetrievalRelevanceScore}}{{else}}0.0{{/if}}), {{/if}} {{#if (includes memoryStrategies "EPISODIC")}} - f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k={{#if memoryRetrievalTopK}}{{memoryRetrievalTopK}}{{else}}5{{/if}}, relevance_score={{#if memoryRetrievalRelevanceScore}}{{memoryRetrievalRelevanceScore}}{{else}}0.5{{/if}}), + f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k={{#if memoryRetrievalTopK}}{{memoryRetrievalTopK}}{{else}}5{{/if}}, relevance_score={{#if memoryRetrievalRelevanceScore}}{{memoryRetrievalRelevanceScore}}{{else}}0.0{{/if}}), {{/if}} {{#if (includes memoryStrategies "SUMMARIZATION")}} - f"/summaries/{actor_id}": RetrievalConfig(top_k={{#if memoryRetrievalTopK}}{{memoryRetrievalTopK}}{{else}}3{{/if}}, relevance_score={{#if memoryRetrievalRelevanceScore}}{{memoryRetrievalRelevanceScore}}{{else}}0.5{{/if}}), + f"/summaries/{actor_id}": RetrievalConfig(top_k={{#if memoryRetrievalTopK}}{{memoryRetrievalTopK}}{{else}}3{{/if}}, relevance_score={{#if memoryRetrievalRelevanceScore}}{{memoryRetrievalRelevanceScore}}{{else}}0.0{{/if}}), {{/if}} } {{/if}} diff --git a/src/core/project/__snapshots__/manager.test.ts.snap b/src/core/project/__snapshots__/manager.test.ts.snap index 9af00244b0..e33bd9075f 100644 --- a/src/core/project/__snapshots__/manager.test.ts.snap +++ b/src/core/project/__snapshots__/manager.test.ts.snap @@ -54,18 +54,6 @@ exports[`FsProjectManager.create snapshots the Strands project manifest and runt ], "type": "SEMANTIC", }, - { - "namespaceTemplates": [ - "/users/{actorId}/preferences", - ], - "type": "USER_PREFERENCE", - }, - { - "namespaceTemplates": [ - "/summaries/{actorId}/{sessionId}", - ], - "type": "SUMMARIZATION", - }, { "namespaceTemplates": [ "/episodes/{actorId}/{sessionId}", @@ -125,18 +113,6 @@ exports[`FsProjectManager.create snapshots the Strands TypeScript project manife ], "type": "SEMANTIC", }, - { - "namespaceTemplates": [ - "/users/{actorId}/preferences", - ], - "type": "USER_PREFERENCE", - }, - { - "namespaceTemplates": [ - "/summaries/{actorId}/{sessionId}", - ], - "type": "SUMMARIZATION", - }, { "namespaceTemplates": [ "/episodes/{actorId}/{sessionId}", @@ -197,18 +173,6 @@ exports[`FsProjectManager.create snapshots the Strands A2A project manifest and ], "type": "SEMANTIC", }, - { - "namespaceTemplates": [ - "/users/{actorId}/preferences", - ], - "type": "USER_PREFERENCE", - }, - { - "namespaceTemplates": [ - "/summaries/{actorId}/{sessionId}", - ], - "type": "SUMMARIZATION", - }, { "namespaceTemplates": [ "/episodes/{actorId}/{sessionId}", diff --git a/src/core/project/bedrockAgentImport/strandsTranslator.ts b/src/core/project/bedrockAgentImport/strandsTranslator.ts index d9acdc0fd3..abd3631c4f 100644 --- a/src/core/project/bedrockAgentImport/strandsTranslator.ts +++ b/src/core/project/bedrockAgentImport/strandsTranslator.ts @@ -248,10 +248,8 @@ function generateStrandsMemoryModule(request: BedrockAgentImportRequest): string const retrievalConfig = request.memory === "longAndShortTerm" ? ` retrieval_config = { - f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.5), - f"/users/{actor_id}/preferences": RetrievalConfig(top_k=3, relevance_score=0.5), - f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.5), - f"/summaries/{actor_id}": RetrievalConfig(top_k=3, relevance_score=0.5), + f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.0), + f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.0), } ` : " retrieval_config = {}\n"; diff --git a/src/core/project/bedrockAgentImport/translator.test.ts b/src/core/project/bedrockAgentImport/translator.test.ts index a73894dd93..ac6175d9b8 100644 --- a/src/core/project/bedrockAgentImport/translator.test.ts +++ b/src/core/project/bedrockAgentImport/translator.test.ts @@ -93,6 +93,22 @@ describe("StrandsBedrockAgentTranslator", () => { expect(plan.files["IMPORT_NOTES.md"]).toContain("Strands guardrail"); }); + test("retrieves semantic and episodic memory without a default score filter", () => { + const memory = new StrandsBedrockAgentTranslator(snapshot(), request).translate().files[ + "memory.py" + ]!; + + expect(memory).toContain( + 'f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.0)', + ); + expect(memory).toContain( + 'f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.0)', + ); + expect(memory.match(/RetrievalConfig\(/g)).toHaveLength(2); + expect(memory).not.toContain("/preferences"); + expect(memory).not.toContain("/summaries/"); + }); + test("pins a code-interpreter dependency that can coexist with strands-agents 1.x", () => { const pyproject = new StrandsBedrockAgentTranslator(snapshot(), request).translate().files[ "pyproject.toml" diff --git a/src/core/project/manager.export.test.ts b/src/core/project/manager.export.test.ts index 0c36621ba8..37995ae2c4 100644 --- a/src/core/project/manager.export.test.ts +++ b/src/core/project/manager.export.test.ts @@ -179,7 +179,14 @@ describe("FsProjectManager.exportHarness rendered tree", () => { resourceConfig: { name: "chat_history", eventExpiryDuration: 30, - strategies: [{ type: "SEMANTIC" }], + strategies: [ + { type: "SEMANTIC" }, + { + type: "EPISODIC", + namespaceTemplates: ["/episodes/{actorId}/{sessionId}"], + reflectionNamespaceTemplates: ["/episodes/{actorId}"], + }, + ], }, }), ); @@ -188,41 +195,52 @@ describe("FsProjectManager.exportHarness rendered tree", () => { const session = await Bun.file(join(result.agentPath, "memory", "session.py")).text(); expect(session).toContain('MEMORY_ID = os.getenv("MEMORY_CHAT_HISTORY_ID")'); + expect(session).toContain( + 'f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.0)', + ); + expect(session).toContain( + 'f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.0)', + ); + expect(session).not.toContain("/preferences"); + expect(session).not.toContain("/summaries/"); expect(await Bun.file(join(result.agentPath, "main.py")).text()).toContain( "from memory.session import get_memory_session_manager", ); expect(result.notes).toEqual([]); }); - test("renders memory retrieval tuning and notes messagesCount", async () => { - const { manager: subject } = manager(); - let project = await projectWithHarness(subject, { - memory: { - mode: "existing", - name: "chat_history", - messagesCount: 12, - retrievalConfig: { topK: 8, relevanceScore: 0.7 }, - }, - }); - project = await drain( - subject.addResource(project, { - resourceType: "memory", - resourceConfig: { + test.each([0, 0.7])( + "renders relevanceScore=%p and notes messagesCount", + async (relevanceScore) => { + const { manager: subject } = manager(); + let project = await projectWithHarness(subject, { + memory: { + mode: "existing", name: "chat_history", - eventExpiryDuration: 30, - strategies: [{ type: "SEMANTIC" }], + messagesCount: 12, + retrievalConfig: { topK: 8, relevanceScore }, }, - }), - ); + }); + project = await drain( + subject.addResource(project, { + resourceType: "memory", + resourceConfig: { + name: "chat_history", + eventExpiryDuration: 30, + strategies: [{ type: "SEMANTIC" }], + }, + }), + ); - const result = await drain(subject.exportHarness(project, exportInput())); + const result = await drain(subject.exportHarness(project, exportInput())); - const session = await Bun.file(join(result.agentPath, "memory", "session.py")).text(); - expect(session).toContain("RetrievalConfig(top_k=8, relevance_score=0.7)"); - expect(result.notes.map((note) => note.category)).toContain( - "Memory messagesCount is not directly portable to Strands", - ); - }); + const session = await Bun.file(join(result.agentPath, "memory", "session.py")).text(); + expect(session).toContain(`RetrievalConfig(top_k=8, relevance_score=${relevanceScore})`); + expect(result.notes.map((note) => note.category)).toContain( + "Memory messagesCount is not directly portable to Strands", + ); + }, + ); test("renders OpenAI Responses settings with compatible Strands extras", async () => { const { manager: subject } = manager(); diff --git a/src/core/project/templates/__snapshots__/harness.test.ts.snap b/src/core/project/templates/__snapshots__/harness.test.ts.snap index c9b2361caf..8c9312d33f 100644 --- a/src/core/project/templates/__snapshots__/harness.test.ts.snap +++ b/src/core/project/templates/__snapshots__/harness.test.ts.snap @@ -48,9 +48,10 @@ skills: [] # a project memory name. Use memory: { disabled: {} } to turn memory off. # https://docs.aws.amazon.com/bedrock-agentcore-control/latest/APIReference/API_HarnessMemoryConfiguration.html memory: - managedMemoryConfiguration: {} -# To tune managed memory, replace {} with strategies and/or eventExpiryDuration. -# https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory.html + managedMemoryConfiguration: + strategies: + - SEMANTIC + - EPISODIC # Context policy and limits for one invocation. truncation: diff --git a/src/core/project/templates/harness.test.ts b/src/core/project/templates/harness.test.ts index 4421a576c6..da78df7cb6 100644 --- a/src/core/project/templates/harness.test.ts +++ b/src/core/project/templates/harness.test.ts @@ -15,7 +15,7 @@ const model = { modelId: "global.anthropic.claude-sonnet-4-6", } as const; const defaultSettings = { - memory: { mode: "managed" }, + memory: { mode: "managed", strategies: ["SEMANTIC", "EPISODIC"] }, allowedTools: ["*"], skills: [], truncation: { strategy: "sliding_window" }, @@ -53,7 +53,7 @@ test("scaffolds valid YAML with inactive examples and a resolvable prompt", asyn expect(data).toEqual({ name: "assistant", model: { bedrockModelConfig: { modelId: model.modelId } }, - memory: { managedMemoryConfiguration: {} }, + memory: { managedMemoryConfiguration: { strategies: ["SEMANTIC", "EPISODIC"] } }, tools: [], allowedTools: ["*"], skills: [], @@ -69,6 +69,7 @@ test("scaffolds valid YAML with inactive examples and a resolvable prompt", asyn test.each([ { mode: "disabled" }, + { mode: "managed" }, { mode: "existing", name: "ConversationMemory", diff --git a/src/core/project/templates/harness.ts b/src/core/project/templates/harness.ts index 3abc429353..169c45af4c 100644 --- a/src/core/project/templates/harness.ts +++ b/src/core/project/templates/harness.ts @@ -11,6 +11,7 @@ import { FsTreeNode } from "./fsTree"; import { InputValidationError, ResourceNotFoundError } from "../../../errors/errors"; import type { TemplateRenderer, TemplateResolver } from "./types"; import type { AssetSource } from "../source"; +import { DEFAULT_TEMPLATE_MEMORY_STRATEGIES } from "../../../projectSchemas/memory"; const DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant"; const TEMPLATE_FIELDS = new Set([ @@ -71,7 +72,10 @@ export function getHarnessTemplateResolver( const parsed = parseHarnessSpec({ ...spec, - memory: spec.memory === undefined ? { mode: "managed" } : spec.memory, + memory: + spec.memory === undefined + ? { mode: "managed", strategies: [...DEFAULT_TEMPLATE_MEMORY_STRATEGIES] } + : spec.memory, dockerfile: spec.dockerfile ? "Dockerfile" : undefined, }); const { systemPrompt, ...settings } = parsed; diff --git a/src/core/project/templates/runtime.test.ts b/src/core/project/templates/runtime.test.ts index c131bbcfbf..cd5ca140a4 100644 --- a/src/core/project/templates/runtime.test.ts +++ b/src/core/project/templates/runtime.test.ts @@ -120,17 +120,18 @@ describe("runtime templates", () => { }); test.each<[RuntimeTemplateShortcutName, string[]]>([ - ["agent-python-strands", ["SEMANTIC", "USER_PREFERENCE", "SUMMARIZATION", "EPISODIC"]], - ["a2a-python-strands", ["SEMANTIC", "USER_PREFERENCE", "SUMMARIZATION", "EPISODIC"]], - ["agent-typescript-strands", ["SEMANTIC", "USER_PREFERENCE", "SUMMARIZATION", "EPISODIC"]], + ["agent-python-strands", ["SEMANTIC", "EPISODIC"]], + ["agent-python-strands-container", ["SEMANTIC", "EPISODIC"]], + ["a2a-python-strands", ["SEMANTIC", "EPISODIC"]], + ["agent-typescript-strands", ["SEMANTIC", "EPISODIC"]], ["agent-python-minimal", []], ["agent-python-langchain", []], ["agent-typescript-vercel", []], ["mcp-python-fastmcp", []], - ["agui-python-strands", ["SEMANTIC", "USER_PREFERENCE", "SUMMARIZATION", "EPISODIC"]], + ["agui-python-strands", ["SEMANTIC", "EPISODIC"]], ["environment-python-bma", []], ])("%s ships with its pre-configured memory", async (template, expectedStrategies) => { - const { spec } = await addTemplate("my_agent", template); + const { spec, appDir } = await addTemplate("my_agent", template); const memory = (spec.memories ?? []).find( (candidate: { name: string }) => candidate.name === "my_agentMemory", ); @@ -140,6 +141,34 @@ describe("runtime templates", () => { } expect(memory).toMatchObject({ name: "my_agentMemory", eventExpiryDuration: 30 }); expect(memory.strategies.map(({ type }: { type: string }) => type)).toEqual(expectedStrategies); + expect(memory.strategies).toEqual([ + { type: "SEMANTIC", namespaceTemplates: ["/users/{actorId}/facts"] }, + { + type: "EPISODIC", + namespaceTemplates: ["/episodes/{actorId}/{sessionId}"], + reflectionNamespaceTemplates: ["/episodes/{actorId}"], + }, + ]); + + const isTypeScript = template === "agent-typescript-strands"; + const memoryCode = await Bun.file( + join(appDir, "memory", isTypeScript ? "memory.ts" : "session.py"), + ).text(); + expect(memoryCode).not.toContain("/preferences"); + expect(memoryCode).not.toContain("/summaries/"); + if (isTypeScript) { + expect(memoryCode).toContain("{ namespace: '/users/{actorId}/facts' }"); + expect(memoryCode).toContain("{ namespace: '/episodes/{actorId}/{sessionId}' }"); + expect(memoryCode.match(/namespace:/g)).toHaveLength(2); + } else { + expect(memoryCode).toContain( + 'f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.0)', + ); + expect(memoryCode).toContain( + 'f"/episodes/{actor_id}/{session_id}": RetrievalConfig(top_k=5, relevance_score=0.0)', + ); + expect(memoryCode.match(/RetrievalConfig\(/g)).toHaveLength(2); + } }); test.each<[RuntimeTemplateShortcutName, string]>([ diff --git a/src/handlers/project/add/harness/index.test.ts b/src/handlers/project/add/harness/index.test.ts index 1e3a7cc0b2..440a6994b6 100644 --- a/src/handlers/project/add/harness/index.test.ts +++ b/src/handlers/project/add/harness/index.test.ts @@ -578,7 +578,9 @@ describe("project add harness", () => { const harnessYaml = parse(await Bun.file(join(projectRoot, "app", "x", "harness.yaml")).text()); expect(harnessYaml.systemPrompt).toBeUndefined(); - expect(harnessYaml.memory).toEqual({ managedMemoryConfiguration: {} }); + expect(harnessYaml.memory).toEqual({ + managedMemoryConfiguration: { strategies: ["SEMANTIC", "EPISODIC"] }, + }); expect(harnessYaml.tools).toEqual([]); expect(harnessYaml.skills).toEqual([]); expect(existsSync(join(projectRoot, "app", "x", "harness.json"))).toBe(false); diff --git a/src/handlers/project/add/runtime/index.test.ts b/src/handlers/project/add/runtime/index.test.ts index c67ef6f2a7..f46787bc2f 100644 --- a/src/handlers/project/add/runtime/index.test.ts +++ b/src/handlers/project/add/runtime/index.test.ts @@ -420,8 +420,6 @@ describe("project add runtime --agent-id (Bedrock Agent import)", () => { expect(spec.memories).toMatchObject([{ name: "support_proxyMemory" }]); expect(spec.memories[0].strategies.map(({ type }: { type: string }) => type)).toEqual([ "SEMANTIC", - "USER_PREFERENCE", - "SUMMARIZATION", "EPISODIC", ]); }); diff --git a/src/handlers/project/create/create.screen.test.tsx b/src/handlers/project/create/create.screen.test.tsx index 8aadbedc9f..0430ba569e 100644 --- a/src/handlers/project/create/create.screen.test.tsx +++ b/src/handlers/project/create/create.screen.test.tsx @@ -217,7 +217,9 @@ describe("project create wizard", () => { expect(harness.model).toEqual({ openAiModelConfig: { modelId: "gpt-6.1-sol", apiKeyArn }, }); - expect(harness.memory).toEqual({ managedMemoryConfiguration: {} }); + expect(harness.memory).toEqual({ + managedMemoryConfiguration: { strategies: ["SEMANTIC", "EPISODIC"] }, + }); expect(harness.systemPrompt).toBeUndefined(); r.unmount(); }, 10000); diff --git a/src/handlers/project/project.test.ts b/src/handlers/project/project.test.ts index 41ebbde2e4..0ace4da996 100644 --- a/src/handlers/project/project.test.ts +++ b/src/handlers/project/project.test.ts @@ -211,8 +211,6 @@ describe("project create", () => { expect(memory).toMatchObject({ name: "agentMemory", eventExpiryDuration: 30 }); expect(memory.strategies.map(({ type }: { type: string }) => type)).toEqual([ "SEMANTIC", - "USER_PREFERENCE", - "SUMMARIZATION", "EPISODIC", ]); expect(await Bun.file(join(projectRoot, "app", "agent", "main.py")).exists()).toBe(true); diff --git a/src/handlers/project/shortcuts.ts b/src/handlers/project/shortcuts.ts index 3d16e3c887..1594ec29e8 100644 --- a/src/handlers/project/shortcuts.ts +++ b/src/handlers/project/shortcuts.ts @@ -2,6 +2,7 @@ import z from "zod"; import { DEFAULT_EPISODIC_REFLECTION_NAMESPACE_TEMPLATES, DEFAULT_STRATEGY_NAMESPACE_TEMPLATES, + DEFAULT_TEMPLATE_MEMORY_STRATEGIES, defaultMemoryName, type Memory, } from "../../projectSchemas/memory"; @@ -15,15 +16,13 @@ export function getDefaultMemorySpec(runtimeName: string): Memory { return { name: defaultMemoryName(runtimeName), eventExpiryDuration: 30, - strategies: (["SEMANTIC", "USER_PREFERENCE", "SUMMARIZATION", "EPISODIC"] as const).map( - (type) => ({ - type, - namespaceTemplates: DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[type], - ...(type === "EPISODIC" && { - reflectionNamespaceTemplates: DEFAULT_EPISODIC_REFLECTION_NAMESPACE_TEMPLATES, - }), + strategies: DEFAULT_TEMPLATE_MEMORY_STRATEGIES.map((type) => ({ + type, + namespaceTemplates: DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[type], + ...(type === "EPISODIC" && { + reflectionNamespaceTemplates: DEFAULT_EPISODIC_REFLECTION_NAMESPACE_TEMPLATES, }), - ), + })), }; } diff --git a/src/projectSchemas/memory.ts b/src/projectSchemas/memory.ts index 738ff8c8eb..fbe2df3f8d 100644 --- a/src/projectSchemas/memory.ts +++ b/src/projectSchemas/memory.ts @@ -8,6 +8,7 @@ export const MemoryStrategyTypeSchema = z.enum([ "EPISODIC", ]); export type MemoryStrategyType = z.infer; +export const DEFAULT_TEMPLATE_MEMORY_STRATEGIES = ["SEMANTIC", "EPISODIC"] as const; export const DEFAULT_STRATEGY_NAMESPACE_TEMPLATES: Partial> = { SEMANTIC: ["/users/{actorId}/facts"], USER_PREFERENCE: ["/users/{actorId}/preferences"], From d1988665b739fa4dc947b43669417d1a2a2777b1 Mon Sep 17 00:00:00 2001 From: gitikavj Date: Tue, 3 Nov 2026 06:51:45 +0000 Subject: [PATCH 2/2] fix(memory): preserve managed harness scaffold defaults --- docs/harness-project-configuration.md | 11 +++++------ .../templates/__snapshots__/harness.test.ts.snap | 7 +++---- src/core/project/templates/harness.test.ts | 5 ++--- src/core/project/templates/harness.ts | 6 +----- src/handlers/project/add/harness/index.test.ts | 4 +--- src/handlers/project/create/create.screen.test.tsx | 4 +--- src/handlers/project/shortcuts.ts | 3 +-- src/projectSchemas/memory.ts | 1 - 8 files changed, 14 insertions(+), 27 deletions(-) diff --git a/docs/harness-project-configuration.md b/docs/harness-project-configuration.md index 4f597a0d12..477783da80 100644 --- a/docs/harness-project-configuration.md +++ b/docs/harness-project-configuration.md @@ -48,10 +48,7 @@ model: bedrockModelConfig: modelId: global.anthropic.claude-sonnet-5-5 memory: - managedMemoryConfiguration: - strategies: - - SEMANTIC - - EPISODIC + managedMemoryConfiguration: {} ``` Edit the file, then run `agentcore deploy` from the project directory to @@ -193,8 +190,8 @@ Memory persists conversation events and can extract information for later retrieval. It is separate from the conversation truncation settings that control what fits in a model request. -Newly scaffolded harnesses use managed memory with `SEMANTIC` and `EPISODIC` strategies unless -another memory configuration was supplied. Removing `memory` from an existing YAML file disables +Newly scaffolded harnesses use `memory: { managedMemoryConfiguration: {} }` unless another memory +configuration was supplied. Removing `memory` from an existing YAML file disables memory. Reading the file never adds the scaffold default. ### Managed Memory @@ -206,6 +203,8 @@ memory: managedMemoryConfiguration: strategies: - SEMANTIC + - SUMMARIZATION + - USER_PREFERENCE - EPISODIC eventExpiryDuration: 30 ``` diff --git a/src/core/project/templates/__snapshots__/harness.test.ts.snap b/src/core/project/templates/__snapshots__/harness.test.ts.snap index 8c9312d33f..c9b2361caf 100644 --- a/src/core/project/templates/__snapshots__/harness.test.ts.snap +++ b/src/core/project/templates/__snapshots__/harness.test.ts.snap @@ -48,10 +48,9 @@ skills: [] # a project memory name. Use memory: { disabled: {} } to turn memory off. # https://docs.aws.amazon.com/bedrock-agentcore-control/latest/APIReference/API_HarnessMemoryConfiguration.html memory: - managedMemoryConfiguration: - strategies: - - SEMANTIC - - EPISODIC + managedMemoryConfiguration: {} +# To tune managed memory, replace {} with strategies and/or eventExpiryDuration. +# https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory.html # Context policy and limits for one invocation. truncation: diff --git a/src/core/project/templates/harness.test.ts b/src/core/project/templates/harness.test.ts index da78df7cb6..4421a576c6 100644 --- a/src/core/project/templates/harness.test.ts +++ b/src/core/project/templates/harness.test.ts @@ -15,7 +15,7 @@ const model = { modelId: "global.anthropic.claude-sonnet-4-6", } as const; const defaultSettings = { - memory: { mode: "managed", strategies: ["SEMANTIC", "EPISODIC"] }, + memory: { mode: "managed" }, allowedTools: ["*"], skills: [], truncation: { strategy: "sliding_window" }, @@ -53,7 +53,7 @@ test("scaffolds valid YAML with inactive examples and a resolvable prompt", asyn expect(data).toEqual({ name: "assistant", model: { bedrockModelConfig: { modelId: model.modelId } }, - memory: { managedMemoryConfiguration: { strategies: ["SEMANTIC", "EPISODIC"] } }, + memory: { managedMemoryConfiguration: {} }, tools: [], allowedTools: ["*"], skills: [], @@ -69,7 +69,6 @@ test("scaffolds valid YAML with inactive examples and a resolvable prompt", asyn test.each([ { mode: "disabled" }, - { mode: "managed" }, { mode: "existing", name: "ConversationMemory", diff --git a/src/core/project/templates/harness.ts b/src/core/project/templates/harness.ts index 169c45af4c..3abc429353 100644 --- a/src/core/project/templates/harness.ts +++ b/src/core/project/templates/harness.ts @@ -11,7 +11,6 @@ import { FsTreeNode } from "./fsTree"; import { InputValidationError, ResourceNotFoundError } from "../../../errors/errors"; import type { TemplateRenderer, TemplateResolver } from "./types"; import type { AssetSource } from "../source"; -import { DEFAULT_TEMPLATE_MEMORY_STRATEGIES } from "../../../projectSchemas/memory"; const DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant"; const TEMPLATE_FIELDS = new Set([ @@ -72,10 +71,7 @@ export function getHarnessTemplateResolver( const parsed = parseHarnessSpec({ ...spec, - memory: - spec.memory === undefined - ? { mode: "managed", strategies: [...DEFAULT_TEMPLATE_MEMORY_STRATEGIES] } - : spec.memory, + memory: spec.memory === undefined ? { mode: "managed" } : spec.memory, dockerfile: spec.dockerfile ? "Dockerfile" : undefined, }); const { systemPrompt, ...settings } = parsed; diff --git a/src/handlers/project/add/harness/index.test.ts b/src/handlers/project/add/harness/index.test.ts index 440a6994b6..1e3a7cc0b2 100644 --- a/src/handlers/project/add/harness/index.test.ts +++ b/src/handlers/project/add/harness/index.test.ts @@ -578,9 +578,7 @@ describe("project add harness", () => { const harnessYaml = parse(await Bun.file(join(projectRoot, "app", "x", "harness.yaml")).text()); expect(harnessYaml.systemPrompt).toBeUndefined(); - expect(harnessYaml.memory).toEqual({ - managedMemoryConfiguration: { strategies: ["SEMANTIC", "EPISODIC"] }, - }); + expect(harnessYaml.memory).toEqual({ managedMemoryConfiguration: {} }); expect(harnessYaml.tools).toEqual([]); expect(harnessYaml.skills).toEqual([]); expect(existsSync(join(projectRoot, "app", "x", "harness.json"))).toBe(false); diff --git a/src/handlers/project/create/create.screen.test.tsx b/src/handlers/project/create/create.screen.test.tsx index 0430ba569e..8aadbedc9f 100644 --- a/src/handlers/project/create/create.screen.test.tsx +++ b/src/handlers/project/create/create.screen.test.tsx @@ -217,9 +217,7 @@ describe("project create wizard", () => { expect(harness.model).toEqual({ openAiModelConfig: { modelId: "gpt-6.1-sol", apiKeyArn }, }); - expect(harness.memory).toEqual({ - managedMemoryConfiguration: { strategies: ["SEMANTIC", "EPISODIC"] }, - }); + expect(harness.memory).toEqual({ managedMemoryConfiguration: {} }); expect(harness.systemPrompt).toBeUndefined(); r.unmount(); }, 10000); diff --git a/src/handlers/project/shortcuts.ts b/src/handlers/project/shortcuts.ts index 1594ec29e8..9fb198b07a 100644 --- a/src/handlers/project/shortcuts.ts +++ b/src/handlers/project/shortcuts.ts @@ -2,7 +2,6 @@ import z from "zod"; import { DEFAULT_EPISODIC_REFLECTION_NAMESPACE_TEMPLATES, DEFAULT_STRATEGY_NAMESPACE_TEMPLATES, - DEFAULT_TEMPLATE_MEMORY_STRATEGIES, defaultMemoryName, type Memory, } from "../../projectSchemas/memory"; @@ -16,7 +15,7 @@ export function getDefaultMemorySpec(runtimeName: string): Memory { return { name: defaultMemoryName(runtimeName), eventExpiryDuration: 30, - strategies: DEFAULT_TEMPLATE_MEMORY_STRATEGIES.map((type) => ({ + strategies: (["SEMANTIC", "EPISODIC"] as const).map((type) => ({ type, namespaceTemplates: DEFAULT_STRATEGY_NAMESPACE_TEMPLATES[type], ...(type === "EPISODIC" && { diff --git a/src/projectSchemas/memory.ts b/src/projectSchemas/memory.ts index fbe2df3f8d..738ff8c8eb 100644 --- a/src/projectSchemas/memory.ts +++ b/src/projectSchemas/memory.ts @@ -8,7 +8,6 @@ export const MemoryStrategyTypeSchema = z.enum([ "EPISODIC", ]); export type MemoryStrategyType = z.infer; -export const DEFAULT_TEMPLATE_MEMORY_STRATEGIES = ["SEMANTIC", "EPISODIC"] as const; export const DEFAULT_STRATEGY_NAMESPACE_TEMPLATES: Partial> = { SEMANTIC: ["/users/{actorId}/facts"], USER_PREFERENCE: ["/users/{actorId}/preferences"],