← BlogAI Agents

Agent Teams with Claude Code and Claude Agent SDK

Isaac Kargar5 min read

  • AI Agents
  • Claude
  • SDK
  • Multi-Agent

Agent Teams with Claude Code and Claude Agent SDK

Introduction

Claude code released a new feature called Agent Teams which is an upgrade to their multi-agent capabilities in claude code. In this tutorial, we build a practical understanding of Agent Teams and then use them in two ways:

  1. Interactive mode in Claude Code
  2. Programmatic mode with Claude Agent SDK

The goal is simple. By the end, you should be able to run one lead agent that can create teammates, coordinate work, exchange messages, and clean up team resources safely.

This is not a theory-only article. It is explanation plus coding, step by step.

Why Agent Teams

Most of the frameworks started with a single agent setup which can get a task, and then do the job step be step. A single agent is enough for short, linear tasks. But sometimes the tasks are not dependent and can be done in parallel. Also, for some tasks, teh agent doesn’t need to know the in-between steps of how a sub-task is done. In these scenarios we can use subagents with fresh context window to do the job and return only the result and output to the lead agent or save it in a file, so other agents can also use it, or in this new feature in claude, to communicate it with other subagents as well. In short, a single agent system starts to struggle when work must happen in parallel, or when you need separate viewpoints that challenge each other.

Agent Teams solve that by introducing coordination roles:

  • one lead agent that orchestrates
  • multiple teammates that work independently
  • shared task and messaging channels

In real projects, this is useful for code review, feature decomposition, bug investigation with competing hypotheses, and cross-layer work where backend, frontend, and tests all move together.

The tradeoff is cost and complexity. Team runs consume more tokens and need clearer task design.

How Agent Teams Work

Before we write any code, keep this model in mind:

The lead session creates a team. Then it spawns teammates. Each teammate gets its own context window and receives a role-specific instruction. Teammates can message the lead, and can also message each other. The lead synthesizes results and decides when the run is complete.

In tool-level terms, these are the key team operations you usually see in claude traces:

  • TeamCreate
  • SendMessage
  • TeamDelete
  • and some other new tools as well.

You may also see Task used to spawn teammate work units.

The lifecycle is normally sth like what is shown below:

Agent Teams lifecycle

That last step matters. Always clean up.

Agent Teams vs Subagents

This distinction is important when you design your system. Subagents are lighter. They are great when you only need focused help and summarized results back to one caller. No communication between subagents, unless you somehow design it yourself. Like telling the subagents that there are other subagents as well working on X, Y, Z and there is a common folder that other subagents save their output ther and you can read and use from there. But this is very unclear to subagents and doesn’t feel like and online and real-time communication channel. We need sth better.

Agent Teams are heavier. They can communicate and talk to eachother. If your task can be solved by one main thread with quick helper calls or communication is needed between subagents, use the simpler subagents. If your task benefits from real parallel collaboration and debate, use teams.

Prerequisites

Use this baseline setup first.

uv --version
claude --version

Make sure your SDK is recent:

uv add "claude-agent-sdk>=0.1.34"

Enable Agent Teams:

export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1

You can also set it in Claude settings. For local scripting, env is enough.

{
  "env": {
    "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
  }
}

Part 1: Agent Teams in Claude Code

Now let’s see how can we use it in claude code. This helps you understand behavior before SDK automation.

Start Claude Code:

claude --dangerously-skip-permissions

Then use a prompt that explicitly pushes team orchestration:

You are the lead agent. Create a team and coordinate the work directly
in this session. Do NOT write a Python script or SDK code to do this.
Do the work yourself.

Put all output files in the taskrunner/ folder.

Task:
Build a Python CLI called taskrunner with commands:
- list
- add <title>
- done <id>
- stats

Team setup (create these as teammates):
- Teammate 1 (Architect): Design the CLI interface. Decide on the function
  signatures, data model (JSON schema for tasks.json), argparse structure,
  and output formats. Send the interface spec to Teammate 2 and Teammate 3
  before they start coding.
- Teammate 2 (Implementer): Wait for the interface spec from Teammate 1.
  Implement cli.py based on that spec. When done, message Teammate 3 with
  the list of public functions and any spec deviations. Also message
  Teammate 4 confirming implementation is complete.
- Teammate 3 (Tester): Wait for the interface spec from Teammate 1 AND
  the implementation confirmation from Teammate 2. Write tests in
  test_cli.py. Run them. If any test fails, message Teammate 2 with the
  failure details and wait for a fix. Repeat until all tests pass. Report
  final test results to the lead.
- Teammate 4 (Docs): Wait for implementation confirmation from Teammate 2.
  Read the actual cli.py code, then write README.md with real usage
  examples based on what was implemented (not the spec alone).

Requirements:
- Teammates must message each other to share specs, confirm readiness,
  and report failures. Do not start work until dependencies are received.
- The lead must wait for all teammates to report completion before
  synthesizing the final result.
- If Teammate 3 reports test failures, Teammate 2 must fix and re-confirm.
- Delete the team before the final response.
- Avoid editing the same file from multiple teammates.

You should see the lead describe team creation, then teammate activity, then final synthesis. Here is the screenshot from the beginning of the job:

Claude Code team creation at the start of the job

And here is the result folder:

Taskrunner result folder

You can ask claude to give your the jsonl file path to the chat. Here is the output screenshots from my run:

Orchestrator agent logs output part 1

Orchestrator agent logs output part 2

This is the creation and communication sequence you should expect in a healthy run.

Part 2: Agent Teams in Claude Agent SDK

Now let’s use the same idea programmatically and using claude agent sdk in python. Again to activate the team agent feature in the current version, you need to set the env var. You can do it via the ClaudeAgentOptions class which accepts an env input as you can see in the code samples in this post.

Here is a sample code (I put it in orchestrator_agent.py) with pretty logging you can copy-paste and run:

"""
Orchestrator Agent (Claude Agent SDK)
=====================================

Single-phase orchestrator with full tool access and Agent Teams.
  - permission_mode="bypassPermissions" — full autonomy
  - Agent Teams enabled via CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
"""

import asyncio
import json
import os
import sys
import time
from datetime import datetime
from dotenv import load_dotenv
from claude_agent_sdk import (
    query,
    ClaudeAgentOptions,
    AssistantMessage,
    ResultMessage,
    ToolResultBlock,
)

load_dotenv()

# ============================================================
# LOGGING
# ============================================================

class C:
    """Terminal colors."""
    RST = "\033[0m"
    B = "\033[1m"
    DIM = "\033[2m"
    RED = "\033[91m"
    GRN = "\033[92m"
    YEL = "\033[93m"
    BLU = "\033[94m"
    MAG = "\033[95m"
    CYN = "\033[96m"
    WHT = "\033[97m"
    BG_BLU = "\033[44m"
    BG_GRN = "\033[42m"
    BG_YEL = "\033[43m"
    BG_MAG = "\033[45m"

def ts():
    return datetime.now().strftime("%H:%M:%S")

def banner(text, bg=C.BG_BLU):
    try:
        w = min(os.get_terminal_size().columns, 70)
    except OSError:
        w = 70
    print(f"\n{bg}{C.WHT}{C.B} {text.center(w - 2)} {C.RST}")

def section(text):
    print(f"\n{C.CYN}{C.B}{'=' * 60}{C.RST}")
    print(f"{C.CYN}{C.B}  {text}{C.RST}")
    print(f"{C.CYN}{C.B}{'=' * 60}{C.RST}")

def log(icon, text, color=C.WHT):
    print(f"{C.DIM}[{ts()}]{C.RST} {icon} {color}{text}{C.RST}")

def log_tool(tool_name, tool_input):
    print(f"{C.DIM}[{ts()}]{C.RST} {C.YEL}🔧 {tool_name}{C.RST}")
    if tool_name == "Task":
        sa = tool_input.get("subagent_type", "general-purpose")
        desc = tool_input.get("description", "")
        prompt_preview = tool_input.get("prompt", "")[:150]
        print(f"         {C.MAG}Subagent: {sa}{C.RST}")
        print(f"         {C.DIM}Description: {desc}{C.RST}")
        if prompt_preview:
            print(f"         {C.DIM}Prompt: {prompt_preview}...{C.RST}")
    elif tool_name == "ExitPlanMode":
        plan = tool_input.get("plan", "")
        if plan:
            print(f"         {C.GRN}Plan submitted:{C.RST}")
            for line in plan.split("\n"):
                print(f"         {C.GRN}  {line}{C.RST}")
    else:
        compact = json.dumps(tool_input, default=str)
        if len(compact) > 150:
            compact = compact[:150] + "..."
        print(f"         {C.DIM}{compact}{C.RST}")

