Skip to content

feat(memory): add Agent memory_manager param with configurable sync auto-flush#2795

Merged
JackYPCOnline merged 7 commits into
strands-agents:mainfrom
JackYPCOnline:feat/memory-flush-default-on
Jun 15, 2026
Merged

feat(memory): add Agent memory_manager param with configurable sync auto-flush#2795
JackYPCOnline merged 7 commits into
strands-agents:mainfrom
JackYPCOnline:feat/memory-flush-default-on

Conversation

@JackYPCOnline

@JackYPCOnline JackYPCOnline commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a dedicated memory_manager parameter to Agent, mirroring the TypeScript SDK's Agent constructor, and makes cross-session memory persistence work out of the box for the synchronous entry point.

Previously a MemoryManager could only be attached via plugins=[...], and persisting background extraction across the synchronous Agent(...) entry point required the caller to opt in (the original flush_on_invocation_end defaulted to False) or to call flush() manually. That left a quiet footgun: each synchronous invocation runs in its own event loop (run_asyncasyncio.run), which cancels in-flight background saves on close, so the last turn's extraction was typically lost unless the user knew to enable the flag.

This change gives Agent a first-class memory_manager param (matching conversation_manager / session_manager) and flushes pending extraction automatically — but only on the synchronous path, so it never forces a save every turn on the async path or interferes with IntervalTrigger cadence.

Public API Changes

Agent accepts a memory_manager (a MemoryManager instance or a MemoryManagerConfig, auto-wrapped) and exposes it as agent.memory_manager:

from strands import Agent
from strands.memory import MemoryManager

agent = Agent(model=model, memory_manager=MemoryManager(stores=[my_store]))
agent("Remember I prefer dark mode")  # extraction is flushed before the loop closes

flush_on_invocation_end (on MemoryManager / MemoryManagerConfig) has been removed. The synchronous Agent(...) entry point now always awaits pending extraction after each invocation; the async invoke_async path never auto-flushes, so callers driving their own loop continue to await memory_manager.flush() at a shutdown boundary.

Auto-flush covers the Agent(...) entry point only. The deprecated structured_output() sync path is not wired (it doesn't append messages, so extraction records nothing to flush there).

Breaking Changes

The flush_on_invocation_end parameter that shipped in the initial memory port has been removed (same unreleased development cycle). Persistence across the synchronous entry point is now automatic and unconditional, so there is no flag to migrate. Callers needing extraction-cadence control should use invoke_async and flush() directly.

Testing

How have you tested the change? Verify that the changes do not break functionality or introduce new warnings.

Ported the three TS test files (memory-manager, extraction, model-extractor) as example-based pytest suites so coverage mirrors the TS suite case-for-case; runs green under tests/strands/memory. Adjacent plugins/tools suites were run to confirm no regressions in plugin/tool discovery.

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

…uto-flush

Wire a memory_manager param on Agent, mirroring the TS constructor: it
accepts a MemoryManager instance or a MemoryManagerConfig (auto-wrapped),
registers it as a plugin, and exposes it as agent.memory_manager.

The synchronous Agent(...) entry point awaits MemoryManager.flush() after
each invocation so background extraction persists across its
per-invocation event loop. This is gated by flush_on_invocation_end
(default True); set it False to opt out. The async invoke_async path
never auto-flushes, so it does not affect trigger cadence.
@github-actions github-actions Bot added size/m python Pull requests that update python code enhancement New feature or request area-agent Related to the agent class or general agent questions area-persistence Session management or checkpointing labels Jun 15, 2026
Expose flush_on_invocation_end as a plain public attribute instead of a
property, and tighten the Agent.memory_manager docstring.
@JackYPCOnline JackYPCOnline marked this pull request as ready for review June 15, 2026 17:26
Comment thread strands-py/src/strands/memory/memory_manager.py Outdated
Comment thread strands-py/src/strands/agent/agent.py
Comment thread strands-py/src/strands/agent/agent.py
@github-actions

Copy link
Copy Markdown
Contributor

Assessment: Comment

Clean, well-tested change that gives MemoryManager a first-class Agent parameter and makes cross-session persistence work out of the box on the sync path. Implementation is sound and the sync-only flush boundary is a sensible design. A few consistency and coverage points worth addressing before merge.

Review Categories
  • Documentation consistency: The primary MemoryManager docstring example still uses the old plugins=[...] wiring instead of the new dedicated param — the obvious path isn't the one shown.
  • Behavior coverage: Auto-flush is wired only into __call__; the deprecated sync structured_output() path runs its own loop and won't flush. Worth confirming/documenting the intended scope.
  • API review process: This adds a public Agent parameter and flips flush_on_invocation_end default FalseTrue (a behavior change). The PR carries no needs-api-review/completed-api-review label — please confirm whether API bar-raising sign-off is required for this surface.
  • Edge case: Passing the same manager via both plugins=[...] and memory_manager=... surfaces a raw "plugin already registered" error; a dedupe or clearer message would improve DX.

Nice work porting the TS test suite case-for-case — the async/sync/opt-out matrix is well covered and all 81 affected tests pass locally.

- Show the memory_manager= param (not plugins=) in the class docstring example
- Raise a clear error when a MemoryManager is passed via both memory_manager=
  and plugins= instead of a raw 'already registered' ValueError
…istry change

Keep the collision guard inline in Agent rather than adding a __contains__
to the shared plugin registry, which is out of scope for this PR.
Comment thread strands-py/src/strands/memory/memory_manager.py Outdated
Comment thread strands-py/src/strands/agent/agent.py Outdated
Comment thread strands-py/src/strands/memory/memory_manager.py Outdated
Comment thread strands-py/src/strands/agent/agent.py Outdated
… error

- Import MemoryManager/MemoryManagerConfig directly (no circular dep, since
  memory imports Agent only under TYPE_CHECKING); drop the inline import
- Reword the duplicate-registration error to point users at the
  memory_manager parameter rather than endorsing the plugins path
@github-actions

Copy link
Copy Markdown
Contributor

Assessment: Comment (re-review on 247c8fd)

Re-reviewed after the latest push. All three points from my initial review are resolved, and 82 affected tests pass locally.

Resolved since first review
  • Docstring consistency: The MemoryManager example now uses memory_manager= instead of plugins=[...]. ✅
  • Flush scope: The PR description now explicitly documents that auto-flush covers the Agent(...) sync entry point only and intentionally skips the deprecated structured_output() path. ✅
  • Double-registration: Passing the same manager via both plugins=[...] and memory_manager=... now raises a clear, intent-specific error, with dedicated test coverage (test_agent_memory_manager_passed_via_both_paths_raises). ✅
Open item — maintainer design discussion
  • flush_on_invocation_end necessity (threads from @pgrayy / @opieter-aws): The parameter now only gates the sync auto-flush. I've left a detailed reply weighing dropping it (correct-by-default, smaller surface) vs. keeping it (needs a concrete opt-out use case, since on sync it means dropping the last turn's extraction). This is a public Agent param addition + default flip, so it likely warrants a needs-api-review pass before merge.

