-
Notifications
You must be signed in to change notification settings - Fork 80
Description
Problem Statement
AgentCoreMemorySessionManager saves each message with 2 separate API calls: one for the message, one for agent state. These capture the same point-in-time snapshot and could be batched into 1 call, but the current actorId design prevents this.
Simple conversation (5 API calls):
User sends "Hello"
→ MessageAddedEvent fires
→ append_message() → saves message ← API call #1
→ sync_agent() → saves agent state ← API call #2
Agent responds "Hi there!"
→ MessageAddedEvent fires
→ append_message() → saves message ← API call #3
→ sync_agent() → saves agent state ← API call #4
→ AfterInvocationEvent fires
→ sync_agent() → saves agent state ← API call #5
With tool usage (9 API calls):
User asks "What time is it?"
→ MessageAddedEvent fires
→ append_message() → saves message ← API call #1
→ sync_agent() → saves agent state ← API call #2
Agent calls get_time tool
→ MessageAddedEvent fires (tool_use)
→ append_message() → saves message ← API call #3
→ sync_agent() → saves agent state ← API call #4
Tool returns result
→ MessageAddedEvent fires (tool_result)
→ append_message() → saves message ← API call #5
→ sync_agent() → saves agent state ← API call #6
Agent responds "It's 3:00 PM"
→ MessageAddedEvent fires
→ append_message() → saves message ← API call #7
→ sync_agent() → saves agent state ← API call #8
→ AfterInvocationEvent fires
→ sync_agent() → saves agent state ← API call #9
Each additional tool call adds 4 more API calls (tool_use message + state, tool_result message + state).
Why this matters:
Each API call adds ~100-300ms of network latency:
| Scenario | API Calls | Unnecessary Latency |
|---|---|---|
| Simple conversation | 5 | ~800ms |
| 1 tool call | 9 | ~1.5s |
| 2 tool calls | 13 | ~2s |
Why batching isn't possible today:
The create_event() API can batch multiple payloads, but only if they share the same actorId. The current implementation uses different actorIds for different data types:
create_event(actorId="session_abc123", ...) # Session metadata
create_event(actorId="agent_strands", ...) # Agent state
create_event(actorId="user_123", ...) # MessagesFrom my understanding, AgentCore Memory is designed around (actorId, sessionId) pairs, where actorId represents a user and sessionId represents a conversation. Storing different parts of the same conversation under different actorIds breaks this model.
Proposed Solution
1. Use a single actorId for all conversation data
Store everything under config.actor_id (the user ID) and distinguish data types using payload markers:
create_event(
actorId=config.actor_id, # Always the user ID
sessionId=session_id,
payload=[
{"_type": "message", "role": "user", "content": [...]},
{"_type": "agent_state", "_agent_id": "strands-agent", "state": {...}}
]
)Key difference: save_message_with_state() batches message + agent state into a single API call, using unified actorId with _type markers to distinguish payload types.
Simple conversation (3 API calls):
User sends "Hello"
→ MessageAddedEvent fires
→ save_message_with_state() → saves message + agent state ← API call #1
Agent responds "Hi there!"
→ MessageAddedEvent fires
→ save_message_with_state() → saves message + agent state ← API call #2
→ AfterInvocationEvent fires
→ _sync_agent_state() → saves agent state if changed ← API call #3
With tool usage (5 API calls):
User asks "What time is it?"
→ MessageAddedEvent fires
→ save_message_with_state() → saves message + agent state ← API call #1
Agent calls get_time tool
→ MessageAddedEvent fires (tool_use)
→ save_message_with_state() → saves message + agent state ← API call #2
Tool returns result
→ MessageAddedEvent fires (tool_result)
→ save_message_with_state() → saves message + agent state ← API call #3
Agent responds "It's 3:00 PM"
→ MessageAddedEvent fires
→ save_message_with_state() → saves message + agent state ← API call #4
→ AfterInvocationEvent fires
→ _sync_agent_state() → saves agent state if changed ← API call #5
2. Merge redundant callbacks
# Current: 2 callbacks = 2 API calls per message
registry.add_callback(MessageAddedEvent, lambda e: self.append_message(...))
registry.add_callback(MessageAddedEvent, lambda e: self.sync_agent(...))
# Proposed: 1 callback = 1 API call per message
registry.add_callback(MessageAddedEvent, lambda e: self.save_message_with_state(...))3. Backward compatibility
AgentCoreMemorySessionManager(
config=config,
storage_version="v1", # Default: current behavior
# storage_version="v2", # New: unified actorId, batched calls
)(Optional) Support dual-read (try v2 first, fall back to v1) for gradual migration.
Additional context
Expected results:
| Scenario | Before | After | Reduction |
|---|---|---|---|
| Simple conversation | 5 calls | 2 calls | 60% |
| 1 tool call | 9 calls | 4 calls | 56% |
Compatibility:
- Multi-agent support preserved via
_agent_idfield in payload - Long-term memory strategies (Summary, Semantic, etc.) remain compatible since messages already use
config.actor_id - (Optional) Dual-read pattern ensures existing sessions continue to work