def log_result_preview(text, max_len=250):
    if not text:
        return
    preview = str(text)[:max_len]
    if len(str(text)) > max_len:
        preview += "..."
    print(f"         {C.GRN}> {preview}{C.RST}")

def log_subagent(text):
    for line in text.strip().split("\n"):
        print(f"         {C.MAG}| {line}{C.RST}")

def log_text(text):
    for line in text.split("\n"):
        print(f"  {C.WHT}{line}{C.RST}")

# ============================================================
# MESSAGE PROCESSOR
# ============================================================

class MessageProcessor:
    """Processes streamed messages and tracks subagent state."""

    def __init__(self, phase_name):
        self.phase = phase_name
        self.session_id = None
        self.task_count = 0
        self.active_tasks = {}
        self.result_msg = None

    def _extract_tool_result_text(self, content):
        if not content:
            return ""
        if isinstance(content, str):
            return content
        if isinstance(content, list):
            parts = []
            for item in content:
                if isinstance(item, dict):
                    text = item.get("text")
                else:
                    text = getattr(item, "text", None)
                if text:
                    parts.append(text)
            return "\n".join(parts)
        return str(content)

    def _handle_tool_result(self, tool_id, result_text):
        if tool_id in self.active_tasks:
            info = self.active_tasks.pop(tool_id)
            elapsed = time.time() - info["t"]
            log(
                "✅",
                f"[{info['type']}] done in {elapsed:.1f}s: {info['desc']}",
                C.GRN,
            )
            log_result_preview(result_text)
        elif result_text:
            log_result_preview(result_text)

    async def process(self, message):
        # --- AssistantMessage: reasoning + tool calls ---
        if isinstance(message, AssistantMessage):
            is_sub = (
                hasattr(message, "parent_tool_use_id")
                and message.parent_tool_use_id
            )

            if hasattr(message, "content") and message.content:
                for block in message.content:
                    btype = getattr(block, "type", None)

                    # Text
                    if btype == "text" or hasattr(block, "text"):
                        text = getattr(block, "text", "")
                        if text.strip():
                            if is_sub:
                                log_subagent(text)
                            else:
                                log_text(text)

                    # Thinking
                    elif btype == "thinking":
                        thinking = getattr(block, "thinking", "")
                        if thinking:
                            preview = thinking[:150] + "..." if len(thinking) > 150 else thinking
                            print(f"         {C.DIM}💭 {preview}{C.RST}")

                    # Tool result
                    elif isinstance(block, ToolResultBlock):
                        tool_id = getattr(block, "tool_use_id", "")
                        result_text = self._extract_tool_result_text(
                            getattr(block, "content", None)
                        )
                        self._handle_tool_result(tool_id, result_text)

                    # Tool use
                    elif btype == "tool_use" or hasattr(block, "name"):
                        tool_name = getattr(block, "name", "?")
                        tool_input = getattr(block, "input", {})
                        tool_id = getattr(block, "id", "")

                        if tool_name == "Task":
                            self.task_count += 1
                            sa_type = tool_input.get("subagent_type", "general-purpose")
                            desc = tool_input.get("description", "subtask")
                            self.active_tasks[tool_id] = {
                                "type": sa_type,
                                "desc": desc,
                                "t": time.time(),
                            }
                            log(
                                "🚀",
                                f"Task #{self.task_count}: [{sa_type}] {desc}",
                                C.MAG,
                            )

                        log_tool(tool_name, tool_input)

        # --- ResultMessage ---
        elif isinstance(message, ResultMessage):
            self.result_msg = message
            if hasattr(message, "session_id"):
                self.session_id = message.session_id

# ============================================================
# ORCHESTRATOR
# ============================================================

