Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8dda5ea
feat: Strands Agents adapter — injectable-model family [INT-971]
nir-singher-band Jul 13, 2026
e207073
fix(strands): use shared tool parser
AlexanderZ-Band Jul 26, 2026
2e7850f
refactor(strands): separate adapter responsibilities
AlexanderZ-Band Jul 26, 2026
e8dda30
test(e2e): cover contacts capability
AlexanderZ-Band Jul 26, 2026
680735e
docs(strands): simplify adapter and test guidance
AlexanderZ-Band Jul 26, 2026
25d78d6
refactor strands tool bridges
AlexanderZ-Band Jul 26, 2026
dd5dba6
test: strengthen adapter state isolation coverage
AlexanderZ-Band Jul 27, 2026
e15556f
fix(baseline): register strands in the baseline support registry
AlexanderZ-Band Jul 27, 2026
affa264
refactor(sdk): emit tool events through ToolEventKey everywhere
AlexanderZ-Band Jul 27, 2026
a81467b
refactor(sdk): simplify the lazy export helper and guard it
AlexanderZ-Band Jul 27, 2026
bc10800
fix(strands): reject shadowed tool names and share the scripted model
AlexanderZ-Band Jul 27, 2026
90225aa
docs(strands): document the adapter and extend the examples
AlexanderZ-Band Jul 27, 2026
a6bdf0c
fix(strands): keep every toolUse next to its toolResult
AlexanderZ-Band Jul 27, 2026
186ee4d
fix(pydantic-ai): end a turn cleanly when the model has nothing left …
AlexanderZ-Band Jul 27, 2026
80d7612
fix(adapters): say what actually happened when no reply reaches the room
AlexanderZ-Band Jul 27, 2026
17834ef
refactor(sdk): single-source shared literals and bind lazy exports once
AlexanderZ-Band Jul 27, 2026
35c80f2
fix(strands): hold peer turns behind every parallel tool call
AlexanderZ-Band Jul 27, 2026
1a0ce47
test: close two holes in the repo-wide guards
AlexanderZ-Band Jul 27, 2026
2966614
chore(ci): scope GITHUB_TOKEN to contents:read
AlexanderZ-Band Jul 27, 2026
5c51df2
test(strands): cover the paths a turn can fail on
AlexanderZ-Band Jul 27, 2026
f32c35b
fix(strands): keep the Converse transcript valid under real room traffic
AlexanderZ-Band Jul 27, 2026
9056d2c
fix(test): stop agno unit tests firing live telemetry POST
AlexanderZ-Band Jul 27, 2026
1cbdad4
fix(strands): pair an orphaned toolResult with a synthetic toolUse
AlexanderZ-Band Jul 29, 2026
107d671
test(a2a): import smokes for the standalone A2A examples
claude Jul 31, 2026
b368468
test(e2e): assert rehydrated recall by effect, not narration
claude Jul 31, 2026
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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ on:
# against the queued merge result. Without this the queue would wait forever.
merge_group:

# Every job here only reads the repo; nothing uses GITHUB_TOKEN for writes.
permissions:
contents: read

jobs:

lint:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ on:
# the orphan sweep only reaps agents older than BAND_E2E_ORPHAN_MAX_AGE_MINUTES,
# so a concurrent run's fresh agents are never swept out from under it.

# Every job here only reads the repo. The GitHub API work uses E2E_GITHUB_TOKEN,
# and the scorecard artifacts are downloaded from this same run.
permissions:
contents: read

jobs:

lanes:
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This is a Python SDK that connects AI agents to the Band collaborative platform.

## Core Features

1. Multi-framework support (LangGraph, Anthropic, CrewAI, Claude SDK, Copilot SDK, Codex, Pydantic AI, Parlant, Gemini, Letta, Google ADK, OpenCode, Agno)
1. Multi-framework support (LangGraph, Anthropic, CrewAI, Claude SDK, Copilot SDK, Codex, Pydantic AI, Parlant, Gemini, Letta, Google ADK, OpenCode, Agno, Strands Agents)
2. A2A protocol support: Bridge to remote A2A agents and expose Band peers as A2A endpoints
3. ACP integration: Editor-facing server and client adapters over stdio or TCP (Cursor, Codex, Claude Code, GitHub Copilot)
4. Platform tools for chat, contacts, and memory management
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ For the full picture, rooms, contacts, platform tools, and how messages flow - s
| Parlant | `parlant` | `ParlantAdapter` | | [examples](examples/parlant/) |
| Letta | `letta` | `LettaAdapter` | | [examples](examples/letta/) |
| Agno | `agno` | `AgnoAdapter` | | [examples](examples/agno/) |
| Strands Agents | `strands` | `StrandsAdapter` | | [examples](examples/strands/) |
| Codex | `codex` | `CodexAdapter` | [docs](docs/adapters/codex.md) | [examples](examples/codex/) |
| OpenCode | `opencode` | `OpencodeAdapter` | | [examples](examples/opencode/) |

Expand Down Expand Up @@ -437,6 +438,7 @@ Adapter emit support:
| Google ADK | Yes | - | - |
| Pydantic AI | Yes | - | - |
| LangGraph | Yes | - | - |
| Strands Agents | Yes | - | - |
| Parlant | - | - | - |
| A2A / A2A Gateway | - | - | - |
| ACP Client | - | - | - |
Expand Down Expand Up @@ -753,7 +755,7 @@ uv run python examples/run_agent.py --example anthropic
uv run python examples/run_agent.py --example codex
```

`examples/run_agent.py` supports `langgraph`, `pydantic_ai`, `anthropic`, `claude_sdk`, `parlant`, `crewai`, `codex`, `a2a`, and `a2a_gateway`, plus contact-management variants. Other supported adapters have direct example files: `examples/gemini/01_basic_agent.py`, `examples/google_adk/01_basic_agent.py`, `examples/letta/01_basic_agent.py`, `examples/agno/01_basic_agent.py`, and `examples/opencode/01_basic_agent.py`.
`examples/run_agent.py` supports `langgraph`, `pydantic_ai`, `anthropic`, `claude_sdk`, `parlant`, `crewai`, `codex`, `a2a`, and `a2a_gateway`, plus contact-management variants. Other supported adapters have direct example files: `examples/gemini/01_basic_agent.py`, `examples/google_adk/01_basic_agent.py`, `examples/letta/01_basic_agent.py`, `examples/agno/01_basic_agent.py`, `examples/strands/01_basic_agent.py`, and `examples/opencode/01_basic_agent.py`.

For a multi-framework collaboration demo that puts CrewAI agents and A2A-bridged services in the same room, see [examples/mixed](examples/mixed/).

Expand Down
17 changes: 15 additions & 2 deletions agent_config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,19 @@ google_adk_agent:
agent_id: ""
api_key: ""

# =============================================================================
# Strands Agents Examples
# =============================================================================

# 01_basic_agent.py, 02_custom_tools.py, 03_custom_instructions.py,
# 04_native_tools.py, 05_bedrock_model.py
strands_agent:
agent_id: ""
api_key: ""

# 06_tom_agent.py - see tom_agent in Shared Agents section
# 07_jerry_agent.py - see jerry_agent in Shared Agents section

# =============================================================================
# Letta Examples
# =============================================================================
Expand Down Expand Up @@ -304,13 +317,13 @@ arena_guesser_3:
# =============================================================================

# Tom the cat character agent
# Used by: langgraph/07, claude_sdk/03, anthropic/03, pydantic_ai/03, parlant/04, crewai/05, agno/03, codex/02, opencode/05
# Used by: langgraph/07, claude_sdk/03, anthropic/03, pydantic_ai/03, parlant/04, crewai/05, agno/03, codex/02, opencode/05, strands/06
tom_agent:
agent_id: ""
api_key: ""

# Jerry the mouse character agent
# Used by: langgraph/08, claude_sdk/04, anthropic/04, pydantic_ai/04, parlant/05, crewai/06, agno/04, codex/03, opencode/06
# Used by: langgraph/08, claude_sdk/04, anthropic/04, pydantic_ai/04, parlant/05, crewai/06, agno/04, codex/03, opencode/06, strands/07
jerry_agent:
agent_id: ""
api_key: ""
Expand Down
69 changes: 69 additions & 0 deletions examples/strands/01_basic_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# /// script
# requires-python = ">=3.11"
# dependencies = ["band-sdk[strands]"]
#
# [tool.uv.sources]
# band-sdk = { git = "https://github.com/band-ai/band-sdk-python.git" }
# ///
"""
Basic Strands Agents example.

This is the simplest way to create a Band agent with AWS Strands Agents.
The adapter handles tool registration automatically.

Strands has no provider-prefix model string (a bare string means a Bedrock
model id), so the OpenAI provider is constructed explicitly; it reads
OPENAI_API_KEY from the environment.

Run with:
uv run examples/strands/01_basic_agent.py
"""

from __future__ import annotations

import asyncio
import logging
import os

from dotenv import load_dotenv
from strands.models.openai import OpenAIModel

from setup_logging import setup_logging
from band import Agent
from band.adapters import StrandsAdapter

setup_logging()
logger = logging.getLogger(__name__)


async def main() -> None:
load_dotenv()

ws_url = os.getenv("BAND_WS_URL")
rest_url = os.getenv("BAND_REST_URL")

if not ws_url:
raise ValueError("BAND_WS_URL environment variable is required")
if not rest_url:
raise ValueError("BAND_REST_URL environment variable is required")

# Create adapter with framework-specific settings
adapter = StrandsAdapter(
model=OpenAIModel(model_id="gpt-5.4-mini"),
custom_section="You are a helpful assistant. Be concise and friendly.",
)

# Create and start agent
agent = Agent.from_config(
"strands_agent",
adapter=adapter,
ws_url=ws_url,
rest_url=rest_url,
)

logger.info("Starting Strands agent...")
await agent.run()


if __name__ == "__main__":
asyncio.run(main())
81 changes: 81 additions & 0 deletions examples/strands/02_custom_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# /// script
# requires-python = ">=3.11"
# dependencies = ["band-sdk[strands]"]
#
# [tool.uv.sources]
# band-sdk = { git = "https://github.com/band-ai/band-sdk-python.git" }
# ///
"""
Strands agent with custom tools and capabilities.

Shows the portable ``CustomToolDef`` (InputModel, handler) form shared across
adapters, plus enabling memory/contact tools and execution/usage event
emission via AdapterFeatures.

Run with:
uv run examples/strands/02_custom_tools.py
"""

from __future__ import annotations

import asyncio
import logging
import os

from dotenv import load_dotenv
from pydantic import BaseModel
from strands.models.openai import OpenAIModel

from setup_logging import setup_logging
from band import Agent
from band.adapters import StrandsAdapter
from band.core.types import AdapterFeatures, Capability, Emit

setup_logging()
logger = logging.getLogger(__name__)


class WeatherInput(BaseModel):
"""Get the weather for a city."""

city: str


async def get_weather(args: WeatherInput) -> str:
return f"{args.city}: sunny, 22°C"


async def main() -> None:
load_dotenv()

ws_url = os.getenv("BAND_WS_URL")
rest_url = os.getenv("BAND_REST_URL")

if not ws_url:
raise ValueError("BAND_WS_URL environment variable is required")
if not rest_url:
raise ValueError("BAND_REST_URL environment variable is required")

adapter = StrandsAdapter(
model=OpenAIModel(model_id="gpt-5.4-mini"),
custom_section="You can check the weather with the weather tool.",
additional_tools=[(WeatherInput, get_weather)], # CustomToolDef tuple
features=AdapterFeatures(
emit=frozenset({Emit.EXECUTION, Emit.USAGE}),
capabilities=frozenset({Capability.MEMORY, Capability.CONTACTS}),
),
)

agent = Agent.from_config(
"strands_agent",
adapter=adapter,
ws_url=ws_url,
rest_url=rest_url,
)

logger.info("Starting Strands agent with custom tools...")
await agent.run()


if __name__ == "__main__":
asyncio.run(main())
88 changes: 88 additions & 0 deletions examples/strands/03_custom_instructions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# /// script
# requires-python = ">=3.11"
# dependencies = ["band-sdk[strands]"]
#
# [tool.uv.sources]
# band-sdk = { git = "https://github.com/band-ai/band-sdk-python.git" }
# ///
"""
Strands agent with a fully custom system prompt.

Two levers shape the prompt:

* ``custom_section`` (see 01_basic_agent.py) is appended to the prompt the SDK
renders, so the Band tool contract stays in place;
* ``system_prompt`` REPLACES that rendered prompt entirely — nothing about the
platform tools is injected for you, so the prompt below states the messaging
contract itself. Without it the model answers in plain text, the reply never
reaches the room, and the adapter reports a dropped-reply error.

Run with:
uv run examples/strands/03_custom_instructions.py
"""

from __future__ import annotations

import asyncio
import logging
import os

from dotenv import load_dotenv
from strands.models.openai import OpenAIModel

from setup_logging import setup_logging
from band import Agent
from band.adapters import StrandsAdapter
from band.core.types import AdapterFeatures, Emit

setup_logging()
logger = logging.getLogger(__name__)

SUPPORT_PROMPT = """
You are a technical support agent for a software company, working inside a Band
chat room.

How to reply:
- Every reply to the room MUST go through the band_send_message tool, mentioning
the person you are answering. Plain text answers never reach the room.
- Send exactly one message per turn.

Guidelines:
- Ask for the environment (OS, version, exact error) before troubleshooting.
- Give numbered, verifiable steps.
- Escalate to a human when the issue needs account or billing access.
"""


async def main() -> None:
load_dotenv()

ws_url = os.getenv("BAND_WS_URL")
rest_url = os.getenv("BAND_REST_URL")

if not ws_url:
raise ValueError("BAND_WS_URL environment variable is required")
if not rest_url:
raise ValueError("BAND_REST_URL environment variable is required")

adapter = StrandsAdapter(
model=OpenAIModel(model_id="gpt-5.4-mini"),
# Full override: custom_section would be ignored alongside this.
system_prompt=SUPPORT_PROMPT,
# Post each tool call and result into the room for visibility.
features=AdapterFeatures(emit={Emit.EXECUTION}),
)

agent = Agent.from_config(
"strands_agent",
adapter=adapter,
ws_url=ws_url,
rest_url=rest_url,
)

logger.info("Starting Strands support agent...")
await agent.run()


if __name__ == "__main__":
asyncio.run(main())
Loading
Loading