Skip to main content

A multi-agent teams implementation using LangGraph (Blackboard & Mailbox architecture)

Project description

๐Ÿค LangGraph Agent Teams

PyPI version Python versions License: MIT PyPI downloads

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 โ€” TeamMemberSpec members automatically get SendMessage + 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 / TeammateIdle hooks)
  • ๐Ÿ“ Plan approval โ€” require a teammate to get its plan approved by the lead before executing each task
  • ๐Ÿ” Session resumption โ€” run with fresh=False to 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(...) (or asyncio.run(...)); synchronous invoke is 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/
  1. A setup node resets the team's on-disk state and registers every member in the roster.
  2. The lead node runs the lead agent with a coordination briefing. The lead breaks the request into tasks (TaskCreate), optionally assigns them (TaskUpdate with owner), and messages teammates (SendMessage).
  3. 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.
  4. Teammate messages are delivered to the lead automatically; the lead responds, reassigns, or creates follow-up work.
  5. 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 owner on a task with TaskUpdate โ€” 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 task in_progress and 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

  1. 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)
  2. Size tasks appropriately โ€” self-contained units with a clear deliverable; 5โ€“6 tasks per teammate keeps everyone productive
  3. Start with 3โ€“5 teammates โ€” token costs scale linearly with team size, and coordination overhead grows with it
  4. Avoid file conflicts โ€” if teammates edit files, break work so each owns a different set
  5. Use dependencies for ordering โ€” blocked_by edges 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

langgraph_agent_teams-0.2.0.tar.gz (40.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

langgraph_agent_teams-0.2.0-py3-none-any.whl (33.6 kB view details)

Uploaded Python 3

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

Hashes for langgraph_agent_teams-0.2.0.tar.gz
Algorithm Hash digest
SHA256 2c894b1484c52ac7fae6d6f3ecb8187e90e15cdc014ed67c570a97fa1b13d8a1
MD5 ff7f923a66b5bb85b01e2dfffe66e1c3
BLAKE2b-256 f60aaa9dd6709c2d3ab2683e904782074643660277b0d724632b0e95da3c35f1

See more details on using hashes here.

File details

Details for the file langgraph_agent_teams-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for langgraph_agent_teams-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8296bca3768507e7bff526baaf58ab674f5397d2b7d499beb3cf64c0694acc06
MD5 93a98b9a9d7ebd59c90c85e2005d5b21
BLAKE2b-256 d59285f693c64f26d0f809bbafc99905410b277444e8c08fcb4dbcbed8cad51a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page