Skip to content

Conversation

@kevmyung
Copy link
Contributor

@kevmyung kevmyung commented Jan 23, 2026

Closes #222

Summary

  • Optimize AgentCore Memory API calls by batching message and agent state into single requests
  • Add state hash tracking to skip redundant sync calls
  • Implement dual-read approach for backward compatibility with existing data

API Call Reduction

Scenario Before After Reduction
Simple message (new session) 5+ calls 2 calls 60%+
With 2 tool calls 9+ calls 4 calls 55%+

Implementation Details

Batched Storage

  • Messages and agent state are saved in a single API call via _save_message_with_state()
  • Payload uses _type markers: "message" and "agent_state"

State Hash Tracking

  • _compute_state_hash() computes hash excluding timestamps (created_at, updated_at)
  • AfterInvocationEvent sync is skipped when state is unchanged

Dual-Read for Backward Compatibility

  • read_agent() tries new format first (unified actorId with type markers)
  • Falls back to legacy format (agent_{id} actorId) for existing data
  • Events are processed in reverse order to get latest state

Files Changed

  • session_manager.py: Batched storage, hash tracking, dual-read
  • bedrock_converter.py: Parse new payload format

Test Plan

  • Unit tests for batched message + state (13 TestOptimizedStorage tests)
  • Unit tests for hash computation (excludes timestamps, detects state changes)
  • Unit tests for dual-read with legacy fallback
  • Unit tests for multi-turn and multi-agent scenarios
  • Integration tests with API call counting
  • Multi-turn context preservation test
  • All 69 tests pass

Breaking Changes

None - existing data is read via legacy fallback, new data uses optimized format.

Add opt-in storage_version="v2" parameter to AgentCoreMemorySessionManager
that enables batched API calls by using unified actorId.

Changes:
- Add storage_version parameter ("v1" default, "v2" opt-in)
- v2 batches message + agent state in single API call
@kevmyung kevmyung requested a review from a team January 23, 2026 16:45
@kevmyung kevmyung force-pushed the feature/reduce-redundant-api-calls branch from e730d72 to 46ce469 Compare January 23, 2026 16:49
MessageAddedEvent, lambda event: self.save_message_with_state(event.message, event.agent)
)
registry.add_callback(AfterInvocationEvent, lambda event: self._sync_agent_state(event.agent))
registry.add_callback(MessageAddedEvent, lambda event: self.retrieve_customer_context(event))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: This v2 branch is missing the multi-agent hooks that the parent class registers:

  • MultiAgentInitializedEventinitialize_multi_agent
  • AfterNodeCallEventsync_multi_agent
  • AfterMultiAgentInvocationEventsync_multi_agent

Multi-agent scenarios will silently fail to persist state with v2. Consider either:

  1. Adding these hooks explicitly here, or
  2. Calling RepositorySessionManager.register_hooks() first, then overriding specific callbacks

Suggested fix:

# Add after line 735:
registry.add_callback(MultiAgentInitializedEvent, lambda event: self.initialize_multi_agent(event.source))
registry.add_callback(AfterNodeCallEvent, lambda event: self.sync_multi_agent(event.source))
registry.add_callback(AfterMultiAgentInvocationEvent, lambda event: self.sync_multi_agent(event.source))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added multi-agent hooks from strands.experimental.hooks.multiagent

"Synced agent state: event=%s, agent=%s", event.get("event", {}).get("eventId"), agent.agent_id
)
except Exception as e:
logger.error("Failed to sync agent state: %s", e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small: This silently swallows the exception, but save_message_with_state (line 243) raises SessionException on failure. Should this also raise to maintain consistent error handling? Silent failures here could lead to undetected data loss.

Suggested fix:

except Exception as e:
   logger.error("Failed to sync agent state: %s", e)
   raise SessionException(f"Failed to sync agent state: {e}") from e

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed - raises SessionException on failure

agent_data = json.loads(events[0].get("payload", {})[0].get("blob"))
return SessionAgent.from_dict(agent_data)
try:
if self.storage_version == "v2":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: V2 only queries config.actor_id, but V1 stored agent state under agent_{id}. If a user switches from v1 to v2 on an existing session, their agent state becomes inaccessible (appears as a new agent, losing previous state).

This silently loses access to existing data when upgrading.

Suggested fix - add fallback to v1 location:

# After the v2 loop finds nothing, before returning None:
# Fallback: check v1 location for migration support
events = self.memory_client.list_events(
   memory_id=self.config.memory_id,
   actor_id=self._get_full_agent_id(agent_id),
   session_id=session_id,
   max_results=1,
)
if events:
   agent_data = json.loads(events[0].get("payload", {})[0].get("blob"))
   return SessionAgent.from_dict(agent_data)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed - dual-read with legacy fallback

logger.error("Failed to save message with state: %s", e)
raise SessionException(f"Failed to save message with state: {e}") from e

def _sync_agent_state(self, agent: "Agent") -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optimization: I know you stated one of the main purposes of the PR is to reduce redundant API calls, this isn't a big issue but I believe there is a chance for more optimization here. Agent state is saved twice at the end of each invocation:

  1. With the final message via save_message_with_state (line 732)
  2. Again here via _sync_agent_state

This adds an extra API call per invocation. The final sync is meant to capture conversation_manager_state updates that happen after the last message, but in most cases the state hasn't changed.

Suggested optimization: Track whether state changed since last save, and skip the final sync if unchanged:

# In save_message_with_state, store a hash/snapshot:
self._last_synced_state = hash(json.dumps(session_agent.to_dict(), sort_keys=True))

# In syncagent_state, check before saving:
def syncagent_state(self, agent: "Agent") -> None:
   current_state = hash(json.dumps(SessionAgent.from_agent(agent).to_dict(), sort_keys=True))
   if current_state == getattr(self, '_last_synced_state', None):
       return  # No change, skip sync
   # ... rest of method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed - skips sync when state unchanged

@kevmyung kevmyung changed the title feat(memory): Add storage_version parameter for API call optimization feat(memory): Optimize API calls with batched storage for better latency/cost Jan 23, 2026
@kevmyung kevmyung force-pushed the feature/reduce-redundant-api-calls branch from 5f872f9 to ba2fab9 Compare January 23, 2026 21:16
@codecov-commenter
Copy link

codecov-commenter commented Jan 23, 2026

Codecov Report

❌ Patch coverage is 77.65957% with 21 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@52bc194). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...ore/memory/integrations/strands/session_manager.py 77.50% 16 Missing and 2 partials ⚠️
...e/memory/integrations/strands/bedrock_converter.py 78.57% 0 Missing and 3 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #223   +/-   ##
=======================================
  Coverage        ?   90.25%           
=======================================
  Files           ?       35           
  Lines           ?     3488           
  Branches        ?      518           
=======================================
  Hits            ?     3148           
  Misses          ?      190           
  Partials        ?      150           
Flag Coverage Δ
unittests 90.25% <77.65%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@kevmyung kevmyung force-pushed the feature/reduce-redundant-api-calls branch from ba2fab9 to 4ff0e93 Compare January 23, 2026 21:18
@kevmyung kevmyung deployed to manual-approval January 23, 2026 21:18 — with GitHub Actions Active
Copy link
Contributor

@aidandaly24 aidandaly24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me -- approved

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Eliminate Redundant API Calls in AgentCoreMemorySessionManager

3 participants