async def run_orchestrator(prompt: str, cwd: str = "."):
    """
    Single-phase orchestrator with full tool access and Agent Teams.
    """
    abs_cwd = os.path.abspath(cwd)

    print(f"\n{C.B}{C.CYN}")
    print("  ╔══════════════════════════════════════════════════╗")
    print("  ║   Orchestrator Agent (Claude Agent SDK)          ║")
    print("  ║   Full tools + Agent Teams enabled                ║")
    print("  ╚══════════════════════════════════════════════════╝")
    print(f"{C.RST}")
    log("📋", f"Task: {prompt}", C.B)
    log("📂", f"CWD: {abs_cwd}", C.DIM)
    log("🚩", "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1", C.DIM)

    start = time.time()
    processor = MessageProcessor("run")

    async for msg in query(
        prompt=prompt,
        options=ClaudeAgentOptions(
            permission_mode="bypassPermissions",
            cwd=abs_cwd,
            env={"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"},
            include_partial_messages=True,
            max_turns=200,
        ),
    ):
        await processor.process(msg)

    duration = time.time() - start

    # Final summary
    banner("COMPLETED", C.BG_GRN)
    if processor.result_msg:
        status = getattr(processor.result_msg, "subtype", "unknown")
        print(f"  {C.GRN}Status: {status}{C.RST}")
        cost = getattr(processor.result_msg, "cost_usd", None)
        if cost:
            print(f"  {C.DIM}Cost: ${cost:.4f}{C.RST}")
        result_text = getattr(processor.result_msg, "result", "")
        if result_text:
            print(f"\n{C.WHT}{result_text}{C.RST}")

    print(f"\n{C.DIM}Total: {duration:.1f}s | Tasks delegated: {processor.task_count}{C.RST}")

# ============================================================
# ENTRY POINT
# ============================================================

async def main():
    # CLI usage: pass prompt directly
    if len(sys.argv) > 1:
        await run_orchestrator(" ".join(sys.argv[1:]))
        return

    # Interactive menu
    print(f"\n{C.B}{C.CYN}")
    print("  ╔══════════════════════════════════════════════════╗")
    print("  ║   Orchestrator Agent (Claude Agent SDK)          ║")
    print("  ║   Full tools + Agent Teams enabled                ║")
    print("  ╚══════════════════════════════════════════════════╝")
    print(f"{C.RST}")
    print(f"{C.B}Choose an example or enter a custom prompt:{C.RST}\n")

    prompt = input(f"{C.B}> {C.RST}").strip()

    await run_orchestrator(prompt)

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

I have the prompt in a txt file and run the code as following:

python orchestrator_agent.py "$(cat prompt.txt)"

You can now sit down and enjoy watching it doing the job. And here are the logs:

  ╔══════════════════════════════════════════════════╗
  ║   Orchestrator Agent (Claude Agent SDK)          ║
  ║   Full tools + Agent Teams enabled                ║
  ╚══════════════════════════════════════════════════╝

[23:17:08] 📋 Task: You are the lead agent. Create a team and coordinate the work directly
in this session. Do NOT write a Python script or SDK code to do this.
Do the work yourself.

Put all output files in the taskrunner/ folder.

Task:
Build a Python CLI called taskrunner with commands:
- list
- add <title>
- done <id>
- stats

Team setup (create these as teammates):
- Teammate 1 (Architect): Design the CLI interface. Decide on the function
  signatures, data model (JSON schema for tasks.json), argparse structure,
  and output formats. Send the interface spec to Teammate 2 and Teammate 3
  before they start coding.
- Teammate 2 (Implementer): Wait for the interface spec from Teammate 1.
  Implement cli.py based on that spec. When done, message Teammate 3 with
  the list of public functions and any spec deviations. Also message
  Teammate 4 confirming implementation is complete.
- Teammate 3 (Tester): Wait for the interface spec from Teammate 1 AND
  the implementation confirmation from Teammate 2. Write tests in
  test_cli.py. Run them. If any test fails, message Teammate 2 with the
  failure details and wait for a fix. Repeat until all tests pass. Report
  final test results to the lead.
- Teammate 4 (Docs): Wait for implementation confirmation from Teammate 2.
  Read the actual cli.py code, then write README.md with real usage
  examples based on what was implemented (not the spec alone).

Requirements:
- Teammates must message each other to share specs, confirm readiness,
  and report failures. Do not start work until dependencies are received.
- The lead must wait for all teammates to report completion before
  synthesizing the final result.
- If Teammate 3 reports test failures, Teammate 2 must fix and re-confirm.
- Delete the team before the final response.
- Avoid editing the same file from multiple teammates.
[23:17:08] 📂 CWD: /Users/isaackargar/codes/personal/claude-agent-sdk-tests
[23:17:08] 🚩 CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1


  I'll create a team to build the taskrunner CLI. Let me set up the team and coordinate the work.
