Skip to content

fix(memory): store completed BidiAgent transcripts - #722

Open
abhiramshaji-cyber wants to merge 1 commit into
aws:mainfrom
abhiramshaji-cyber:fix/memory-bidi-transcripts
Open

abhiramshaji-cyber wants to merge 1 commit into
aws:mainfrom
abhiramshaji-cyber:fix/memory-bidi-transcripts

Conversation

@abhiramshaji-cyber

Copy link
Copy Markdown
Contributor

Voice agents built with BidiAgent now save what the caller and the assistant said into AgentCore Memory, so long term memory has something to learn from.

What this code is for

AgentCoreMemorySessionManager listens to Strands agent events and writes each conversation turn to AgentCore Memory as a USER or ASSISTANT event. Memory strategies (summary, preferences, facts) read those events to build long term memory.

Before

  1. The caller speaks. BidiAgent adds an empty placeholder message to its history and fires MessageAddedEvent with bidi status "pending".
  2. The session manager tries to save it. The message has no text, so nothing is written.
  3. The caller stops speaking. BidiAgent replaces the placeholder with the finished text and fires MessageUpdatedEvent with status "complete".
  4. The session manager does not listen to MessageUpdatedEvent, so the finished text is never written.
  5. The same happens for every assistant reply. Memory holds only the greeting and agent state, and memory strategies extract nothing.

After

  1. The caller speaks. The "pending" placeholder is skipped on purpose.
  2. The caller stops speaking. BidiAgent fires MessageUpdatedEvent with status "complete".
  3. The session manager now listens to MessageUpdatedEvent and saves the finished text through append_message, the same path text messages use.
  4. Updates with status "incomplete" (a turn cut off when the session ends) are not saved, and empty text is still dropped by the converter.
  5. Each turn completes once, so each turn is saved once, as USER or ASSISTANT.
flowchart LR
  subgraph Before
    direction TB
    A1[Turn starts: placeholder added] --> A2[MessageAddedEvent: empty, nothing saved]
    A2 --> A3[Turn ends: placeholder replaced]
    A3 --> A4[MessageUpdatedEvent: not handled]
    A4 --> A5[Memory has no turns]
  end
  subgraph After
    direction TB
    B1[Turn starts: placeholder added] --> B2[MessageAddedEvent: pending, skipped]
    B2 --> B3[Turn ends: placeholder replaced]
    B3 --> B4[MessageUpdatedEvent: complete, saved once]
    B4 --> B5[Memory has USER and ASSISTANT turns]
  end
Loading

The change

In session_manager.py:

  1. A small _bidi_status helper reads metadata.custom.bidi.status from a message. It returns None for normal messages, including ones whose metadata is missing or None.
  2. append_message returns early for "pending" messages, so a custom converter can never store an empty placeholder.
  3. A new _append_completed_message callback saves a MessageUpdatedEvent message only when its status is "complete". It is registered in sync mode and, through the existing asyncio.to_thread wrapper, in async mode.
  4. MessageUpdatedEvent is looked up with getattr, and the callback is only registered when the installed strands-agents provides it. The minimum supported version (1.56.0) does not have it, and nothing changes there.

Tests in test_agentcore_memory_session_manager.py cover: pending placeholders are not saved, complete user and assistant transcripts are saved exactly once with the right role, incomplete, empty and whitespace transcripts are not saved, updates without a bidi status are not saved, and normal messages with missing or None metadata are still saved. Each runs in sync and async mode. The update tests are skipped when strands-agents has no MessageUpdatedEvent.

I also ran the reproduction script from the issue against strands-agents 1.62.0. Before this change memory held only the greeting. After it, memory holds the greeting, the user turn and the assistant turn, each once, in both sync and async mode.

What changes for users

Voice agents that use BidiAgent with AgentCoreMemorySessionManager now keep a record of each conversation, and long term memory strategies can summarize it and remember the caller in later sessions. Regular Agent users see no change.

This PR does not change the write latency in the receive loop or the error handling that the issue also mentions; those can be handled separately.

Closes #716

BidiAgent adds each spoken turn to history as an empty placeholder
(MessageAddedEvent, bidi status "pending") and later replaces it with
the finished text through MessageUpdatedEvent (status "complete").
The session manager only persisted on MessageAddedEvent, so the
placeholder was dropped as empty and the finished text was never
written. Voice agents therefore stored no user or assistant turns and
long term memory strategies had nothing to extract.

Persist MessageUpdatedEvent messages whose bidi status is "complete"
through append_message, the same path text messages use, in both sync
and async mode. Pending placeholders are skipped explicitly so a custom
converter cannot store them, and "incomplete" updates are ignored.
Each transcript completes once, so it is written exactly once. The hook
is only registered when the installed strands-agents provides
MessageUpdatedEvent.

Closes aws#716

This branch is waiting to be deployed

1 waiting deployment
manual-approval — faef3a09 Waiting Oct 28, 2026 by abhiramshaji-cyber via Test (tools) #1676
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AgentCoreMemorySessionManager: BidiAgent transcripts are never stored (MessageUpdatedEvent not handled)

1 participant