A multi-agent teams implementation using LangGraph (Blackboard & Mailbox architecture)
Project description
๐ค LangGraph Agent Teams
A Python library for building agent teams with LangGraph, inspired by the Claude Code Agent Teams feature. One agent acts as the team lead โ coordinating work, assigning tasks, and synthesizing results โ while teammates work independently, each in its own context window, claiming tasks from a shared task list and messaging each other through mailboxes.
Unlike supervisor/subagent patterns where workers only report back to a caller, agent teams coordinate through two shared primitives:
- Task board โ a shared list of work items with dependencies (
blocks/blocked_by). The lead creates tasks; teammates claim and complete them. Claiming uses file locking, so concurrent teammates never grab the same task. - Mailbox โ asynchronous, direct agent-to-agent messaging. Any member can message any other member by name โ teammate-to-teammate debate is a first-class capability, not just teammate-to-lead reporting. Idle notifications flow to the lead automatically, and the team shuts down gracefully via a shutdown request/response protocol.
As in Claude Code, the coordination tools (SendMessage plus the task tools) are always available to every team member โ define members with TeamMemberSpec and the package attaches them automatically.
All coordination state (task board, mailboxes, roster) is persisted as JSON files on disk โ you can cat a mailbox or ls the task list at any time while the team is running, and state survives crashes.
Features
- ๐ Lead / teammate architecture โ a coordinator agent delegates to independent worker agents
- ๐ Shared task board โ dependency-aware tasks with atomic, lock-protected claiming
- ๐ฌ Inter-agent mailboxes โ every member can message every other member (or broadcast), with idle notifications and graceful shutdown
- ๐งฐ Coordination tools always on โ
TeamMemberSpecmembers automatically getSendMessage+ task tools, like Claude Code teammates - โก True parallelism โ teammates run concurrently as async LangGraph nodes
- ๐๏ธ File-system-as-a-bus โ inspect and debug the team's state live on disk
- ๐ ๏ธ LangChain tools included โ
SendMessage,TaskCreate,TaskUpdate,TaskList,TaskGet - ๐ช Lifecycle hooks โ validate task creation, gate task completion behind quality checks, and keep idle teammates working (like Claude Code's
TaskCreated/TaskCompleted/TeammateIdlehooks) - ๐ Plan approval โ require a teammate to get its plan approved by the lead before executing each task
- ๐ Session resumption โ run with
fresh=Falseto resume an interrupted team: completed tasks stay done, interrupted tasks are requeued
When to Use Agent Teams
Agent teams shine when parallel exploration adds real value:
- Research and review โ teammates investigate different aspects of a problem simultaneously
- New modules or features โ each teammate owns a separate piece without stepping on the others
- Competing hypotheses โ teammates test different theories in parallel and converge faster
- Cross-layer work โ frontend, backend, and tests each owned by a different teammate
For sequential tasks or work with many dependencies, a single agent or a supervisor/subagent pattern is more effective โ teams add coordination overhead and use more tokens.
Installation
pip install langgraph-agent-teams
Quickstart
pip install langgraph-agent-teams langchain-openai
export OPENAI_API_KEY=<your_api_key>
import asyncio
from langgraph_agent_teams import TeamMemberSpec, create_agent_team
# Every member automatically gets the coordination tools:
# SendMessage, TaskCreate, TaskUpdate, TaskList, TaskGet
lead = TeamMemberSpec(
name="team-lead",
model="openai:gpt-5.5",
system_prompt="You are a team lead. Delegate work to your teammates.",
)
researcher = TeamMemberSpec(
name="researcher",
model="openai:gpt-5.5",
system_prompt="You are a thorough researcher.",
agent_type="researcher",
)
critic = TeamMemberSpec(
name="critic",
model="openai:gpt-5.5",
system_prompt="You are a devil's advocate who challenges findings.",
agent_type="critic",
)
app = create_agent_team(
lead, [researcher, critic], team_name="research-team"
).compile()
result = asyncio.run(
app.ainvoke(
{
"messages": [
{
"role": "user",
"content": "Evaluate whether we should migrate from REST to GraphQL. "
"Have one teammate research the benefits and another challenge them.",
}
]
}
)
)
print(result["messages"][-1].content)
[!IMPORTANT] Agent teams rely on asyncio concurrency โ the lead and all teammates run at the same time. Always invoke the compiled graph with
await app.ainvoke(...)(orasyncio.run(...)); synchronousinvokeis not supported.
How It Works
โโโโโโโโโโโโโโโ
user โโโโโบ โ team lead โ โโโโโ idle notifications, reports, replies
โโโโโโโโฌโโโโโโโ
TaskCreate / โ SendMessage
TaskUpdate โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ shared task board โ {base_dir}/tasks/{team}/
โ 1.json 2.json .lock ... โ
โโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโ
โ claim (locked) โ claim (locked)
โโโโโโโโผโโโโโโโ โโโโโโโโผโโโโโโโ
โ teammate A โ โ teammate B โ each with its own context window
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โฒ โฒ
โโโโโโ mailboxes โโ {base_dir}/teams/{team}/inboxes/
- A
setupnode resets the team's on-disk state and registers every member in the roster. - The lead node runs the lead agent with a coordination briefing. The lead breaks the request into tasks (
TaskCreate), optionally assigns them (TaskUpdatewithowner), and messages teammates (SendMessage). - Each teammate node runs a poll loop: read unread mail โ claim the next available task โ run its own agent turn โ mark the task complete โ report the result and an idle notification back to the lead.
- Teammate messages are delivered to the lead automatically; the lead responds, reassigns, or creates follow-up work.
- When every task is complete and all teammates are idle, the lead broadcasts a shutdown request; each teammate acknowledges with a shutdown response and exits. The lead's final message synthesizes the team's results.
Task dependencies
Tasks can depend on each other. A pending task with unresolved dependencies cannot be claimed until those dependencies are completed โ blocked tasks unblock automatically:
# Inside the lead's reasoning, via the TaskCreate / TaskUpdate tools:
# TaskCreate(subject="Research API options", description="...") -> task 1
# TaskCreate(subject="Write recommendation", description="...", blocked_by=["1"]) -> task 2
Lead-assigned vs. self-claimed tasks
- Lead assigns: the lead sets
owneron a task withTaskUpdateโ only that teammate can claim it. - Self-claim: unassigned tasks are claimed by idle teammates in ID order.
Teammate-to-teammate communication
Every TeamMemberSpec member can message any other member by name with SendMessage โ the classic "teammates debate each other" pattern from the Claude Code docs works out of the box:
result = await app.ainvoke({"messages": [{
"role": "user",
"content": "Investigate why the app disconnects after one message. "
"Have teammates pursue different hypotheses and message each other "
"to try to disprove each other's theories, like a scientific debate.",
}]})
Communication is agent-driven with a harness fallback: if a teammate finishes a turn without calling SendMessage, its final reply is forwarded automatically โ task reports go to the lead, message replies go back to the sender โ so no work is silently lost even with models that under-use tools. Fallback replies are marked internally and never trigger another automatic reply, so two teammates can't ping-pong forever.
Lifecycle hooks
Mirror Claude Code's team hooks with plain Python callables. Each hook returns None to allow the event or a feedback string to reject it โ the feedback is delivered to the acting agent so it can adjust:
from langgraph_agent_teams import TeamHooks, create_agent_team
def quality_gate(task):
# e.g. run tests, check the task's metadata, inspect files...
if "test" not in task.description.lower():
return "Completion rejected: describe how you verified the work."
return None # allow completion
hooks = TeamHooks(
on_task_created=lambda task: None, # validate new tasks
on_task_completed=quality_gate, # gate completion (quality gate)
on_teammate_idle=lambda name: None, # keep an idle teammate working
)
app = create_agent_team(lead, teammates, team_name="research-team", hooks=hooks).compile()
on_task_created(task)โ rejecting deletes the task and returns the feedback to the creating agent.on_task_completed(task)โ rejecting keeps the taskin_progressand sends the feedback to the working teammate, which keeps going until the gate passes.on_teammate_idle(name)โ returning feedback gives the teammate another turn instead of letting it go idle.
Plan approval
Require a teammate to plan before acting โ the equivalent of Claude Code's plan approval for teammates. Set require_plan_approval=True on the spec; before executing each task the teammate submits a plan to the lead, and the lead approves or rejects it (with feedback) using the automatically attached ApprovePlan tool:
coder = TeamMemberSpec(
name="coder",
model="openai:gpt-5.5",
system_prompt="You write production code.",
require_plan_approval=True, # plan first, execute after lead approval
)
Rejected plans are revised and resubmitted until approved. If the lead doesn't respond within plan_approval_timeout seconds (default 120), the teammate proceeds so the team never deadlocks.
Resuming an interrupted team
Because the task board lives on disk, a crashed or interrupted run can be resumed. Pass fresh=False and the board is kept: completed tasks stay completed, tasks that were in_progress are requeued, and teammates pick up where the previous run stopped:
app = create_agent_team(
lead, teammates, team_name="research-team", fresh=False
).compile()
result = await app.ainvoke({"messages": [{"role": "user", "content": "Continue the work."}]})
The default (fresh=True) wipes the team's state at the start of every run.
Using prebuilt agents (advanced)
You can also pass prebuilt agents (e.g. from langchain.agents.create_agent) instead of specs โ useful when you need middleware or custom agent construction. In that case, attach the coordination tools yourself so the agent can communicate:
from langchain.agents import create_agent
from langgraph_agent_teams import create_team_tools
coder = create_agent(
model="openai:gpt-5.5",
tools=[*my_tools, *create_team_tools("coder", team_name="research-team")],
name="coder",
)
Prebuilt agents without team tools still work โ the harness claims tasks, completes them, and forwards their replies for them.
Inspecting a Running Team
Team state lives under ~/.langgraph-agent-teams by default (override with base_dir):
~/.langgraph-agent-teams/
โโโ teams/{team_name}/
โ โโโ config.json # Roster: members, roles, live status
โ โโโ inboxes/
โ โโโ team-lead.json # The lead's mailbox
โ โโโ researcher.json # A teammate's mailbox
โโโ tasks/{team_name}/
โโโ .lock # Claim lock
โโโ .highwatermark # Highest task ID ever created
โโโ 1.json # Task #1
# Watch the task board while the team works
watch -n1 'cat ~/.langgraph-agent-teams/tasks/research-team/*.json | jq -r "\(.id) [\(.status)] \(.owner // \"unassigned\") โ \(.subject)"'
You can also use the store classes directly:
from langgraph_agent_teams import get_team_resources
resources = get_team_resources("research-team")
for task in resources.task_board().list_tasks():
print(task.id, task.status, task.owner, task.subject)
for message in resources.mailbox().read_all("team-lead"):
print(message.from_agent, "->", message.summary or message.text[:80])
API Reference
TeamMemberSpec(name, model, system_prompt=None, tools=[], agent_type=None, require_plan_approval=False)
Declarative definition of one team member. The package builds the agent with langchain.agents.create_agent and automatically attaches the five coordination tools bound to the member's identity. tools adds extra domain tools on top; agent_type is a role label shown in the roster and briefings; require_plan_approval makes the teammate submit a plan to the lead before executing each task.
create_agent_team(lead, teammates, team_name, base_dir=None, *, fresh=True, hooks=None, poll_interval=0.5, shutdown_timeout=60.0, plan_approval_timeout=120.0, max_agent_turns=100)
Builds the team StateGraph. The lead and every teammate run as concurrent async nodes; compile and ainvoke it like any LangGraph graph.
| Parameter | Description |
|---|---|
lead |
TeamMemberSpec (recommended) or a prebuilt agent equipped with create_team_tools. |
teammates |
TeamMemberSpecs (recommended) or prebuilt agents, each with a unique name. |
team_name |
Unique team name; also names the on-disk state directories. |
base_dir |
Root directory for team state (default ~/.langgraph-agent-teams). |
fresh |
True (default) wipes team state at the start; False resumes the existing task board, requeuing interrupted tasks. |
hooks |
Optional TeamHooks lifecycle callbacks (see above). |
poll_interval |
Seconds between mailbox/task-board polls. |
shutdown_timeout |
Max seconds the lead waits for shutdown acknowledgements. |
plan_approval_timeout |
Max seconds a teammate waits for a plan verdict before proceeding. |
max_agent_turns |
Safety cap on agent invocations per member per run. |
TeamHooks(on_task_created=None, on_task_completed=None, on_teammate_idle=None)
Lifecycle callbacks, mirroring Claude Code's team hooks. Each is a plain callable returning None to allow the event or a feedback string to reject it (see Lifecycle hooks).
create_team_tools(agent_name, team_name, base_dir=None, hooks=None)
Returns the five coordination tools bound to one agent's identity:
| Tool | Purpose |
|---|---|
SendMessage |
DM another agent by name, or "*" to broadcast |
TaskCreate |
Add a task to the shared board (optionally blocked_by) |
TaskUpdate |
Change status/owner/description, add dependency edges, or set status deleted to remove a task |
TaskList |
List every task with status, owner, and blockers |
TaskGet |
Fetch one task's full details |
create_plan_approval_tool(agent_name, team_name, base_dir=None)
Returns the ApprovePlan tool the lead uses to answer plan approval requests. Attached automatically to a spec-built lead when any teammate has require_plan_approval=True; attach it manually to a prebuilt lead.
Store classes
SwarmTaskBoard, AgentMailbox, and TeamRoster are exported for direct use โ every operation is atomic and protected by portalocker file locks, so they're safe across threads, asyncio tasks, and even separate processes.
Comparing Agent Teams with Other Multi-Agent Patterns
| Pattern | Coordination | Communication | Best for |
|---|---|---|---|
| Agent Teams | Shared task list + lead | Teammates message each other directly | Parallel work requiring discussion and collaboration |
| Group Chat | Centralized selector | Broadcast (shared history) | Moderated turn-taking discussions |
| Swarm | Decentralized handoffs | Control transfer via tools | Fluid handoffs, direct user interaction |
| Subagents | Main agent delegates | Results report back to caller only | Focused tasks where only the result matters |
Use subagents when you need quick, focused workers that report back. Use agent teams when workers benefit from a shared work queue, independent context windows, and inter-agent communication.
Best Practices
- Give teammates enough context โ teammates don't see the lead's conversation history, so the lead's task descriptions must be self-contained (the built-in briefing tells it so)
- Size tasks appropriately โ self-contained units with a clear deliverable; 5โ6 tasks per teammate keeps everyone productive
- Start with 3โ5 teammates โ token costs scale linearly with team size, and coordination overhead grows with it
- Avoid file conflicts โ if teammates edit files, break work so each owns a different set
- Use dependencies for ordering โ
blocked_byedges let the board sequence work without the lead micromanaging
Examples
See the examples/ directory:
software_teamโ a lead coordinating researcher/coder/reviewer teammates
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
MIT License โ see LICENSE for details.
Acknowledgments
- Inspired by the Claude Code Agent Teams feature and its blackboard/mailbox architecture
- Built on LangGraph and LangChain agents
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langgraph_agent_teams-0.2.0.tar.gz.
File metadata
- Download URL: langgraph_agent_teams-0.2.0.tar.gz
- Upload date:
- Size: 40.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c894b1484c52ac7fae6d6f3ecb8187e90e15cdc014ed67c570a97fa1b13d8a1
|
|
| MD5 |
ff7f923a66b5bb85b01e2dfffe66e1c3
|
|
| BLAKE2b-256 |
f60aaa9dd6709c2d3ab2683e904782074643660277b0d724632b0e95da3c35f1
|
File details
Details for the file langgraph_agent_teams-0.2.0-py3-none-any.whl.
File metadata
- Download URL: langgraph_agent_teams-0.2.0-py3-none-any.whl
- Upload date:
- Size: 33.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8296bca3768507e7bff526baaf58ab674f5397d2b7d499beb3cf64c0694acc06
|
|
| MD5 |
93a98b9a9d7ebd59c90c85e2005d5b21
|
|
| BLAKE2b-256 |
d59285f693c64f26d0f809bbafc99905410b277444e8c08fcb4dbcbed8cad51a
|