-
Notifications
You must be signed in to change notification settings - Fork 89
Description
Description
MemoryClient.list_events couples the caller's max_results with the ListEvents API's maxResults page size. When read_session calls list_events(max_results=1) with a metadata filter, the API scans 1 event per round-trip. Since the ListEvents filter is applied post-scan (not pre-scan), each non-matching event costs a full API call. This results in O(N) ListEvents calls per AgentCoreMemorySessionManager initialization — one call per event in the session.
Steps to Reproduce
- Create a session using
AgentCoreMemorySessionManager(creates the SESSION metadata event — becomes the oldest event) - Have ~100+ conversational exchanges (building up events in the session)
- On a subsequent invocation,
AgentCoreMemorySessionManager.__init__()calls:read_session→list_events(max_results=1, event_metadata=[stateType=SESSION])— scans ALL eventsread_agent→list_events(max_results=1, event_metadata=[stateType=AGENT])— finds it quickly (AGENT event is near the top)
- Observe
read_sessionmaking ~N ListEvents API calls (one per event) before finding the SESSION metadata event
The Bug
In bedrock_agentcore/memory/client.py, MemoryClient.list_events:
"maxResults": min(100, max_results - len(all_events)), # sends 1 when max_results=1When max_results=1 and no events found yet, this sends maxResults=1 to the API. The API scans 1 event, applies the metadata filter, returns 0 matches + nextToken, and the loop repeats — scanning the entire session one event at a time.
The Fix
# Before:
"maxResults": min(100, max_results - len(all_events)),
# After:
"maxResults": 100,The SDK already truncates with return all_events[:max_results], so max_results continues to control how many results the caller receives. This just stops it from also controlling the API scan window.
Trade-offs
We used 100 because it's the maximum value the ListEvents API accepts for maxResults (per the API docs), minimising the number of round-trips needed to scan through events. The API default when maxResults is omitted is 20.
100 may not be the perfect number for all cases. Larger page sizes mean each individual API call takes longer (~0.4s for maxResults=100 vs ~0.05s for maxResults=1) because the API scans more events per page. For small sessions (e.g., 10 events), the improvement is marginal: 10 × 50ms = 500ms vs 1 × 400ms = 400ms. The benefit grows significantly with session size — for 200 events: 200 × 50ms = 10s vs 2 × 400ms = 0.8s. The per-call overhead is far outweighed by eliminating round-trips at scale.
An alternative would be to use the API's default page size (20) as a compromise, or to introduce a separate page_size parameter so callers can tune it independently of max_results.
Mitigation Note
This fix reduces read_session from N calls to ceil(N/100) calls, but it is a mitigation rather than a complete solution. The SESSION event is always on the last page due to reverse-chronological ordering, so read_session still requires a full scan regardless of page size. A complete fix would also require API-level changes (e.g., a sortOrder parameter on ListEvents, or making the metadata filter pre-scan rather than post-scan).
Before / After Evidence
Before — default SDK behaviour (session with ~100 events)
X-Ray trace: 110 ListEvents calls, ~5.5s overhead before the agent starts:
POST /invocations 14.30s
Bedrock AgentCore.ListEvents 0.10s
Bedrock AgentCore.ListEvents 0.05s
Bedrock AgentCore.ListEvents 0.08s
... (107 more ListEvents calls, ~0.04-0.05s each) ...
Bedrock AgentCore.ListEvents 0.34s
Bedrock AgentCore.ListEvents 0.07s
invoke_agent Strands Agents 7.27s
Bedrock AgentCore.CreateEvent 0.13s
Application log at call 70 of ~98 — still 0 events found:
[ListEvents #70] 43.6ms - returned 0 events, has_more=True,
has_filter=True, max_results=1,
running_total={calls=70, time=3445ms, events=0}
After — monkey-patched to maxResults=100 (137 events)
X-Ray trace: 5 ListEvents calls, ~0.84s total:
POST /invocations 10.56s
Bedrock AgentCore.ListEvents 0.08s
Bedrock AgentCore.ListEvents 0.07s
Bedrock AgentCore.ListEvents 0.20s
Bedrock AgentCore.ListEvents 0.36s
Bedrock AgentCore.ListEvents 0.13s
invoke_agent Strands Agents 8.39s
Bedrock AgentCore.CreateEvent
Application log — read_session found the SESSION event in 2 pages / 147ms:
[ListEvents #2] 75.2ms - returned 1 events, has_more=False,
max_results=100,
filter={'eventMetadata': [{'left': {'metadataKey': 'stateType'},
'operator': 'EQUALS_TO',
'right': {'metadataValue': {'stringValue': 'SESSION'}}}]},
running_total={calls=2, time=147ms, events=1}
Total invocation: 14.3s → 10.6s. read_session: ~100 calls / ~5s → 2 calls / 147ms.
Impact
| Session Events | ListEvents calls (before) | ListEvents calls (after) |
|---|---|---|
| 50 | ~50 | 1 |
| 100 | ~100 | 1 |
| 200 | ~200 | 2 |
| 500 | ~500 | 5 |
This affects every invocation on every existing session using AgentCoreMemorySessionManager.
Environment
bedrock-agentcore==1.4.0(also confirmed on1.3.0)- Python 3.13,
strands-agentsSDK AgentCoreMemorySessionManagerwith STM- Sessions with 50-500+ events
Additional Context
- The ListEvents API returns events in reverse-chronological order. This is not documented in the API reference and there is no
sortOrderparameter to control it. - The SESSION metadata event is created once at session start and never updated — it is always the oldest event, always the last to be scanned.
- The AGENT metadata event is re-created (not updated in-place) on each
update_agentcall. This means the latest AGENT event is typically the newest or near-newest event in the session, which is whyread_agentcompletes in 1 call with the current reverse-chronological ordering. If the API ordering were reversed to chronological,read_agentwould degrade — though not as severely asread_sessiondoes today, since the AGENT event is near the end of the timeline rather than being the absolute last event. Either way, the page size fix matters regardless of ordering — it's the coupling ofmax_resultstomaxResultsthat's the core SDK issue. - The ListEvents
filterparameter is applied post-scan:maxResultscontrols how many events are scanned per page, and the filter only removes non-matching events from the response. It does not narrow the scan window. This was confirmed by production logs showingmaxResults=100returning 0 matching events on the first page with anextToken, then finding the SESSION event on the second page.
The underlying API-level issues (reverse-chronological ordering with no sortOrder parameter, and post-scan filtering behaviour) have been raised separately through AWS support.
References
- Buggy line:
bedrock_agentcore/memory/client.py→MemoryClient.list_events - Callers:
bedrock_agentcore/memory/integrations/strands/session_manager.py→read_session,read_agent - ListEvents API reference