Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/assets/templates/a2a-python-strands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 2 additions & 4 deletions src/assets/templates/a2a-python-strands/memory/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
7 changes: 7 additions & 0 deletions src/assets/templates/agent-python-strands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 2 additions & 4 deletions src/assets/templates/agent-python-strands/memory/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
5 changes: 5 additions & 0 deletions src/assets/templates/agent-typescript-strands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions src/assets/templates/agui-python-strands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 2 additions & 4 deletions src/assets/templates/agui-python-strands/memory/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 4 additions & 4 deletions src/assets/templates/export-harness-python/memory/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down
36 changes: 0 additions & 36 deletions src/core/project/__snapshots__/manager.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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}",
Expand Down Expand Up @@ -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}",
Expand Down Expand Up @@ -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}",
Expand Down
6 changes: 2 additions & 4 deletions src/core/project/bedrockAgentImport/strandsTranslator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
16 changes: 16 additions & 0 deletions src/core/project/bedrockAgentImport/translator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
70 changes: 44 additions & 26 deletions src/core/project/manager.export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}"],
},
],
},
}),
);
Expand All @@ -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();
Expand Down
39 changes: 34 additions & 5 deletions src/core/project/templates/runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
);
Expand All @@ -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]>([
Expand Down
2 changes: 0 additions & 2 deletions src/handlers/project/add/runtime/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]);
});
Expand Down
2 changes: 0 additions & 2 deletions src/handlers/project/project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading