Skip to content

Conversation

@yaron2
Copy link
Member

@yaron2 yaron2 commented Dec 19, 2025

Adds the ability to store Strands Agents session state with Dapr.

Signed-off-by: yaron2 <schneider.yaron@live.com>
@yaron2 yaron2 requested review from a team as code owners December 19, 2025 20:33
Signed-off-by: yaron2 <schneider.yaron@live.com>
@codecov
Copy link

codecov bot commented Dec 19, 2025

Codecov Report

❌ Patch coverage is 85.23985% with 40 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.97%. Comparing base (bffb749) to head (a6c4de5).
⚠️ Report is 56 commits behind head on main.

Files with missing lines Patch % Lines
...t-strands/dapr/ext/strands/dapr_session_manager.py 79.47% 39 Missing ⚠️
ext/dapr-ext-strands/tests/test_session_manager.py 98.73% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #872      +/-   ##
==========================================
+ Coverage   86.63%   86.97%   +0.34%     
==========================================
  Files          84      103      +19     
  Lines        4473     6900    +2427     
==========================================
+ Hits         3875     6001    +2126     
- Misses        598      899     +301     

☔ 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.

dapr_client: DaprClient,
ttl: Optional[int] = None,
consistency: ConsistencyLevel = DAPR_CONSISTENCY_EVENTUAL,
**kwargs: Any,
Copy link
Member

Choose a reason for hiding this comment

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

I'd remove this to avoid confusion, it's not being used

Copy link
Member Author

Choose a reason for hiding this comment

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

Agree, removed

Comment on lines 242 to 245
def _get_manifest_key(self, session_id: str) -> str:
"""Get session manifest key (tracks agent_ids for deletion)."""
session_id = _identifier.validate(session_id, _identifier.Identifier.SESSION)
return f'{session_id}:manifest'
Copy link
Member

Choose a reason for hiding this comment

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

Move this where the rest of the _key methods are please

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Comment on lines +260 to +270
session_key = self._get_session_key(session.session_id)

# Check if session already exists
existing = self.read_session(session.session_id)
if existing is not None:
raise SessionException(f'Session {session.session_id} already exists')

# Write session data
session_dict = session.to_dict()
self._write_state(session_key, session_dict)
return session
Copy link
Member

Choose a reason for hiding this comment

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

Would this require some sort of locking mechanim? If we get two concurrent calls to create_session, both might end up writing the state and both will use the session but shouldn't.

Copy link
Member Author

Choose a reason for hiding this comment

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

All the existing implementations for Strands session managers (both in-tree and community contributed) assume the manager is not thread safe and do not coordinate locking. This is most likely because how Strands agents encapsulate sessions lifecycles and keeping access sequential

Comment on lines +202 to +222
def _write_state(self, key: str, data: Dict[str, Any]) -> None:
"""Write JSON state to Dapr.

Args:
key: State store key.
data: Dictionary to serialize and store.

Raises:
SessionException: If write fails.
"""
try:
content = json.dumps(data, ensure_ascii=False)
self._dapr_client.save_state(
store_name=self._state_store_name,
key=key,
value=content,
state_metadata=self._get_write_metadata(),
options=self._get_state_options(),
)
except Exception as e:
raise SessionException(f'Failed to write state key {key}: {e}') from e
Copy link
Member

Choose a reason for hiding this comment

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

Do you think it'd make sense to make use of etags to make sure we don't overwrite states?

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe this would be tied to the locking question. I audited how the Strands built-in S3 file manager works, and it also does not use etags/record versioning even though it supports it. Again, I suspect this is because of the way Strands handles the access and lifecycle of a session manager in a way that makes this a non-issue.

Raises:
SessionException: If creation fails.
"""
messages_key = self._get_messages_key(session_id, agent_id)
Copy link
Member

Choose a reason for hiding this comment

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

Have you considered using a unique key per message? This would simplify read operations so we won't need to fetch all messages every time, but we'd need to keep track of all messages created somewhere so we can delete them all when deleting a session... What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

We encountered the key/value data structure paradigm in both agents and non-agents workloads. In the end it's preferable to optimize for the least amount of I/O operations, especially when it comes to writing state. Not just to stop split/brain but to optimize for costs. Likely DynamoDB will be used a lot with Strands and we don't want to penalize two write operation for every message.

@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-

Copy link
Member

Choose a reason for hiding this comment

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

missing copyright header

Copy link
Member Author

Choose a reason for hiding this comment

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

Added

@@ -0,0 +1,553 @@
"""Dapr state store session manager for distributed storage."""

Copy link
Member

Choose a reason for hiding this comment

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

missing copyright header

Copy link
Member Author

Choose a reason for hiding this comment

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

Added

[metadata]
url = https://dapr.io/
author = Dapr Authors
author_email = daprweb@microsoft.com
Copy link
Member

Choose a reason for hiding this comment

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

👀

Copy link
Member Author

Choose a reason for hiding this comment

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

🤷‍♂️

yaron2 added 2 commits January 9, 2026 12:37
Signed-off-by: yaron2 <schneider.yaron@live.com>
Signed-off-by: yaron2 <schneider.yaron@live.com>
@yaron2
Copy link
Member Author

yaron2 commented Jan 9, 2026

@acroca All feedback and questions addressed. Examples seem to be failing regardless.

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.

2 participants