Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dependencies = [
"uvicorn[standard]==0.46.0",

# AWS and cloud services
"boto3==1.43.68",
"boto3==1.43.103",

# Utilities
"python-dotenv==1.2.2",
Expand Down Expand Up @@ -67,11 +67,11 @@ dependencies = [
[project.optional-dependencies]
# AgentCore-specific dependencies (for inference_api)
agentcore = [
"strands-agents==1.55.0",
"strands-agents==1.57.1",
"strands-agents-tools==0.8.8",

"aws-opentelemetry-distro==0.19.0",
"bedrock-agentcore==1.21.0",
"bedrock-agentcore==1.23.1",
# Multi-provider LLM support
"openai==2.32.0", # For OpenAI models
"google-genai==1.73.1", # For Google Gemini models
Expand All @@ -82,7 +82,7 @@ agentcore = [

# Voice/BidiAgent dependencies (Nova Sonic speech-to-speech)
bidi = [
"strands-agents[bidi]==1.55.0",
"strands-agents[bidi]==1.57.1",
]

# Document ingestion pipeline dependencies (for Lambda deployment)
Expand Down Expand Up @@ -125,11 +125,11 @@ all = [
# Guard rail on a TRANSITIVE dependency — this is not a loosened pin.
#
# `mcp` arrives only via `strands-agents`; we do not depend on it directly and
# deliberately do not promote it to a direct dependency. `strands-agents==1.55.0`
# — the version pinned above, today — already declares `mcp>=1.23.0,<2.2`, so
# the resolver is free to cross into `mcp` 2.x whenever it gets the chance.
# deliberately do not promote it to a direct dependency. `strands-agents==1.57.1`
# — the version pinned above, today — declares `mcp>=1.23.0,<2.2`, so the
# resolver is free to cross into `mcp` 2.x whenever it gets the chance.
#
# The ONLY thing holding us on the 1.x line right now is incidental: `mcp` 2.x
# Without this constraint the only thing holding us on 1.x is incidental: `mcp` 2.x
# pulls `httpx2>=2.5.0`, which needs `idna>=3.18`, and we pin `idna==3.15` for
# an unrelated Dependabot alert. A routine security bump of `idna` would quietly
# unblock the major crossing — nobody would be choosing it, and nothing in the
Expand All @@ -147,6 +147,7 @@ all = [
# A constraint, not a dependency: it bounds the version IF `mcp` is in the
# resolution graph, without adding it to our own dependency metadata. Lift it
# deliberately, together with a test pass over that `ClientSession` patch.
# Asserted by backend/tests/supply_chain/test_strands_agentcore_pairing.py.
constraint-dependencies = ["mcp<2"]

[tool.setuptools]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ def _approx_tokens(text: Any) -> int:
return 0


def _is_bidi_agent(agent: Any) -> bool:
"""True for the voice path's ``BidiAgent``.

Since strands-agents 1.56 a ``BidiAgent`` drives the *ordinary* session
hooks (``AgentInitializedEvent`` -> ``initialize``, ``MessageAddedEvent``
-> ``append_message`` / ``retrieve_customer_context``) instead of the
dedicated Bidi callbacks it used before, so every override here now sees
voice agents too. The text-only work in them must not run for voice.
"""
try:
from strands.experimental.bidi import BidiAgent
except ImportError:
return False
return isinstance(agent, BidiAgent)


class TurnBasedSessionManager(AgentCoreMemorySessionManager):
"""
Session manager with token-based context compaction.
Expand Down Expand Up @@ -322,6 +338,11 @@ def retrieve_customer_context(self, event: Any) -> None:
``register_hooks`` in both sync and async mode via
``self.retrieve_customer_context``, so the override is picked up.
"""
# Mirrors bedrock-agentcore 1.23.1's own guard: voice transcripts are
# not queries, and LTM context spliced into a live Bidi history would
# be persisted into the voice agent's messages.
if _is_bidi_agent(event.agent):
return None
messages = event.agent.messages
if not messages or messages[-1].get("role") != "user":
return None
Expand Down Expand Up @@ -430,6 +451,16 @@ def initialize(self, agent: "Agent", **kwargs: Any) -> None:
"""
logger.info(f"TurnBasedSessionManager.initialize() called for agent_id={agent.agent_id}")

# A voice BidiAgent reaches here since strands-agents 1.56 (it used to
# take the SDK's separate initialize_bidi_agent path). Restore it the
# same way that path did and stop: everything below is text-chat
# history processing, and the compaction state it reads is keyed on the
# session, so applying the text agent's checkpoint to the voice
# agent's own message list would slice the wrong history.
if _is_bidi_agent(agent):
super().initialize(agent, **kwargs)
return

# Let the SDK handle all session restore logic:
# - read/create agent in session repository
# - restore agent state, internal state, conversation manager state
Expand Down
Loading