Skip to content

Commit 215b5bd

Browse files
authored
feat: add read_only flag to AgentCoreMemorySessionManager to disable ACM persistence (#389)
* feat: add persistence_mode to AgentCoreMemorySessionManager to control ACM persistence Add PersistenceMode enum (FULL | NONE) and persistence_mode config option to AgentCoreMemoryConfig. When set to NONE, disables persistence to AgentCore Memory (create_event calls) while keeping local session/agent state management and memory injection (LTM retrieval) working. This allows customers who only need memory injection into agent context without persisting agent state to set persistence_mode=PersistenceMode.NONE on their existing configuration. * fix: address review feedback - store persistence_mode directly and fix lint errors - Replace self.read_only boolean with self.persistence_mode to store the enum directly, making it easier to support future PersistenceMode values - Fix 5 E501 line-too-long lint errors in test file - Run ruff format on changed files
1 parent 4ebfdcb commit 215b5bd

3 files changed

Lines changed: 285 additions & 29 deletions

File tree

src/bedrock_agentcore/memory/integrations/strands/config.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Configuration for AgentCore Memory Session Manager."""
22

3+
from enum import Enum
34
from typing import Any, Callable, Dict, Optional
45

56
from pydantic import BaseModel, Field, field_validator
@@ -10,6 +11,19 @@ def normalize_metadata(raw: Dict[str, Any]) -> Dict[str, Any]:
1011
return {k: {"stringValue": v} if isinstance(v, str) else v for k, v in raw.items()}
1112

1213

14+
class PersistenceMode(str, Enum):
15+
"""Controls what gets persisted to AgentCore Memory.
16+
17+
Attributes:
18+
FULL: Persist everything (session, agent state, messages) to AgentCore Memory. Default behavior.
19+
NONE: Disable all persistence. Local session/agent state management and memory injection
20+
(LTM retrieval) still work, but no create_event calls are made to AgentCore Memory.
21+
"""
22+
23+
FULL = "FULL"
24+
NONE = "NONE"
25+
26+
1327
class RetrievalConfig(BaseModel):
1428
"""Configuration for memory retrieval operations.
1529
@@ -51,6 +65,9 @@ class AgentCoreMemoryConfig(BaseModel):
5165
event creation, so it can return dynamic values (e.g. current traceId). The returned
5266
dict is merged after default_metadata but before per-call metadata.
5367
Accepts plain strings (auto-wrapped) or explicit MetadataValue dicts.
68+
persistence_mode: Controls what gets persisted to AgentCore Memory.
69+
FULL (default): persist everything. NONE: disable all persistence while keeping
70+
local state management and memory injection working.
5471
"""
5572

5673
memory_id: str = Field(min_length=1)
@@ -63,6 +80,7 @@ class AgentCoreMemoryConfig(BaseModel):
6380
filter_restored_tool_context: bool = Field(default=False)
6481
default_metadata: Optional[Dict[str, Any]] = None
6582
metadata_provider: Optional[Callable[[], Dict[str, Any]]] = None
83+
persistence_mode: PersistenceMode = Field(default=PersistenceMode.FULL)
6684

6785
@field_validator("default_metadata", mode="before")
6886
@classmethod

src/bedrock_agentcore/memory/integrations/strands/session_manager.py

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
)
3030

3131
from .bedrock_converter import AgentCoreMemoryConverter
32-
from .config import AgentCoreMemoryConfig, RetrievalConfig, normalize_metadata
32+
from .config import AgentCoreMemoryConfig, PersistenceMode, RetrievalConfig, normalize_metadata
3333
from .converters import MemoryConverter
3434

3535
if TYPE_CHECKING:
@@ -142,6 +142,7 @@ def __init__(
142142
"""
143143
self.converter = converter or AgentCoreMemoryConverter
144144
self.config = agentcore_memory_config
145+
self.persistence_mode = agentcore_memory_config.persistence_mode
145146
self.memory_client = MemoryClient(region_name=region_name)
146147
session = boto_session or boto3.Session(region_name=region_name)
147148
self.has_existing_agent = False
@@ -255,17 +256,19 @@ def create_session(self, session: Session, **kwargs: Any) -> Session:
255256
if session.session_id != self.config.session_id:
256257
raise SessionException(f"Session ID mismatch: expected {self.config.session_id}, got {session.session_id}")
257258

258-
event = self.memory_client.gmdp_client.create_event(
259-
memoryId=self.config.memory_id,
260-
actorId=self.config.actor_id,
261-
sessionId=self.session_id,
262-
payload=[
263-
{"blob": json.dumps(session.to_dict())},
264-
],
265-
eventTimestamp=self._get_monotonic_timestamp(),
266-
metadata={STATE_TYPE_KEY: {"stringValue": StateType.SESSION.value}},
267-
)
268-
logger.info("Created session: %s with event: %s", session.session_id, event.get("event", {}).get("eventId"))
259+
if self.persistence_mode is not PersistenceMode.NONE:
260+
event = self.memory_client.gmdp_client.create_event(
261+
memoryId=self.config.memory_id,
262+
actorId=self.config.actor_id,
263+
sessionId=self.session_id,
264+
payload=[
265+
{"blob": json.dumps(session.to_dict())},
266+
],
267+
eventTimestamp=self._get_monotonic_timestamp(),
268+
metadata={STATE_TYPE_KEY: {"stringValue": StateType.SESSION.value}},
269+
)
270+
logger.info("Created session: %s with event: %s", session.session_id, event.get("event", {}).get("eventId"))
271+
269272
return session
270273

271274
def read_session(self, session_id: str, **kwargs: Any) -> Optional[Session]:
@@ -318,14 +321,15 @@ def read_session(self, session_id: str, **kwargs: Any) -> Optional[Session]:
318321
session_data = json.loads(old_event.get("payload", {})[0].get("blob"))
319322
session = Session.from_dict(session_data)
320323
# Migrate: create new event with metadata, delete old
321-
self.create_session(session)
322-
self.memory_client.gmdp_client.delete_event(
323-
memoryId=self.config.memory_id,
324-
actorId=legacy_actor_id,
325-
sessionId=session_id,
326-
eventId=old_event.get("eventId"),
327-
)
328-
logger.info("Migrated legacy session event for session: %s", session_id)
324+
if self.persistence_mode is not PersistenceMode.NONE:
325+
self.create_session(session)
326+
self.memory_client.gmdp_client.delete_event(
327+
memoryId=self.config.memory_id,
328+
actorId=legacy_actor_id,
329+
sessionId=session_id,
330+
eventId=old_event.get("eventId"),
331+
)
332+
logger.info("Migrated legacy session event for session: %s", session_id)
329333
return session
330334

331335
return None
@@ -364,6 +368,9 @@ def create_agent(self, session_id: str, session_agent: SessionAgent, **kwargs: A
364368
if session_agent.created_at:
365369
self._agent_created_at_cache[session_agent.agent_id] = session_agent.created_at
366370

371+
if self.persistence_mode is PersistenceMode.NONE:
372+
return
373+
367374
if self.config.batch_size > 1:
368375
# Buffer the agent state events
369376
should_flush = False
@@ -462,14 +469,15 @@ def read_agent(self, session_id: str, agent_id: str, **kwargs: Any) -> Optional[
462469
agent_data = json.loads(old_event.get("payload", {})[0].get("blob"))
463470
agent = SessionAgent.from_dict(agent_data)
464471
# Migrate: create new event with metadata, delete old
465-
self.create_agent(session_id, agent)
466-
self.memory_client.gmdp_client.delete_event(
467-
memoryId=self.config.memory_id,
468-
actorId=legacy_actor_id,
469-
sessionId=session_id,
470-
eventId=old_event.get("eventId"),
471-
)
472-
logger.info("Migrated legacy agent event for agent: %s", agent_id)
472+
if self.persistence_mode is not PersistenceMode.NONE:
473+
self.create_agent(session_id, agent)
474+
self.memory_client.gmdp_client.delete_event(
475+
memoryId=self.config.memory_id,
476+
actorId=legacy_actor_id,
477+
sessionId=session_id,
478+
eventId=old_event.get("eventId"),
479+
)
480+
logger.info("Migrated legacy agent event for agent: %s", agent_id)
473481
return agent
474482

475483
return None
@@ -546,6 +554,9 @@ def create_message(
546554
if not messages:
547555
return None
548556

557+
if self.persistence_mode is PersistenceMode.NONE:
558+
return {}
559+
549560
is_blob = self.converter.exceeds_conversational_limit(messages[0])
550561

551562
# Build merged metadata from config defaults + per-call overrides
@@ -862,6 +873,9 @@ def _flush_messages_only(self) -> list[dict[str, Any]]:
862873
Raises:
863874
SessionException: If message creation fails. On failure, messages remain in the buffer.
864875
"""
876+
if self.persistence_mode is PersistenceMode.NONE:
877+
return []
878+
865879
with self._message_lock:
866880
messages_to_send = list(self._message_buffer)
867881

@@ -940,6 +954,9 @@ def _flush_agent_states_only(self) -> list[dict[str, Any]]:
940954
Raises:
941955
SessionException: If agent state creation fails. On failure, agent states remain in the buffer.
942956
"""
957+
if self.persistence_mode is PersistenceMode.NONE:
958+
return []
959+
943960
with self._agent_state_lock:
944961
agent_states_to_send = list(self._agent_state_buffer)
945962

0 commit comments

Comments
 (0)