From 29cecd88eac7952f7a8f9c058f98085f9b54f935 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Fri, 24 Apr 2026 14:50:27 -0400 Subject: [PATCH] fix(invoke): auto-generate session ID for bearer-token invocations Closes #840 When invoking an agent with a bearer token (OAuth/CUSTOM_JWT) and no session ID, `AgentCoreMemoryConfig` raised a Pydantic validation error because `session_id=None` is rejected. Unlike SigV4 callers, bearer-token callers do not get a server-side auto-generated runtime session ID. Two-layer fix: 1. CLI synthesizes a UUID in `invoke` action when `--bearer-token` is set and `--session-id` is missing, using the existing `generateSessionId` helper. Covers both explicit `--bearer-token` and the CUSTOM_JWT auto-fetch path. 2. Strands memory session templates (http, agui, a2a) synthesize a UUID when `session_id` is falsy before constructing AgentCoreMemoryConfig. Protects direct runtime callers (curl, custom apps) who forget the `X-Amzn-Bedrock-AgentCore-Runtime-Session-Id` header. Snapshot tests updated. --- .../assets.snapshot.test.ts.snap | 21 ++++++++++++++++--- .../strands/capabilities/memory/session.py | 7 ++++++- .../strands/capabilities/memory/session.py | 7 ++++++- .../strands/capabilities/memory/session.py | 7 ++++++- src/cli/commands/invoke/action.ts | 9 ++++++++ 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index a1274e29..8fa17f31 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -1889,6 +1889,7 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/a2 exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/capabilities/memory/session.py should match snapshot 1`] = ` "import os +import uuid from typing import Optional from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig{{#if memoryProviders.[0].strategies.length}}, RetrievalConfig{{/if}} @@ -1897,10 +1898,14 @@ from bedrock_agentcore.memory.integrations.strands.session_manager import AgentC MEMORY_ID = os.getenv("{{memoryProviders.[0].envVarName}}") REGION = os.getenv("AWS_REGION") -def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[AgentCoreMemorySessionManager]: +def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Optional[AgentCoreMemorySessionManager]: if not MEMORY_ID: return None + # AgentCoreMemoryConfig rejects None; OAuth/CUSTOM_JWT callers can reach us + # without a runtime session header, so synthesize one when absent. + session_id = session_id or uuid.uuid4().hex + {{#if memoryProviders.[0].strategies.length}} retrieval_config = { {{#if (includes memoryProviders.[0].strategies "SEMANTIC")}} @@ -2724,6 +2729,7 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/ag exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/strands/capabilities/memory/session.py should match snapshot 1`] = ` "import os +import uuid from typing import Optional from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig{{#if memoryProviders.[0].strategies.length}}, RetrievalConfig{{/if}} @@ -2732,10 +2738,14 @@ from bedrock_agentcore.memory.integrations.strands.session_manager import AgentC MEMORY_ID = os.getenv("{{memoryProviders.[0].envVarName}}") REGION = os.getenv("AWS_REGION") -def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[AgentCoreMemorySessionManager]: +def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Optional[AgentCoreMemorySessionManager]: if not MEMORY_ID: return None + # AgentCoreMemoryConfig rejects None; OAuth/CUSTOM_JWT callers can reach us + # without a runtime session header, so synthesize one when absent. + session_id = session_id or uuid.uuid4().hex + {{#if memoryProviders.[0].strategies.length}} retrieval_config = { {{#if (includes memoryProviders.[0].strategies "SEMANTIC")}} @@ -4993,6 +5003,7 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/ht exports[`Assets Directory Snapshots > Python framework assets > python/python/http/strands/capabilities/memory/session.py should match snapshot 1`] = ` "import os +import uuid from typing import Optional from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig{{#if memoryProviders.[0].strategies.length}}, RetrievalConfig{{/if}} @@ -5001,10 +5012,14 @@ from bedrock_agentcore.memory.integrations.strands.session_manager import AgentC MEMORY_ID = os.getenv("{{memoryProviders.[0].envVarName}}") REGION = os.getenv("AWS_REGION") -def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[AgentCoreMemorySessionManager]: +def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Optional[AgentCoreMemorySessionManager]: if not MEMORY_ID: return None + # AgentCoreMemoryConfig rejects None; OAuth/CUSTOM_JWT callers can reach us + # without a runtime session header, so synthesize one when absent. + session_id = session_id or uuid.uuid4().hex + {{#if memoryProviders.[0].strategies.length}} retrieval_config = { {{#if (includes memoryProviders.[0].strategies "SEMANTIC")}} diff --git a/src/assets/python/a2a/strands/capabilities/memory/session.py b/src/assets/python/a2a/strands/capabilities/memory/session.py index 9661243b..46883bf5 100644 --- a/src/assets/python/a2a/strands/capabilities/memory/session.py +++ b/src/assets/python/a2a/strands/capabilities/memory/session.py @@ -1,4 +1,5 @@ import os +import uuid from typing import Optional from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig{{#if memoryProviders.[0].strategies.length}}, RetrievalConfig{{/if}} @@ -7,10 +8,14 @@ MEMORY_ID = os.getenv("{{memoryProviders.[0].envVarName}}") REGION = os.getenv("AWS_REGION") -def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[AgentCoreMemorySessionManager]: +def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Optional[AgentCoreMemorySessionManager]: if not MEMORY_ID: return None + # AgentCoreMemoryConfig rejects None; OAuth/CUSTOM_JWT callers can reach us + # without a runtime session header, so synthesize one when absent. + session_id = session_id or uuid.uuid4().hex + {{#if memoryProviders.[0].strategies.length}} retrieval_config = { {{#if (includes memoryProviders.[0].strategies "SEMANTIC")}} diff --git a/src/assets/python/agui/strands/capabilities/memory/session.py b/src/assets/python/agui/strands/capabilities/memory/session.py index 9661243b..46883bf5 100644 --- a/src/assets/python/agui/strands/capabilities/memory/session.py +++ b/src/assets/python/agui/strands/capabilities/memory/session.py @@ -1,4 +1,5 @@ import os +import uuid from typing import Optional from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig{{#if memoryProviders.[0].strategies.length}}, RetrievalConfig{{/if}} @@ -7,10 +8,14 @@ MEMORY_ID = os.getenv("{{memoryProviders.[0].envVarName}}") REGION = os.getenv("AWS_REGION") -def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[AgentCoreMemorySessionManager]: +def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Optional[AgentCoreMemorySessionManager]: if not MEMORY_ID: return None + # AgentCoreMemoryConfig rejects None; OAuth/CUSTOM_JWT callers can reach us + # without a runtime session header, so synthesize one when absent. + session_id = session_id or uuid.uuid4().hex + {{#if memoryProviders.[0].strategies.length}} retrieval_config = { {{#if (includes memoryProviders.[0].strategies "SEMANTIC")}} diff --git a/src/assets/python/http/strands/capabilities/memory/session.py b/src/assets/python/http/strands/capabilities/memory/session.py index dc0cb7bf..159f82d1 100644 --- a/src/assets/python/http/strands/capabilities/memory/session.py +++ b/src/assets/python/http/strands/capabilities/memory/session.py @@ -1,4 +1,5 @@ import os +import uuid from typing import Optional from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig{{#if memoryProviders.[0].strategies.length}}, RetrievalConfig{{/if}} @@ -7,10 +8,14 @@ MEMORY_ID = os.getenv("{{memoryProviders.[0].envVarName}}") REGION = os.getenv("AWS_REGION") -def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[AgentCoreMemorySessionManager]: +def get_memory_session_manager(session_id: Optional[str], actor_id: str) -> Optional[AgentCoreMemorySessionManager]: if not MEMORY_ID: return None + # AgentCoreMemoryConfig rejects None; OAuth/CUSTOM_JWT callers can reach us + # without a runtime session header, so synthesize one when absent. + session_id = session_id or uuid.uuid4().hex + {{#if memoryProviders.[0].strategies.length}} retrieval_config = { {{#if (includes memoryProviders.[0].strategies "SEMANTIC")}} diff --git a/src/cli/commands/invoke/action.ts b/src/cli/commands/invoke/action.ts index 41b19c0f..f0025570 100644 --- a/src/cli/commands/invoke/action.ts +++ b/src/cli/commands/invoke/action.ts @@ -14,6 +14,7 @@ import { import { InvokeLogger } from '../../logging'; import { formatMcpToolList } from '../../operations/dev/utils'; import { canFetchRuntimeToken, fetchRuntimeToken } from '../../operations/fetch-access'; +import { generateSessionId } from '../../operations/session'; import type { InvokeOptions, InvokeResult } from './types'; export interface InvokeContext { @@ -114,6 +115,14 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption } } + // When invoking with a bearer token (OAuth/CUSTOM_JWT), AgentCore does not + // auto-generate a runtime session ID the way it does for SigV4 callers. Templates + // that wire up AgentCoreMemorySessionManager require a non-null session_id, so + // generate one here if the caller didn't pass --session-id. + if (options.bearerToken && !options.sessionId) { + options = { ...options, sessionId: generateSessionId() }; + } + // Exec mode: run shell command in runtime container if (options.exec) { const logger = new InvokeLogger({