Implementation is solid and the sync-only flush boundary is well-reasoned and well-tested; the remaining question is purely about API surface, not correctness.

…nditional

Per review, drop the flush_on_invocation_end flag. With a True default it
only risked confusing users, and cadence control is already available via
the async invoke_async path. The synchronous Agent(...) entry point now
always flushes pending extraction after each invocation.
@github-actions

Copy link
Copy Markdown
Contributor

Assessment: Approve (re-review on f4b2056)

The design discussion landed well — flush_on_invocation_end has been removed entirely, leaving a minimal, correct-by-default surface. All earlier review points are resolved.

Verification on this commit
  • Parameter removal: flush_on_invocation_end gone from MemoryManager.__init__, MemoryManagerConfig, and _resolve_memory_manager. No dangling references in src/, tests/, or docs/. ✅
  • Flush correctness: Sync flush runs in a finally block in _invoke_async_and_flush, so extraction persists even if the invocation raises; invoke_async never auto-flushes. ✅
  • Tests: 210 passed (memory + agent suites); obsolete opt-out tests correctly removed, sync/async/collision/wrapping coverage retained. ✅
  • Lint/types: ruff check and mypy clean on touched files. ✅
One open process note
  • API review label: The surface is now smaller (a single new Agent parameter, no behavioral toggle), but it's still a new public param. The PR still lacks a needs-api-review/completed-api-review label — worth confirming whether bar-raising sign-off is needed before merge. Not a code blocker.

Clean, well-tested change with a sensible sync-only flush boundary. Nice work iterating on the feedback.

Comment thread strands-py/src/strands/agent/agent.py
Comment thread strands-py/src/strands/agent/agent.py
It does not use self, matching the sibling _resolve_context_manager.
@JackYPCOnline JackYPCOnline enabled auto-merge (squash) June 15, 2026 19:14
@github-actions

Copy link
Copy Markdown
Contributor

Assessment: Approve (re-review on 51ed5e6)

All review threads are now resolved. Verified the two new points from @opieter-aws on this commit:

Verification on this commit
  • @staticmethod (comment 3415839986): _resolve_memory_manager is now @staticmethod, matching _resolve_context_manager. ✅
  • Flush masking primary exception (comment 3415836704): Traced the path — MemoryManager.flush()ExtractionCoordinator.flush() drains with asyncio.gather(..., return_exceptions=True) and is documented "Never raises," so the finally-block flush can't swallow a model error or CancelledError. Author's response is correct; no guard needed. ✅
  • Tests / lint / types: 210 passed (memory + agent suites); ruff check and mypy clean on touched files. ✅
One open process note (unchanged)
  • API review label: Still a new public Agent parameter without a needs-api-review/completed-api-review label. Surface is minimal (single param, no behavioral toggle), so not a code blocker — just worth confirming bar-raising sign-off before merge.

Implementation is solid, the sync-only flush boundary is well-reasoned, and the iteration on feedback has been thorough. LGTM.

@JackYPCOnline JackYPCOnline merged commit 6a2445f into strands-agents:main Jun 15, 2026
22 of 23 checks passed
@JackYPCOnline JackYPCOnline deleted the feat/memory-flush-default-on branch June 15, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-agent Related to the agent class or general agent questions area-persistence Session management or checkpointing enhancement New feature or request python Pull requests that update python code size/m

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants