Use when building or modifying a Python app that uses the agent-squad Python package — async multi-agent orchestration for Python 3.11+: orchestrator, agents (BedrockLLMAgent, AnthropicAgent, OpenAIAgent, SupervisorAgent, GroundedAgent, ChainAgent, and more), classifier routing (Bedrock, Anthropic, OpenAI), storage (in-memory, DynamoDB, SQL/Turso), retrievers (Amazon KB, Dakera), tools (AgentTools, MCPToolProvider), and custom implementations.
Scanned 8/30/2026
Install to Claude Code
npx -y skills add 2FastLabs/agent-squad --skill python --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Python?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/2fastlabs-python)More formats (shields.io, HTML) on the badges page.
---
name: agent-squad-python
description: >-
Use when building or modifying a Python app that uses the agent-squad Python package — async
multi-agent orchestration for Python 3.11+: orchestrator, agents (BedrockLLMAgent,
AnthropicAgent, OpenAIAgent, SupervisorAgent, GroundedAgent, ChainAgent, and more), classifier
routing (Bedrock, Anthropic, OpenAI), storage (in-memory, DynamoDB, SQL/Turso), retrievers
(Amazon KB, Dakera), tools (AgentTools, MCPToolProvider), and custom implementations.
---
# agent-squad Python — assistant guide
Async-first, dependency-optional multi-agent orchestration framework (Python 3.11+). This is a
guide and a map — **not an API reference**. Read exact signatures from the source
(`python/src/agent_squad/`) and the docs site (`docs/src/content/docs/`); this file tells you
*what to use, when, and what to watch out for*.
## When to use what
- **One assistant** — a single `Agent` subclass; no orchestrator needed, call `process_request`
directly.
- **Several specialists** — multiple agents + an `AgentSquad` orchestrator; the classifier routes
each turn to the right agent automatically.
- **Answers must not drift from data** (prices, balances, live stock) — `GroundedAgent`: a gatherer
LLM calls tools and sees raw results but never speaks to the user; an isolated presenter LLM
writes the reply grounded only on curated facts.
- **Fixed pipeline** — `ChainAgent`: routes the output of one agent as the input to the next,
sequentially.
- **Team coordination** — `SupervisorAgent`: a lead `BedrockLLMAgent` or `AnthropicAgent` delegates
to a team of sub-agents via an internal tool loop, maintaining shared context. Can itself be
registered in an `AgentSquad`.
- **External tool servers** — `MCPToolProvider` (requires `agent-squad[mcp]`) connects any number
of MCP servers (stdio or SSE) and makes their tools available to any agent.
## How to install
All third-party integrations are optional extras — never forced on users who don't need them.
```bash
pip install agent-squad # core only (no LLM runtime)
pip install "agent-squad[aws]" # + boto3 — BedrockLLMAgent, BedrockClassifier, DynamoDbChatStorage, etc.
pip install "agent-squad[anthropic]" # + anthropic SDK — AnthropicAgent, AnthropicClassifier
pip install "agent-squad[openai]" # + openai SDK — OpenAIAgent, OpenAIClassifier
pip install "agent-squad[sql]" # + libsql-client — SqlChatStorage (Turso/libSQL)
pip install "agent-squad[strands-agents]"# + strands-agents — StrandsAgent
pip install "agent-squad[dakera]" # + dakera — DakeraRetriever
pip install "agent-squad[mcp]" # + mcp — MCPToolProvider
pip install "agent-squad[all]" # everything except strands-agents
```
## How a turn works
`AgentSquad.route_request` is the one entry point worth memorising. It is a coroutine — you must
`await` it.
```python
import asyncio
from agent_squad.orchestrator import AgentSquad
from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions
from agent_squad.classifiers import BedrockClassifier, BedrockClassifierOptions
orchestrator = AgentSquad(
classifier=BedrockClassifier(BedrockClassifierOptions())
)
orchestrator.add_agent(BedrockLLMAgent(BedrockLLMAgentOptions(
name="General Assistant",
description="Handles general knowledge questions",
)))
async def main():
response = await orchestrator.route_request(
user_input="What is the capital of France?",
user_id="user-123",
session_id="session-abc",
)
if response.streaming:
# response.output is an async generator of AgentStreamResponse
async for chunk in response.output:
if chunk.text:
print(chunk.text, end="", flush=True)
if chunk.final_message:
pass # full ConversationMessage — already persisted
else:
# response.output is a ConversationMessage
print(response.output.content[0]["text"])
asyncio.run(main())
```
`AgentResponse` has three fields: `metadata` (`AgentProcessingResult`), `output`, and `streaming`
(bool). Always branch on `response.streaming` — the type of `output` differs.
To stream back from `route_request`, pass `stream_response=True`:
```python
response = await orchestrator.route_request(
user_input="...",
user_id="u1",
session_id="s1",
stream_response=True,
)
```
If no agent is selected and no default agent is configured, `route_request` returns an
`AgentResponse` with the `NO_SELECTED_AGENT_MESSAGE` text rather than raising.
## The pieces
### AgentSquad (orchestrator)
`from agent_squad.orchestrator import AgentSquad`
The top-level object. Holds an agent registry, a classifier, and a `ChatStorage`.
```python
from agent_squad.types import AgentSquadConfig
orchestrator = AgentSquad(
options=AgentSquadConfig(
LOG_CLASSIFIER_OUTPUT=True,
MAX_MESSAGE_PAIRS_PER_AGENT=20,
USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED=True,
),
storage=my_storage, # default: InMemoryChatStorage
classifier=my_classifier, # default: BedrockClassifier (if boto3 installed)
default_agent=fallback, # used when classifier returns no match
)
orchestrator.add_agent(agent)
```
`AgentSquadConfig` fields: `LOG_AGENT_CHAT`, `LOG_CLASSIFIER_CHAT`, `LOG_CLASSIFIER_RAW_OUTPUT`,
`LOG_CLASSIFIER_OUTPUT`, `LOG_EXECUTION_TIMES`, `MAX_RETRIES`, `USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED`,
`NO_SELECTED_AGENT_MESSAGE`, `GENERAL_ROUTING_ERROR_MSG_MESSAGE`, `MAX_MESSAGE_PAIRS_PER_AGENT`.
You can also call `classify_request` and `agent_process_request` separately if you need to inspect
the routing decision before dispatching.
### Agents
All agents require `agent-squad[aws]`, `[anthropic]`, or `[openai]` depending on the underlying
SDK. The base `Agent` and `AgentOptions` plus `SupervisorAgent` and `GroundedAgent` are always
available with the core install.
| Agent | Extra needed | Notes |
|---|---|---|
| `BedrockLLMAgent` | `aws` | Bedrock Converse API; supports streaming, tools, retriever |
| `AmazonBedrockAgent` | `aws` | Bedrock Agents runtime (managed agents with KB/action groups) |
| `BedrockInlineAgent` | `aws` | Bedrock inline agents — code interpretation, KB, and tools inline |
| `BedrockFlowsAgent` | `aws` | Bedrock Flows — runs a preconfigured flow |
| `BedrockTranslatorAgent` | `aws` | Bedrock translation agent |
| `LambdaAgent` | `aws` | Invokes an AWS Lambda function |
| `LexBotAgent` | `aws` | Routes to an Amazon Lex bot |
| `ComprehendFilterAgent` | `aws` | Comprehend PII/toxicity filter before passing to another agent |
| `ChainAgent` | `aws` | Sequential pipeline — each agent's output feeds the next |
| `AnthropicAgent` | `anthropic` | Anthropic Messages API; supports streaming and tools |
| `OpenAIAgent` | `openai` | OpenAI Chat Completions API; supports streaming and tools |
| `StrandsAgent` | `strands-agents` | Strands Agents integration |
| `SupervisorAgent` | `aws` or `anthropic` | Lead agent coordinates a team via tools; always available in `__init__.py` but requires a compatible `lead_agent` |
| `GroundedAgent` | same as gatherer/presenter | Two-LLM anti-hallucination pattern; always importable |
All agents extend `Agent` (`agent_squad.agents.agent`). The `AgentOptions` dataclass is the
construction pattern — every concrete agent has a matching `*Options` dataclass that extends it:
```python
from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions
from agent_squad.utils import AgentTools, AgentTool
agent = BedrockLLMAgent(BedrockLLMAgentOptions(
name="My Agent",
description="Handles X", # shown to the classifier
model_id="anthropic.claude-3-5-sonnet-20240620-v1:0",
streaming=True,
save_chat=True, # default True — persists history
tool_config={"tool": my_tools}, # AgentTools instance
retriever=my_retriever,
LOG_AGENT_DEBUG_TRACE=False,
))
```
**`GroundedAgent`** takes a `gatherer`, `presenter`, `tools`, optional `curator`
(`ToolOutputCurator`), and optional `presenter_prompt` (`PresenterPrompt`). The gatherer runs the
tool loop; the presenter receives only the curated data and never sees the chat history or the
gatherer's transcript.
**`SupervisorAgent`** takes a `lead_agent` (must be `BedrockLLMAgent` or `AnthropicAgent`), a
`team` list, optional `storage`, and optional `extra_tools`. The lead agent must not have its own
`tool_config` — tools are managed internally. Use `extra_tools` for additional tools beyond the
team-dispatch tools.
**`ChainAgent`** (requires `aws`) takes an `agents` list. Each agent's text output becomes the next
agent's input.
### Classifiers
`from agent_squad.classifiers import BedrockClassifier, AnthropicClassifier, OpenAIClassifier`
| Classifier | Extra needed |
|---|---|
| `BedrockClassifier` | `aws` |
| `AnthropicClassifier` | `anthropic` |
| `OpenAIClassifier` | `openai` |
The orchestrator defaults to `BedrockClassifier` if `boto3` is installed and no classifier is
provided. If boto3 is not installed and no classifier is passed, the orchestrator raises
`ValueError` at construction time.
All classifiers extend `Classifier` (`agent_squad.classifiers.classifier`). You can override the
routing prompt via `set_system_prompt(template, variables)` where `{{AGENT_DESCRIPTIONS}}` and
`{{HISTORY}}` are the built-in template placeholders.
### Storage
`from agent_squad.storage import InMemoryChatStorage, DynamoDbChatStorage, SqlChatStorage`
| Storage | Extra needed | Notes |
|---|---|---|
| `InMemoryChatStorage` | none | Default; not persistent |
| `DynamoDbChatStorage` | `aws` | DynamoDB-backed; production default for AWS deployments |
| `SqlChatStorage` | `sql` | libSQL/Turso-backed |
| `SummarizingChatStorage` | none | Wraps any storage; compresses history via a user-supplied async callable when history exceeds `trigger_at` pairs |
All storage classes extend `ChatStorage` (`agent_squad.storage.chat_storage`). Storage is keyed by
`(user_id, session_id, agent_id)`. `MAX_MESSAGE_PAIRS_PER_AGENT` (default 100) trims history at
write time. `save_chat=False` on an agent disables history for that agent only.
### Retrievers
`from agent_squad.retrievers import AmazonKnowledgeBasesRetriever, DakeraRetriever`
| Retriever | Extra needed | Notes |
|---|---|---|
| `AmazonKnowledgeBasesRetriever` | `aws` | Amazon Bedrock Knowledge Bases |
| `DakeraRetriever` | `dakera` | Self-hosted Dakera memory server |
All retrievers extend `Retriever` (`agent_squad.retrievers.retriever`). A retriever attached to an
agent augments its prompt with retrieved context before the LLM call.
`DakeraRetriever` reads `DAKERA_API_KEY` and `DAKERA_URL` from environment variables if not
provided in `DakeraRetrieverOptions`.
### Tools
**`AgentTools` / `AgentTool`** — the native tool system, always available:
```python
from agent_squad.utils import AgentTools, AgentTool
def get_weather(city: str) -> str:
"""Get the weather for a city.
:param city: The city name.
"""
return f"Sunny in {city}"
tools = AgentTools(tools=[
AgentTool(name="get_weather", func=get_weather)
])
# AgentTool auto-extracts properties from type hints and :param docstrings.
```
`AgentTool` wraps both sync and async functions transparently. Properties, descriptions, and
required fields can be overridden explicitly. `AgentTools.tool_handler` processes tool call
responses for Bedrock and Anthropic wire formats.
**`MCPToolProvider`** — drop-in `AgentTools` subclass for MCP servers (requires `agent-squad[mcp]`):
```python
from agent_squad.tools import MCPToolProvider, MCPServerConfig
provider = await MCPToolProvider.create([
MCPServerConfig(type="stdio", command="uvx", args=["my-mcp-server"]),
MCPServerConfig(type="sse", url="http://localhost:3000/sse"),
])
agent = BedrockLLMAgent(BedrockLLMAgentOptions(
name="MCP Agent",
description="...",
tool_config={"tool": provider},
))
# Clean up when done:
await provider.disconnect()
```
`MCPToolProvider.create` is an async factory — it connects to all servers and fetches tool
definitions upfront so they are available synchronously when the agent builds its API request.
### Callbacks
`AgentCallbacks` (on `AgentOptions`) hooks into the agent lifecycle:
- `on_agent_start` — returns a dict (tracking info) available to other callbacks via `kwargs`
- `on_agent_end`
- `on_llm_start`
- `on_llm_new_token`
- `on_llm_end`
`AgentToolCallbacks` (on `AgentTools`) hooks into tool execution:
- `on_tool_start`
- `on_tool_end`
- `on_tool_error`
`ClassifierCallbacks` hooks into the classifier:
- `on_classifier_start`
- `on_classifier_stop`
## Custom implementations
Subclass the abstract base and pass your instance where the built-in goes. Source paths are
under `python/src/agent_squad/`.
| Seam | Base class | Source file |
|---|---|---|
| Agent | `Agent` | `agents/agent.py` |
| Classifier | `Classifier` | `classifiers/classifier.py` |
| Storage | `ChatStorage` | `storage/chat_storage.py` |
| Retriever | `Retriever` | `retrievers/retriever.py` |
| Tool curator | `ToolOutputCurator` | `agents/grounded_agent.py` |
| Presenter prompt | `PresenterPrompt` | `agents/grounded_agent.py` |
Minimal custom agent:
```python
from typing import Optional, AsyncIterable, Union
from agent_squad.agents import Agent, AgentOptions
from agent_squad.types import ConversationMessage, ParticipantRole
class MyAgent(Agent):
def __init__(self, options: AgentOptions):
super().__init__(options)
async def process_request(
self,
input_text: str,
user_id: str,
session_id: str,
chat_history: list[ConversationMessage],
additional_params: Optional[dict] = None,
) -> Union[ConversationMessage, AsyncIterable]:
return ConversationMessage(
role=ParticipantRole.ASSISTANT.value,
content=[{"text": f"Echo: {input_text}"}],
)
```
For a streaming custom agent, also override `is_streaming_enabled` to return `True` and yield
`AgentStreamResponse` objects (set `final_message` on the last one).
## Gotchas
- **All public methods are async.** `route_request`, `process_request`, `classify`, storage methods,
and retriever methods are all coroutines. You must `await` them inside an async context. Use
`asyncio.run(main())` at the top level.
- **Optional imports at module level, not inside methods.** The framework guards all optional
integrations with `try/except ImportError` at the top of each module. If you copy this pattern
for your own extensions, keep the guard at module level — never inside `__init__` or a method.
- **Classifier is required.** Unlike the Swift version, the Python `AgentSquad` raises at
construction time if no classifier can be resolved (no `boto3` installed and no classifier
passed). Always pass a `classifier` explicitly when `boto3` is not available.
- **`response.streaming` and `response.output` type are coupled.** When `streaming=True`, `output`
is an async generator; when `False`, it is a `ConversationMessage`. Always branch on
`response.streaming` before consuming `output`.
- **`stream_response=False` by default.** Even if the agent itself streams internally, the
orchestrator will drain the stream and return a single `ConversationMessage` unless you pass
`stream_response=True` to `route_request`.
- **Agent `id` is derived from `name`** via `generate_key_from_name`: lowercased, spaces replaced
with hyphens, special characters stripped. `agent.id` is the storage key — two agents with names
that normalise to the same string will collide. Pick distinct names.
- **`AgentOptions` is a dataclass; new fields must have defaults.** When subclassing (e.g.
`BedrockLLMAgentOptions`), add new fields with defaults so existing construction call-sites
keep working.
- **`SupervisorAgent` name and description come from the lead agent.** Whatever you set on
`SupervisorAgentOptions.name` / `.description` is overwritten by `lead_agent.name` /
`lead_agent.description` at construction time.
- **`SupervisorAgent` forbids `tool_config` on the lead agent.** The supervisor manages tools
internally. Use `extra_tools` for any additional tools beyond team dispatch.
- **`GroundedAgent` presenter isolation is strict.** The presenter never sees chat history or the
gatherer's transcript — only the curated data produced by the `ToolOutputCurator`. A chit-chat
turn that calls no tools is answered by the gatherer alone (presenter is skipped).
- **`MCPToolProvider.create` is async.** It must be `await`ed before building the agent. Call
`await provider.disconnect()` when done to close server connections.
- **`save_chat=True` by default.** Every agent persists both sides of each exchange unless
explicitly set to `False`. Storage is scoped per `(user_id, session_id, agent_id)`. The
`MAX_MESSAGE_PAIRS_PER_AGENT` config trims at save time, not at fetch time.
- **`ConversationMessage.content` is a list of dicts**, not a plain string. Text is at
`content[0]["text"]` for most agents. Some agents may produce multi-block content (tool use,
images). Do not assume `content` has a single element.
- **`DakeraRetriever` and `AmazonKnowledgeBasesRetriever` are not import-guarded** — they are
always exported from `agent_squad.retrievers`. If the underlying SDK (`dakera`, `boto3`) is not
installed, the import will fail at runtime when you try to construct them.
## Go deeper
- **Prose and recipes** — the Starlight docs under `docs/src/content/docs/` (run with
`npm run dev` from `docs/`): `get-started/`, `agents/`, `classifiers/`, `storage/`, `retrievers/`,
`tools/`.
- **Exact signatures** — `python/src/agent_squad/`:
- `orchestrator.py` — `AgentSquad`, `route_request`, `classify_request`, `agent_process_request`
- `agents/agent.py` — `Agent`, `AgentOptions`, `AgentCallbacks`, `AgentResponse`, `AgentStreamResponse`
- `agents/grounded_agent.py` — `GroundedAgent`, `GroundedAgentOptions`, `ToolOutputCurator`, `DataBlockCurator`, `PerToolCurator`, `PresenterPrompt`, `CapturedToolResult`
- `agents/supervisor_agent.py` — `SupervisorAgent`, `SupervisorAgentOptions`
- `agents/chain_agent.py` — `ChainAgent`, `ChainAgentOptions`
- `classifiers/classifier.py` — `Classifier`, `ClassifierResult`, `ClassifierCallbacks`
- `storage/chat_storage.py` — `ChatStorage`
- `retrievers/retriever.py` — `Retriever`
- `utils/tool.py` — `AgentTools`, `AgentTool`, `AgentToolCallbacks`, `AgentToolResult`
- `tools/mcp_tool_provider.py` — `MCPToolProvider`, `MCPServerConfig`
- `types/types.py` — `ConversationMessage`, `ParticipantRole`, `AgentSquadConfig`, `TimestampedMessage`
- **Tests** — `python/src/tests/` — pytest; run from `python/` with `make test`.
- **Optional extras** — `python/setup.cfg` — `[options.extras_require]`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!