Repository navigation
fix(memory): store completed BidiAgent transcripts - #722
Open
abhiramshaji-cyber wants to merge 4 commits into
Open
abhiramshaji-cyber wants to merge 4 commits into
abhiramshaji-cyber wants to merge 4 commits into
Conversation
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
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 28, 2026 00:48 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 28, 2026 00:48 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 28, 2026 00:48 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 28, 2026 00:48 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 28, 2026 00:48 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 28, 2026 00:48 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 28, 2026 00:48 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 28, 2026 00:48 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 28, 2026 00:48 — with
GitHub Actions
Waiting
Contributor
Author
MessageUpdatedEvent, which BidiAgent uses to report a finished transcript, first shipped in strands-agents 1.57.1. The lock pinned 1.56.0, so CI skipped every test that exercises the transcript fix. Raise the minimum to 1.57.1 and relock to exactly that version. strands-agents 1.57.1 also adds RepositorySessionManager._held_records and walks agent.messages in sync_agent. The test helper patches the parent __init__ away and some tests use bare Mock agents, so set _held_records in the helper and give those agents an empty message list. Production code calls the parent __init__ and is unaffected.
BidiAgent reserves a message's place in history when a transcript starts and fills it in when the transcript finishes. With Gemini and OpenAI realtime the caller's transcript can still be open while the assistant reply starts and finishes, so writing each transcript when it finished stored the reply before the question, and a restored session replayed them in that order. Hold every message that comes after an unfinished BidiAgent message and write them in history order once it finishes. An incomplete transcript releases the messages behind it without being stored, and BidiAgentStopEvent and close() write anything still held so nothing is lost when a session ends with a transcript open. A failed write is dropped and raised as before, so it cannot block later messages. Regular Agent messages never wait because nothing is pending. Import MessageUpdatedEvent directly now that strands-agents 1.57.1 is the minimum, give the update callback its real event type, and add tests that fail without the fix, including one with a converter that keeps empty content to show why pending placeholders are never written.
A MessageUpdatedEvent that matched a waiting BidiAgent transcript but carried no bidi status fell into the delete branch, so the transcript was dropped and the messages behind it were written ahead of it. Only an "incomplete" status means the transcript will never finish; any other update now leaves it waiting until it completes or the agent stops. Also cover history order when batch_size is above one, in sync and async mode, including messages released when the agent stops.
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 29, 2026 05:42 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 29, 2026 05:42 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 29, 2026 05:42 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 29, 2026 05:42 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 29, 2026 05:42 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 29, 2026 05:42 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 29, 2026 05:42 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 29, 2026 05:42 — with
GitHub Actions
Waiting
abhiramshaji-cyber
requested a deployment
to
manual-approval
October 29, 2026 05:42 — with
GitHub Actions
Waiting
Contributor
Author
|
I pushed an update. Turns are now saved in history order, so a reply that finishes before the caller's words no longer lands first. I also raised the |
This branch is waiting to be deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Voice agents now save what the caller and the assistant said into AgentCore Memory, in the order it was said, so long term memory has something to learn from.
What this code is for
The AgentCore Memory session manager listens to Strands agent events and writes each conversation turn to AgentCore Memory as a
USERorASSISTANTevent. Memory strategies (summary, preferences, facts) read those events to build long term memory, and a restored session reads them back to rebuild the conversation.Before this PR
BidiAgentand says "Any rooms in London?"BidiAgentadds an empty message for the caller's words to its history and firesMessageAddedEventwith bidi status"pending".BidiAgentfills in the message and firesMessageUpdatedEventwith status"complete".MessageUpdatedEvent, so the caller's words are never saved. The assistant's reply "Yes, two rooms." goes through the same steps and is not saved either.After this PR
BidiAgentand says "Any rooms in London?"BidiAgentadds an empty message for the caller's words to its history and firesMessageAddedEventwith bidi status"pending".BidiAgentfills in the message and firesMessageUpdatedEventwith status"complete".The diagram
flowchart LR subgraph BEFORE direction TB B1["Caller asks: Any rooms in London?"] --> B2["Empty message added, MessageAddedEvent"] B2 ==> B3["Empty message not saved"] B3 --> B4["Words written down, MessageUpdatedEvent"] B4 --> B5["Update ignored, words never saved"] B5 --> B6["Memory has no record of the call"] end subgraph AFTER direction TB A1["Caller asks: Any rooms in London?"] --> A2["Empty message added, MessageAddedEvent"] A2 ==> A3["THE CHANGE: not saved yet, later messages saved only after it"] A3 --> A4["Words written down, MessageUpdatedEvent"] A4 --> A5["Saved, then later finished messages saved in history order"] A5 --> A6["Memory holds question, then answer"] N1["Finished text or tool message added, nothing unsaved before it"] --> N2["Saved right away on MessageAddedEvent, as before"] end B1 ~~~ A1 classDef change fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px class A3 change linkStyle 1 stroke:#c62828,stroke-width:3px linkStyle 6 stroke:#2e7d32,stroke-width:3pxThe change
In
session_manager.py:append_messagekeeps a message whose bidi status is"pending"in a short waiting list instead of writing it. While anything is in that list, later messages are added to the end of it. When the list is empty, messages are written straight away exactly as before._complete_messagecallback handlesMessageUpdatedEvent. A"complete"update fills in the waiting message, and every ready message at the front of the list is written in order. An"incomplete"update (Strands sends one for each transcript still open when the model stream ends) removes its message without saving it, so the messages after it can be written. Any other update, including one without a bidi status, leaves the list unchanged.BidiAgentStopEventandclose()write every finished message still in the list and drop messages that never finished, so nothing finished is lost when a session ends with a turn still open.close().asyncio.to_threadwrapper, in async mode. A lock keeps the order when async mode runs the callbacks on worker threads.Version bump:
MessageUpdatedEventfirst shipped instrands-agents1.57.1. The minimum is raised from 1.56.0 to 1.57.1 inpyproject.toml, anduv.lockis relocked to exactly 1.57.1, so CI now runs every new test instead of skipping them.MessageUpdatedEventis now imported directly. Version 1.57.1 also added_held_recordsto the Strands base session manager and readsagent.messageswhen syncing the agent. The test helper skips the base class constructor and some tests use bare mock agents, so the helper now sets_held_recordsand those agents get an empty message list. Production code calls the base constructor and is not affected.A question for you: on newer
strands-agentsversions (I checked 1.62.0),BidiAgentalso streams finished text replies and reasoning text through the same pending and complete messages, so with this PR those are saved asASSISTANTevents too, reasoning included. I was not sure whether you want reasoning stored in memory. Happy to filter reasoning out if you'd rather not store it.What changes for a user
Voice agents that use
BidiAgentwith AgentCore Memory now keep a record of each conversation in the order it happened, and long term memory strategies can summarize it and remember the caller in later sessions. This also holds when the caller's words are written down after the assistant has started or finished replying.What still works: regular agents and finished text or tool messages are saved the moment they are added, as before. Batching, async mode and restoring a session are unchanged.
One thing to know: while the caller's words are still being written down, the messages added after them are kept only in the running process. They are written when the words finish, when the agent stops, or on
close(). If the process crashes before then, those messages are lost along with the unfinished words.Projects pinned below
strands-agents1.57.1 need to upgrade.Not in this PR, as the issue also mentions: the first audio of a reply still waits for one memory write in async mode, and a failed memory write can still end the voice session. Those can be handled separately.
Closes #716