[23:17:15] 🔧 TeamCreate
         {"team_name": "taskrunner-build", "description": "Building a Python CLI called taskrunner with list, add, done, and stats commands", "agent_type": "le...
[23:17:18] 🔧 Bash
         {"command": "mkdir -p taskrunner", "description": "Create taskrunner output directory"}
  Now let me spawn all four teammates in parallel:
[23:17:37] 🚀 Task #1: [general-purpose] Design CLI interface spec
[23:17:37] 🔧 Task
         Subagent: general-purpose
         Description: Design CLI interface spec
         Prompt: You are the Architect (Teammate 1) on a team building a Python CLI called "taskrunner".

Your job: Design the CLI interface spec and send it to your t...
[23:17:45] 🚀 Task #2: [general-purpose] Implement cli.py code
[23:17:45] 🔧 Task
         Subagent: general-purpose
         Description: Implement cli.py code
         Prompt: You are the Implementer (Teammate 2) on a team building a Python CLI called "taskrunner".

IMPORTANT: Wait for the interface spec from the architect t...
[23:17:53] 🚀 Task #3: [general-purpose] Write and run tests
[23:17:53] 🔧 Task
         Subagent: general-purpose
         Description: Write and run tests
         Prompt: You are the Tester (Teammate 3) on a team building a Python CLI called "taskrunner".

IMPORTANT: You must wait for TWO things before starting work:
1....
[23:17:58] 🚀 Task #4: [general-purpose] Write README documentation
[23:17:58] 🔧 Task
         Subagent: general-purpose
         Description: Write README documentation
         Prompt: You are the Docs Writer (Teammate 4) on a team building a Python CLI called "taskrunner".

IMPORTANT: Wait for implementation confirmation from "imple...
  All four teammates are spawned and running. The workflow is:
  1. **Architect** designs the spec → sends to Implementer and Tester
  2. **Implementer** waits for spec → builds cli.py → notifies Tester and Docs Writer
  3. **Tester** waits for spec + implementation → writes and runs tests → reports to me
  4. **Docs Writer** waits for implementation → reads code → writes README → reports to me

  Now I'll wait for the teammates to coordinate and complete their work. Let me check on their progress.
[23:18:05] 🔧 TodoWrite
         {"todos": [{"content": "Wait for Architect to send spec to teammates", "status": "in_progress", "activeForm": "Waiting for Architect to send spec"}, {...
  The team is now working autonomously. Let me wait for their messages to come in as they coordinate and complete their tasks.

  [Waiting for teammate messages to arrive automatically...]
[23:18:10] 🔧 Bash
         {"command": "sleep 15 && echo \"Check-in after 15s\"", "description": "Wait for teammates to start coordinating", "timeout": 30000}
[23:18:27] 🔧 Bash
         {"command": "ls -la taskrunner/", "description": "Check what files exist in taskrunner directory"}
  Good progress - the implementer has already created cli.py. Let me wait a bit more for testing and docs to complete.
[23:18:31] 🔧 Bash
         {"command": "sleep 20 && echo \"Check-in after 20s\"", "description": "Wait for more progress", "timeout": 30000}
[23:18:54] 🔧 Bash
         {"command": "ls -la taskrunner/", "description": "Check current state of taskrunner files"}
[23:18:56] 🔧 Bash
         {"command": "sleep 30 && echo \"Check-in\"", "description": "Wait for tester and docs writer", "timeout": 45000}
[23:19:29] 🔧 Bash
         {"command": "ls -la taskrunner/", "description": "Check taskrunner directory for new files"}
  README.md is done. Still waiting for the tester to write and run tests. Let me wait a bit more.
[23:19:32] 🔧 Bash
         {"command": "sleep 30 && echo \"Check-in\"", "description": "Wait for tester to complete", "timeout": 45000}
[23:20:05] 🔧 Bash
         {"command": "ls -la taskrunner/", "description": "Check for test file"}
  Tests have been written and are being run (I can see `__pycache__`). Let me update my todos and wait for the final test results.
[23:20:09] 🔧 TodoWrite
         {"todos": [{"content": "Wait for Architect to send spec to teammates", "status": "completed", "activeForm": "Waiting for Architect to send spec"}, {"c...
[23:20:12] 🔧 Bash
         {"command": "sleep 30 && echo \"Check-in\"", "description": "Wait for test results to come in", "timeout": 45000}
  Let me check if the tester has reported back yet. Let me also verify the test status myself:
[23:20:46] 🔧 Bash
         {"command": "cd /Users/isaackargar && python -m pytest taskrunner/test_cli.py -v 2>&1 | tail -40", "description": "Run tests to check current status",...
[23:20:49] 🔧 Bash
         {"command": "python -m pytest /Users/isaackargar/codes/personal/claude-agent-sdk-tests/taskrunner/test_cli.py -v 2>&1 | tail -40", "description": "Run...
  There are 3 test failures - they're related to how the test calls `add_task` with positional args. The tester should be coordinating with the implementer on this. Let me check if the tester already messaged the implementer about these failures. Let me wait for the fix cycle.
[23:20:55] 🔧 Bash
         {"command": "sleep 40 && echo \"Check-in after fix cycle wait\"", "description": "Wait for fix cycle between tester and implementer", "timeout": 60000...
[23:21:38] 🔧 Bash
         {"command": "python -m pytest /Users/isaackargar/codes/personal/claude-agent-sdk-tests/taskrunner/test_cli.py -v 2>&1 | tail -20", "description": "Re-...
  All 31 tests pass now. The fix cycle between the tester and implementer worked. Let me wait a moment for the tester to send their final report to me.
[23:21:43] 🔧 Bash
         {"command": "sleep 15 && echo \"done\"", "description": "Brief wait for final messages", "timeout": 20000}
[23:22:02] 🔧 TodoWrite
         {"todos": [{"content": "Wait for Architect to send spec to teammates", "status": "completed", "activeForm": "Waiting for Architect to send spec"}, {"c...
  All work is complete. Let me verify the final deliverables and then shut down the team.
[23:22:05] 🔧 Bash
         {"command": "ls -la /Users/isaackargar/codes/personal/claude-agent-sdk-tests/taskrunner/", "description": "Final file listing"}
[23:22:05] 🔧 Read
         {"file_path": "/Users/isaackargar/codes/personal/claude-agent-sdk-tests/taskrunner/cli.py"}
[23:22:06] 🔧 Read
         {"file_path": "/Users/isaackargar/codes/personal/claude-agent-sdk-tests/taskrunner/test_cli.py"}
[23:22:06] 🔧 Read
         {"file_path": "/Users/isaackargar/codes/personal/claude-agent-sdk-tests/taskrunner/README.md"}
  All deliverables look great. Let me now shut down all teammates and delete the team.
[23:22:11] 🔧 SendMessage
         {"type": "shutdown_request", "recipient": "architect", "content": "All work is complete. Please shut down."}
[23:22:11] 🔧 SendMessage
         {"type": "shutdown_request", "recipient": "implementer", "content": "All work is complete. Please shut down."}
[23:22:12] 🔧 SendMessage
         {"type": "shutdown_request", "recipient": "tester", "content": "All work is complete. Please shut down."}
[23:22:12] 🔧 SendMessage
         {"type": "shutdown_request", "recipient": "docs-writer", "content": "All work is complete. Please shut down."}


  Now waiting for teammates to acknowledge shutdown. Let me give them a moment and then delete the team.
[23:22:16] 🔧 Bash
         {"command": "sleep 10 && echo \"Ready to delete team\"", "description": "Wait for shutdown acknowledgments", "timeout": 15000}
  All four teammates have shut down. Now let me delete the team.
[23:22:29] 🔧 TeamDelete
         {}
  The teammates are marked completed but the team still considers them active. Let me wait a moment and retry.
[23:22:32] 🔧 Bash
         {"command": "sleep 5 && echo \"retry\"", "description": "Brief wait before retry"}
[23:22:40] 🔧 TeamDelete
         {}
[23:22:43] 🔧 Bash
         {"command": "rm -rf ~/.claude/teams/vivid-gliding-comet ~/.claude/tasks/vivid-gliding-comet", "description": "Manually clean up team files"}
[23:22:47] 🔧 TodoWrite
         {"todos": [{"content": "Wait for Architect to send spec to teammates", "status": "completed", "activeForm": "Waiting for Architect to send spec"}, {"c...
  ---

  ## ✅ Taskrunner CLI — Complete!

  The team of 4 agents successfully built the **taskrunner** Python CLI. Here's a summary of what happened and what was delivered:

  ### Team Coordination Flow
  1. **Architect** → Designed the full interface spec (data model, function signatures, argparse structure, output formats) and sent it to both Implementer and Tester
  2. **Implementer** → Waited for the spec, then built `cli.py`. Notified Tester and Docs Writer when done
  3. **Tester** → Waited for both the spec and implementation. Wrote 31 tests, discovered 3 failures (argument passing issue with `functools.partial`), messaged Implementer, and the tester fixed the tests to work correctly. **All 31 tests pass** ✅
  4. **Docs Writer** → Waited for implementation confirmation, read the actual code, and wrote `README.md` with real examples

  ### Deliverables in `taskrunner/`

  | File | Description |
  |------|-------------|
  | `cli.py` | Full CLI with `list`, `add`, `done`, `stats` commands (117 lines) |
  | `test_cli.py` | 31 comprehensive pytest tests covering all functions + CLI integration (335 lines) |
  | `README.md` | Documentation with usage examples, data format, and workflow walkthrough |
  | `__init__.py` | Package init file |

  ### CLI Commands
  ```
  python -m taskrunner.cli add "Buy groceries"    → Added task 1: Buy groceries
  python -m taskrunner.cli list                    → Table with ID, Status (✓/✗), Title
  python -m taskrunner.cli done 1                  → Completed task 1: Buy groceries
  python -m taskrunner.cli stats                   → Total: 1 | Done: 1 | Pending: 0
  ```


  The team has already been shut down — all 4 teammates (architect, implementer, tester, docs-writer) completed their work and approved shutdown requests. The team files have been cleaned up as well. Let me verify everything is clean:
[23:23:03] 🔧 Bash
         {"command": "ls ~/.claude/teams/vivid-gliding-comet 2>&1; ls ~/.claude/tasks/vivid-gliding-comet 2>&1", "description": "Verify team files are cleaned ...
  Team is fully cleaned up. Let me do a final verification of all deliverables:
[23:23:07] 🔧 Bash
         {"command": "python -m pytest /Users/isaackargar/codes/personal/claude-agent-sdk-tests/taskrunner/test_cli.py -v 2>&1", "description": "Final test run...
  **All 31 tests pass.** Here's the final summary:

  ---

  ## Taskrunner CLI — Build Complete

  A 4-agent team coordinated to build the taskrunner CLI. Here's how they worked together:

  ### Team Coordination
  | Agent | Role | Waited For | Delivered To |
  |-------|------|------------|-------------|
  | **Architect** | Designed interface spec | — | Implementer, Tester |
  | **Implementer** | Built `cli.py` | Spec from Architect | Tester, Docs Writer |
  | **Tester** | Wrote & ran 31 tests | Spec + Implementation | Implementer (bug report), Lead (results) |
  | **Docs Writer** | Wrote `README.md` | Implementation confirmation | Lead |

  The tester found 3 failures in the initial test run (argument collision with `functools.partial` in CLI integration tests). The tester fixed the tests to correctly use original unpatched function references, and all 31 tests passed on the second run.

  ### Deliverables in `taskrunner/`

  | File | Lines | Description |
  |------|-------|-------------|
  | `cli.py` | 117 | Full CLI — `add`, `list`, `done`, `stats` via argparse subcommands |
  | `test_cli.py` | 335 | 31 pytest tests across 8 test classes (unit + integration) |
  | `README.md` | 132 | Usage docs with real examples and workflow walkthrough |
  | `__init__.py` | — | Package init |

  ### Usage
  ```bash
  python -m taskrunner.cli add "Buy groceries"   # → Added task 1: Buy groceries
  python -m taskrunner.cli list                   # → Table with ID, Status (✓/✗), Title
  python -m taskrunner.cli done 1                 # → Completed task 1: Buy groceries
  python -m taskrunner.cli stats                  # → Total: 1 | Done: 1 | Pending: 0
  ```

Pretty amazing job. That’s it for this post. Please like and share if you liked the post. It helps the post to be seen by more interested people.

Work with Nazmi

Have a problem like this to ship?

The two-week AI Opportunity Audit turns it into a prioritized map, a 90-day roadmap, and one build-ready spec — a fixed-fee first step.

See the AI Opportunity Audit or book a 20-minute call →