feat(session): add dirty flag to skip unnecessary agent state persistence#1803
feat(session): add dirty flag to skip unnecessary agent state persistence#1803
Conversation
…ence - Add _dirty flag to JSONSerializableDict with _is_dirty() and _clear_dirty() methods - Set _dirty=True when set() or delete() is called - Modify sync_agent() to check dirty flag and state changes before persisting - Track internal_state and conversation_manager_state for comparison - Skip update_agent() calls when no changes detected - Clear dirty flag only after successful sync (keep set on failure for retry) This optimization reduces unnecessary I/O operations when the agent processes messages without modifying its state.
|
Assessment: Comment Good implementation that addresses the performance optimization goal. The dirty flag pattern is well-executed with proper retry semantics (flag preserved on failure). Review Details
Minor suggestion for future consideration: The |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
/strands Address the review comment |
Address review feedback by adding missing test coverage for the conversation_manager_state_changed condition in sync_agent().
|
Assessment: Approve The previous review feedback has been addressed - the test for Test Coverage SummaryAll three sync conditions are now tested:
Plus edge cases: skip behavior, flag clearing, failure handling, first sync. Clean implementation that follows repository patterns and properly optimizes I/O operations. |
|
/strands |
- Add _dirty flag to _InterruptState dataclass (excluded from compare/repr) - Set _dirty=True when activate(), deactivate(), or resume() is called - Add _is_dirty() and _clear_dirty() methods - Update sync_agent() to use _interrupt_state._is_dirty() instead of state diffing - Add 6 tests for _InterruptState dirty flag behavior - Update to_dict() to exclude _dirty from serialization
|
Assessment: Approve Good update adding dirty tracking to Changes Reviewed
Tests (
Clean improvement that makes dirty tracking consistent across both state types. |
src/strands/types/json_dict.py
Outdated
|
|
||
| Called after successful persistence to mark the state as clean. | ||
| """ | ||
| self._dirty = False |
There was a problem hiding this comment.
Would it be better to have an id that is incremented that the session manager checks/stores? That way it doesn't have to explicitly clear it?
This seems to be tying dirtiness only to the session-manager which is fine, but slightly leaky
There was a problem hiding this comment.
Same for the other classes.
(we could do this later)
There was a problem hiding this comment.
Good suggestion! A version ID approach would decouple change tracking from specific consumers. The current dirty flag is simpler for the single-consumer case, but we can refactor to version IDs in a follow-up if multiple consumers need independent tracking.
|
/strands apply the proposed fix for the dirty flag |
Per review feedback, replace boolean _dirty flag with incrementing _version ID: - JSONSerializableDict: _dirty -> _version, _is_dirty()/_clear_dirty() -> _get_version() - _InterruptState: _dirty -> _version, _is_dirty()/_clear_dirty() -> _get_version() - sync_agent(): Compare versions instead of checking dirty flags Benefits: - No need for explicit _clear_dirty() calls - Multiple consumers can independently track changes - Less coupling between state objects and session manager - Version is stored after successful sync for comparison
Motivation
Session managers currently call
update_agent()on every sync, even when the agent's state hasn't changed. This results in unnecessary I/O operations (file writes, S3 puts, etc.) that impact performance, especially in high-throughput scenarios where agents process messages without modifying their state.This change reduces the number of
update_agent()calls by tracking state changes via version IDs and skipping persistence when nothing has changed.Resolves #1798
Public API Changes
JSONSerializableDictnow includes a private method for version tracking:The version increments each time
set()ordelete()is called:_InterruptStatesimilarly uses_get_version()that increments onactivate(),deactivate(), orresume().RepositorySessionManager.sync_agent()now conditionally persists by comparing versions:agent.state._get_version())agent._interrupt_state._get_version())Use Cases
MessageAddedEventandAfterInvocationEventhooks both trigger syncs; subsequent syncs will be skipped if nothing changed