5. Agents That Work as a Team
With multi-agent setups, you can have multiple agents collaborate within a single session. On this page, you will build a coordinator that leads a researcher (web research via the Jina MCP) and a reporter (draft creation via the Gmail MCP), and have the team handle the job of “researching the news and summarizing it into an email draft.”
This page introduces the following Managed Agents features:
| Feature | Role |
|---|---|
Multi-agent (multiagent) | Configuration that gives an agent a list (roster) of agents it can delegate to |
| Session threads | An independent execution context for each subagent. They run in parallel |
Why Build a Team?
Section titled “Why Build a Team?”Purely in terms of functionality, you could achieve the same thing by giving a single agent both the Jina and Gmail MCP servers. Still, splitting the work across a team pays off in the three patterns the official documentation calls out:
- Parallelization — fan out independent subtasks at the same time and have the coordinator integrate the results
- Specialization — route work to agents with dedicated prompts and tools. By not giving one agent every capability, you can scope tools and credentials to individual agents
- Escalation — hand only the hard subtasks to a more capable model
The setup on this page includes all three. The coordinator (claude-sonnet-5) can neither search the web nor touch email; the researcher (claude-haiku-4-5) can only use Jina, and the reporter (claude-haiku-4-5) can only use Gmail. Each agent runs in an independent context called a session thread, so the researcher’s long logs never pollute the coordinator’s conversation.
1. Create a Project
Section titled “1. Create a Project”Create a project, move into its folder, and install the Claude SDK.
uv init team-agentcd team-agentuv add anthropic2. Create the Team
Section titled “2. Create the Team”-
Create
setup_team.pyin the project folder and save the following content.setup_team.py import jsonfrom anthropic import Anthropicclient = Anthropic()################################ 1. Find the vaults that hold the Jina and Gmail credentials###############################vault_ids = []for vault in client.beta.vaults.list():for cred in client.beta.vaults.credentials.list(vault_id=vault.id):url = getattr(cred.auth, "mcp_server_url", "") or ""if ("mcp.jina.ai" in url or "gmailmcp.googleapis.com" in url) and vault.id not in vault_ids:vault_ids.append(vault.id)print(f"Found credential: vault={vault.id} ({vault.display_name}) / {url}")if not vault_ids:raise SystemExit("No Jina or Gmail credentials found.")################################ 2. Create an environment (allow network access to MCP servers)###############################environment = client.beta.environments.create(name="Team-environment",config={"type": "cloud","networking": {"type": "limited", "allow_mcp_servers": True},},)print(f"environment: {environment.id}")################################ 3. Create the researcher (has only the Jina MCP)###############################researcher = client.beta.agents.create(name="Team Researcher",model={"id": "claude-haiku-4-5", "speed": "standard"},description="A researcher that investigates topics via web search and article reading.",system=("You are a researcher. Search for the requested topic with search_web, ""and read the full text of promising articles with read_url. ""Return your findings as a concise English summary with source URLs."),mcp_servers=[{"name": "jina", "type": "url", "url": "https://mcp.jina.ai/v1"}],tools=[{"type": "mcp_toolset","mcp_server_name": "jina","default_config": {"enabled": True, "permission_policy": {"type": "always_allow"}},}],)print(f"researcher: {researcher.id}")################################ 4. Create the reporter (has only the Gmail MCP, with pre-execution approval)###############################reporter = client.beta.agents.create(name="Team Reporter",model={"id": "claude-haiku-4-5", "speed": "standard"},description="A reporter that creates Gmail drafts.",system=("You are a reporter. Turn the content you are given into a readable English email ""and create it as a Gmail draft. Do not send any email."),mcp_servers=[{"name": "gmail", "type": "url", "url": "https://gmailmcp.googleapis.com/mcp/v1"}],tools=[{"type": "mcp_toolset","mcp_server_name": "gmail","default_config": {"enabled": True, "permission_policy": {"type": "always_ask"}},}],)print(f"reporter: {reporter.id}")################################ 5. Create the coordinator (no external permissions; delegates to the researcher and reporter)###############################coordinator = client.beta.agents.create(name="Research Team Coordinator",model={"id": "claude-sonnet-5", "speed": "standard"},description="A coordinator that leads the researcher and the reporter.",system=("You are the coordinator of a research team. ""Delegate research to Team Researcher and email draft creation to Team Reporter. ""You yourself can neither search the web nor operate email. ""When researching multiple topics, run the researcher in parallel. ""Finally, integrate the results and report back to the user in English."),tools=[{"type": "agent_toolset_20260401","default_config": {"enabled": True},"configs": [{"name": "web_search", "enabled": False},{"name": "web_fetch", "enabled": False},],}],multiagent={"type": "coordinator","agents": [{"type": "agent", "id": researcher.id},{"type": "agent", "id": reporter.id},],},)print(f"coordinator: {coordinator.id}")with open("ids_team.json", "w") as f:json.dump({"vault_ids": vault_ids,"environment_id": environment.id,"coordinator_id": coordinator.id,},f,indent=2,)print("Saved the IDs to ids_team.json")There are three key points.
multiagent.agentsis the list of delegation targets (the roster). Simply registering existing agents by ID turns them into a team (delegation is limited to one level deep, and you can register up to 20 agents)- Tools and credentials are scoped per agent. Only the researcher can touch Jina, and only the reporter can touch Gmail. The coordinator also has the built-in
web_search/web_fetchtools individually disabled, so it cannot reach the outside world on its own - Different models for different roles — only the coordinator, which handles integration and judgment, uses
claude-sonnet-5; the workers useclaude-haiku-4-5
-
Run the script.
Terminal window uv run setup_team.py
3. Run the Team
Section titled “3. Run the Team”-
Create
run_team.pyin the project folder and save the following content. ReplaceTO_ADDRESSat the top with your own email address.run_team.py import jsonfrom anthropic import Anthropic# Recipient of the email draft (replace with your own email address)TO_ADDRESS = "your email address here"client = Anthropic()with open("ids_team.json") as f:ids = json.load(f)################################ 1. Create a session (combining the coordinator, environment, and vaults)###############################session = client.beta.sessions.create(agent=ids["coordinator_id"],environment_id=ids["environment_id"],vault_ids=ids["vault_ids"],)print(f"session: {session.id}")print(f"Console: https://platform.claude.com/workspaces/default/sessions/{session.id}")print("Chat with the agent team. Type exit to quit.\n")def text_of(content) -> str:return "".join(block.text for block in content if block.type == "text")################################ 2. Send and receive one turn (show the team's activity; confirm approvals with y/N)###############################def run_turn(text: str) -> None:pending_tools = {} # event_id -> (tool name, input, thread ID)stream = client.beta.sessions.events.stream(session_id=session.id)client.beta.sessions.events.send(session.id,events=[{"type": "user.message", "content": [{"type": "text", "text": text}]}],)for event in stream:if event.type == "session.thread_created":print(f"[Thread created: {event.agent_name}]")elif event.type == "agent.thread_message_sent":summary = text_of(event.content).replace("\n", " ")[:80]print(f"[→ request to {event.to_agent_name}] {summary}...")elif event.type == "agent.thread_message_received":summary = text_of(event.content).replace("\n", " ")[:80]print(f"[← result from {event.from_agent_name}] {summary}...")elif event.type == "agent.mcp_tool_use":if getattr(event, "evaluated_permission", None) == "ask":thread_id = getattr(event, "session_thread_id", None)pending_tools[event.id] = (event.name, event.input, thread_id)else:print(f"[Tool: {event.name}]")elif event.type == "agent.message":for block in event.content:if block.type == "text":print(block.text)elif event.type == "session.status_idle":if event.stop_reason.type == "requires_action":for event_id in event.stop_reason.event_ids:if event_id not in pending_tools:continue # skip approval requests that have already been answeredname, tool_input, thread_id = pending_tools.pop(event_id)print("\n----- Approval request -----")print(f"Tool: {name}")print(f"Input: {json.dumps(tool_input, ensure_ascii=False, indent=2)}")answer = input("Allow execution? [y/N] ").strip().lower()confirmation = {"type": "user.tool_confirmation","tool_use_id": event_id,"result": "allow" if answer == "y" else "deny",}if thread_id:confirmation["session_thread_id"] = thread_idif answer != "y":confirmation["deny_message"] = "The user denied execution."client.beta.sessions.events.send(session.id, events=[confirmation])print("Response sent. Continuing...\n")else:breakelif event.type == "session.status_terminated":print("The session has terminated.")raise SystemExit(1)stream.close()################################ 3. Send the first task, then keep the conversation going###############################run_turn("Research two of today's generative AI news stories and summarize each in 2-3 lines. "f"Then put those summaries into an email draft addressed to {TO_ADDRESS} (do not send it).")while True:user_input = input("\nYou> ").strip()if user_input == "exit":breakif not user_input:continuerun_turn(user_input)print("\nDone")What differs from the earlier interactive scripts is that this one displays the multi-agent-specific events.
Event Meaning session.thread_createdA subagent thread was created agent.thread_message_sentThe coordinator sent a request to a subagent agent.thread_message_receivedA subagent’s result arrived at the coordinator Also, approval requests from a subagent (the reporter) are cross-posted to the main session stream. When you reply with
user.tool_confirmation, attach thesession_thread_idof the originating request. Because the same approval request can arrive twice, the script skips IDs that have already been answered. -
Run the script. You can watch the delegation flow as events, and when the draft is being created you will see the same approval request as in 3. An Agent That Works with Gmail.
Terminal window uv run run_team.pysession: sesn_012EBVy3rLmecShnTGTU1PxZChat with the agent team. Type exit to quit.[Thread created: Team Researcher][→ request to Team Researcher] Research two of today's (as of the current date) generative AI news stories...I have asked the researcher to investigate. Once the results arrive, I will review them and ask the Reporter to create the email draft.[← result from Team Researcher] I have completed the research on generative AI news as of July 5, 2026. Here are the two stories...[Thread created: Team Reporter][→ request to Team Reporter] Create a Gmail draft (do not send it) addressed to (your email address)...----- Approval request -----Tool: create_draftInput: {"body": "Hello,\n\nHere is today's roundup of important generative AI news....","subject": "Today's Generative AI News Roundup (July 5, 2026)","to": ["(your email address)"]}Allow execution? [y/N] yResponse sent. Continuing...[← result from Team Reporter] The Gmail draft has been created. [Details] - To: (your email address) - Subject: Today's Generative AI News Roundup...Team Researcher's research and Team Reporter's email draft are both complete. Here is a summary of the results....You> exitDone -
In Gmail’s “Drafts” folder, you will find the news roundup draft the team created. If you open the session page in the console, you can also follow the exchanges between the coordinator and each thread on screen.
Summary
Section titled “Summary”- Just register existing agents by ID in
multiagent.agents(the roster) and you have a team the coordinator can delegate to. Agents are reusable building blocks. - Tools and credentials are scoped per agent. You can divide work with least privilege: the coordinator gets no external permissions, the researcher gets only Jina, and the reporter gets only Gmail (vaults apply to the whole session, so a single attachment is enough).
- Subagents run in parallel as session threads, and you can observe them through the
session.thread_createdandagent.thread_message_sent/receivedevents. - Subagents’ approval requests are also cross-posted to the main session, so the human approval gate can be managed in one place.
Great work! Please share your thoughts so far in a post on X.
Share your thoughts on X