Multi-Agent Business Automation Pipeline

Build a lead processing pipeline with three AI agents, Airtable, and email.

Introduction

30 Second Summary

Every business has repetitive tasks that eat up hours. Researching a new lead, adding them to a CRM, and sending a personalized email is a workflow that most sales teams do dozens of times a day.

In this project, you will build a multi-agent automation pipeline that processes leads end-to-end. A coordinator AI agent delegates work to three specialized sub-agents that research companies, update your CRM, and send personalized outreach emails.

What You'll Build

A fully automated lead processing pipeline using the Claude Agent SDK, Airtable as a CRM, and Resend for email outreach.

By the end of this project, you'll have:

  • A working multi-agent pipeline where a coordinator delegates tasks to specialized research, CRM, and outreach sub-agents.
  • Real business tool integrations with Airtable for CRM records and Resend for automated email.
  • A reusable architectural pattern for building any multi-agent automation service you can deliver to clients.
  • Secret Mission: Add intelligent lead scoring that decides whether to send outreach immediately, delay it, or skip it entirely based on research quality.

Are there any prerequisites?

You need basic Python knowledge (variables, functions, running scripts) and familiarity with what APIs are and how API keys work. No prior experience with AI agents or the specific tools in this project is required.

Install Dependencies and Create Project Structure

Before your agents can research leads, update a CRM, or send emails, you need the right Python packages installed.

In this step, you'll install four packages and create a clean project folder with all the files your pipeline needs.

In this step, get ready to:
  • Install the Python packages for AI agents, CRM integration, and email.
  • Create your project folder and file structure.
  • Configure your environment variables file with API keys.
Install Python packages

Your pipeline relies on four packages. claude-agent-sdk gives you the multi-agent orchestration framework. pyairtable and resend connect to your CRM and email tools. python-dotenv loads your API keys from a local file.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Install all four packages by running this command:
pip install claude-agent-sdk pyairtable resend python-dotenv
  • Verify all packages installed correctly by running:
python -c "import claude_agent_sdk; import pyairtable; import resend; print('All packages installed successfully')"

You should see All packages installed successfully printed in your terminal.

Seeing a ModuleNotFoundError?

If Python cannot find one of the packages, the install may have failed silently. Run the pip install command again and watch for red error text in the output.

If you have multiple Python versions, try using pip3 instead of pip to make sure packages go to the right version.

Help me fix a ModuleNotFoundError after running pip install

Create your project folder structure

Your pipeline needs a main script, an environment config file, and a tools/ folder for the helper scripts your agents will call.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and all the required files by running these commands:

macOS

mkdir multi-agent-pipeline
cd multi-agent-pipeline
mkdir tools
touch main.py .env tools/__init__.py tools/airtable_tools.py tools/resend_tools.py

Windows

mkdir multi-agent-pipeline
cd multi-agent-pipeline
mkdir tools
type nul > main.py
type nul > .env
type nul > tools\__init__.py
type nul > tools\airtable_tools.py
type nul > tools\resend_tools.py
  • Confirm the files exist by running:
ls

You should see main.py, .env, and tools in the output.

Don't see all the files?

If .env is missing from the ls output, it may be hidden by default. Run ls -a (macOS) or dir /a (Windows) to show hidden files.

Make sure you ran cd multi-agent-pipeline before running ls. If you're still in ~/Desktop, you'll see the folder itself instead of its contents.

Help me find my project files after creating the folder structure

Set up your .env file

The .env file stores your API keys so they stay out of your code. You'll fill in the Airtable and Resend keys in later steps.

  • Open .env in your text editor and paste the following content:
ANTHROPIC_API_KEY=your-api-key-here
AIRTABLE_TOKEN=placeholder
AIRTABLE_BASE_ID=placeholder
RESEND_API_KEY=placeholder

What does this file do?

The python-dotenv package reads this file and loads each line as an environment variable when your script starts.

This keeps secrets separate from your code. If you ever share your project, you can exclude .env and your keys stay private.

  • Replace your-api-key-here with your actual Anthropic API key.
  • Leave the other three values as placeholder for now.
  • Save .env.

Keep your API key safe

Never commit your .env file to Git or share it publicly. If you use version control, add .env to your .gitignore file.

✔️ Awesome, I've got everything!

Great. Double check you've saved your .env file with your real Anthropic API key in place.

ⓧ I'd like to double check the full code

Here is the complete .env file for reference:

ANTHROPIC_API_KEY=your-api-key-here
AIRTABLE_TOKEN=placeholder
AIRTABLE_BASE_ID=placeholder
RESEND_API_KEY=placeholder

Remember to replace your-api-key-here with your actual key. The other three values stay as placeholder until later steps.

Your environment is ready. Next up, you'll set up Airtable as a real CRM that your agents can read from and write to.

Set Up Airtable as Your CRM

Your project structure is ready and all dependencies are installed. Now it's time to give your CRM agent somewhere to store lead data.

In this step, you'll set up Airtable as your CRM database and write the Python tool that your AI agent will use to create and update lead records. By the end, you'll have a working connection between your pipeline and a real business tool.

In this step, get ready to:
  • Create and configure an Airtable base as your lead pipeline CRM.
  • Write the CRM tool script that handles creating, reading, and updating records.
  • Test the connection by creating a lead record from your terminal.
Create an Airtable account and configure your Leads base

Airtable is a spreadsheet-database hybrid that works well as a lightweight CRM. Your CRM agent will store research findings and lead status here so you can track your pipeline visually.

  • Go to https://airtable.com and sign up for a free account (or log in if you already have one).
  • From your Airtable home dashboard, click Create a base.
  • Name the base Lead Pipeline.

Airtable creates a default table with some pre-filled columns. You need to replace those with the specific fields your CRM agent expects.

  • Rename the default table to Leads by double-clicking its tab name at the top.
  • Delete all existing default fields by right-clicking each column header and selecting Delete field.
  • Create these fields by clicking the + button at the end of the column headers:
  • Name with type Single line text.
  • Email with type Email.
  • Company with type Single line text.
  • Status with type Single select. Add four options: New, Researched, Contacted, Qualified.
  • Research Notes with type Long text.
  • Score with type Number (set format to integer).
  • Last Contacted with type Date.

What is Airtable?

Airtable combines the simplicity of a spreadsheet with the power of a database. Each "base" is like a database, each "table" is like a sheet, and each row is a record.

The free tier gives you 1,000 records per base and 1,000 API calls per month. That is more than enough for building and demoing your pipeline.

Now you need to create an API token so your Python script can access this base programmatically.

  • Open the Airtable token creation page at https://airtable.com/create/tokens.
  • Click Create token.
  • Name your token Lead Pipeline Access.
  • Click + Add a scope and check both data.records:read and data.records:write.
  • Click + Add a base and select your Lead Pipeline base.
  • Click Create token.
  • Copy the token immediately. Airtable only shows it once.

Copy your token right away

Airtable will never show this token again after you close the dialog. If you lose it, you will need to create a new one.

Next, grab your Base ID from the Airtable URL.

  • Open your Lead Pipeline base in your browser.
  • Look at the URL in your address bar. It looks like https://airtable.com/appXXXXXXXXXX/....
  • The appXXXXXXXXXX portion is your Base ID. Copy it.

Now update your .env file with both values.

  • Open .env in your text editor.
  • Find these two lines:
AIRTABLE_TOKEN=placeholder
AIRTABLE_BASE_ID=placeholder
  • Replace placeholder with your real token and Base ID so the lines look like this:
AIRTABLE_TOKEN=your-airtable-pat-here
AIRTABLE_BASE_ID=your-base-id-here
  • Save .env.

Can't find your Base ID?

Make sure you have the base open (not just the Airtable home page). The URL must start with airtable.com/app.... The Base ID always starts with app followed by a string of letters and numbers.

Help me find my Airtable Base ID.

Build the Airtable CRM tool

Your CRM agent needs a way to interact with Airtable. You will write a Python script that handles creating, reading, and updating lead records. The agent will call this script from the command line.

  • Open tools/airtable_tools.py in your text editor and add the imports and configuration:
import os
import sys
import json
from dotenv import load_dotenv
from pyairtable import Api

load_dotenv()

AIRTABLE_TOKEN = os.environ.get("AIRTABLE_TOKEN")
AIRTABLE_BASE_ID = os.environ.get("AIRTABLE_BASE_ID")
TABLE_NAME = "Leads"


def get_table():
    # Create an Airtable API connection and return the Leads table
    api = Api(AIRTABLE_TOKEN)
    return api.table(AIRTABLE_BASE_ID, TABLE_NAME)

What does this code do?

load_dotenv() reads your .env file and loads the token and Base ID into environment variables. This keeps secrets out of your source code.

get_table() creates a connection to the specific Airtable table. Every other function will call this to get a reference to the Leads table before performing operations.

  • Below get_table(), add the three CRUD functions:

def create_lead(data):
    # Insert a new lead record into the Leads table
    table = get_table()
    record = table.create(data)
    return record


def get_all_leads():
    # Retrieve all lead records from the table
    table = get_table()
    records = table.all()
    return records


def update_lead(record_id, data):
    # Update an existing lead record by its Airtable record ID
    table = get_table()
    record = table.update(record_id, data)
    return record

What does this code do?

  • create_lead(data) takes a dictionary of field values and inserts a new row in your Airtable.
  • get_all_leads() fetches every record from the Leads table.
  • update_lead(record_id, data) modifies specific fields on an existing record. The record_id is assigned by Airtable when the record is created.
  • Finally, add the CLI interface at the bottom of the file so the agent can call it from the command line:

if __name__ == "__main__":
    action = sys.argv[1] if len(sys.argv) > 1 else "help"

    if action == "create":
        data = json.loads(sys.argv[2])
        result = create_lead(data)
        print(json.dumps(result, indent=2, default=str))
    elif action == "list":
        results = get_all_leads()
        print(json.dumps(results, indent=2, default=str))
    elif action == "update":
        record_id = sys.argv[2]
        data = json.loads(sys.argv[3])
        result = update_lead(record_id, data)
        print(json.dumps(result, indent=2, default=str))
    else:
        print("Usage: python tools/airtable_tools.py [create|list|update] [args]")
        print('  create \'{"Name": "...", "Company": "...", "Email": "...", "Status": "New"}\'')        print("  list")
        print('  update RECORD_ID \'{"Status": "Contacted"}\'') 

Why a CLI interface?

The CRM sub-agent uses the Bash tool to execute commands. By making this script callable from the command line with actions like create, list, and update, the agent can interact with Airtable without needing a direct Python API integration.

  • Save tools/airtable_tools.py.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

import os
import sys
import json
from dotenv import load_dotenv
from pyairtable import Api

load_dotenv()

AIRTABLE_TOKEN = os.environ.get("AIRTABLE_TOKEN")
AIRTABLE_BASE_ID = os.environ.get("AIRTABLE_BASE_ID")
TABLE_NAME = "Leads"


def get_table():
    api = Api(AIRTABLE_TOKEN)
    return api.table(AIRTABLE_BASE_ID, TABLE_NAME)


def create_lead(data):
    table = get_table()
    record = table.create(data)
    return record


def get_all_leads():
    table = get_table()
    records = table.all()
    return records


def update_lead(record_id, data):
    table = get_table()
    record = table.update(record_id, data)
    return record


if __name__ == "__main__":
    action = sys.argv[1] if len(sys.argv) > 1 else "help"

    if action == "create":
        data = json.loads(sys.argv[2])
        result = create_lead(data)
        print(json.dumps(result, indent=2, default=str))
    elif action == "list":
        results = get_all_leads()
        print(json.dumps(results, indent=2, default=str))
    elif action == "update":
        record_id = sys.argv[2]
        data = json.loads(sys.argv[3])
        result = update_lead(record_id, data)
        print(json.dumps(result, indent=2, default=str))
    else:
        print("Usage: python tools/airtable_tools.py [create|list|update] [args]")
        print('  create \'{"Name": "...", "Company": "...", "Email": "...", "Status": "New"}\'')        print("  list")
        print('  update RECORD_ID \'{"Status": "Contacted"}\'') 
Test the CRM tool

Before wiring this into your agent pipeline, verify that your Airtable connection actually works.

  • In your terminal (still inside the multi-agent-pipeline folder), create a test lead by running:
python tools/airtable_tools.py create "{\"Name\": \"Test Lead\", \"Company\": \"Test Corp\", \"Email\": \"test@example.com\", \"Status\": \"New\"}"

You should see the created record printed as JSON in your terminal, including an id field that starts with rec.

  • Open your Airtable base in your browser and verify that the record appears in the Leads table with the Name, Company, Email, and Status fields populated.

Seeing an authentication error?

  • Check that your AIRTABLE_TOKEN in .env is the full token you copied (not the word placeholder).
  • Verify that your token has both data.records:read and data.records:write scopes enabled.
  • Confirm the token has access granted to your Lead Pipeline base specifically.
  • Make sure your AIRTABLE_BASE_ID starts with app and matches the ID in your Airtable URL.

Help me debug my Airtable connection error.

Windows JSON quoting

On Windows, command-line JSON escaping can be tricky. If you get a JSON parsing error, try using double quotes for the outer string and escaped double quotes inside. Alternatively, you can test by opening a Python shell and calling create_lead({"Name": "Test"}) directly.

Your CRM integration is live. Next up, you will set up email outreach so your pipeline can send personalized messages automatically.

Set Up Email Outreach with Resend

Your CRM is connected and ready to store lead data. Now you need a way to reach out to those leads automatically.

Resend is an email API that lets you send transactional emails with a few lines of Python. In this step, you'll create a Resend account, build the outreach tool, and send a test email to prove it works.

In this step, get ready to:
  • Create a Resend account and get your API key.
  • Build the email sending tool in resend_tools.py.
  • Send a test email to verify everything works.
Create a Resend account and get your API key

Resend provides a free tier with 3,000 emails per month. No credit card is required to sign up.

  • Go to https://resend.com and sign up for a free account.
  • Once you're in the dashboard, click API Keys in the left sidebar.
  • Click Create API Key.
  • Give your key a name (e.g. Lead Pipeline).
  • Select Full access as the permission.
  • Click Add to create the key.
  • Copy the key immediately. It starts with re_ and you can only view it once.

What about the sender address?

On the free tier, you can only send emails from onboarding@resend.dev. This is Resend's built-in test sender, and it works perfectly for this project.

To send from a custom domain (like hello@yourcompany.com), you would verify your domain in the Resend dashboard. That is optional for this project.

Now add your Resend API key to the .env file.

  • In .env, find this line:
RESEND_API_KEY=placeholder
  • Replace placeholder with your actual Resend API key, so the line looks like this:
RESEND_API_KEY=your-resend-api-key-here
  • Save .env.

All four values in your .env file are now real keys. Your project can authenticate with both Airtable and Resend.

Build the email sending tool

The Outreach sub-agent will use this script to send personalized emails via the Bash tool. The pattern is identical to the Airtable tool: a Python script with functions that can be called from the command line.

  • Open tools/resend_tools.py and add the imports and configuration by pasting this code:
import os
import sys
import json
from dotenv import load_dotenv
import resend

# Load environment variables from .env
load_dotenv()

# Set the Resend API key for authentication
resend.api_key = os.environ.get("RESEND_API_KEY")

What does this code do?

  • The imports bring in the same utilities you used for the Airtable tool: os for environment variables, sys for command-line arguments, and json for parsing input.
  • The resend library authenticates by setting resend.api_key as a module-level variable. Once set, all send calls use this key automatically.
  • Below the configuration, add the send_email function by pasting this code:

def send_email(to_email, subject, html_body, from_email=None):
    # Default to Resend's free test sender if no custom domain
    if from_email is None:
        from_email = "onboarding@resend.dev"

    # Build the email parameters
    params: resend.Emails.SendParams = {
        "from": from_email,
        "to": [to_email],
        "subject": subject,
        "html": html_body,
    }

    # Send the email and return the response
    response = resend.Emails.send(params)
    return response

What does this function do?

  • The from_email defaults to onboarding@resend.dev so you do not need a verified custom domain for testing.
  • The params dictionary follows Resend's SendParams type. The to field expects a list of recipient emails.
  • The resend.Emails.send(params) call hits the Resend API and returns a response containing the email ID.
  • Finally, add the CLI interface at the bottom of the file by pasting this code:

if __name__ == "__main__":
    action = sys.argv[1] if len(sys.argv) > 1 else "help"

    if action == "send":
        # Parse the JSON argument and send the email
        data = json.loads(sys.argv[2])
        result = send_email(
            to_email=data["to"],
            subject=data["subject"],
            html_body=data["html"],
            from_email=data.get("from", None),
        )
        print(json.dumps(result, indent=2, default=str))
    else:
        # Show usage instructions
        print("Usage: python tools/resend_tools.py send '<json>'")
        print('  send \'{"to": "email@example.com", "subject": "Hello", "html": "<p>Hi!</p>"}\'')

What does the CLI interface do?

  • This block lets you run the script directly from the terminal with a send action and a JSON payload.
  • The Outreach sub-agent will call this exact command via the Bash tool to send emails during the pipeline.
  • If no valid action is given, it prints usage instructions so you can quickly check the expected format.
  • Save tools/resend_tools.py.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on to the test.

ⓧ I'd like to double check the full code

Here is the complete tools/resend_tools.py file for comparison.

import os
import sys
import json
from dotenv import load_dotenv
import resend

load_dotenv()

resend.api_key = os.environ.get("RESEND_API_KEY")


def send_email(to_email, subject, html_body, from_email=None):
    if from_email is None:
        from_email = "onboarding@resend.dev"

    params: resend.Emails.SendParams = {
        "from": from_email,
        "to": [to_email],
        "subject": subject,
        "html": html_body,
    }

    response = resend.Emails.send(params)
    return response


if __name__ == "__main__":
    action = sys.argv[1] if len(sys.argv) > 1 else "help"

    if action == "send":
        data = json.loads(sys.argv[2])
        result = send_email(
            to_email=data["to"],
            subject=data["subject"],
            html_body=data["html"],
            from_email=data.get("from", None),
        )
        print(json.dumps(result, indent=2, default=str))
    else:
        print("Usage: python tools/resend_tools.py send '<json>'")
        print('  send \'{"to": "email@example.com", "subject": "Hello", "html": "<p>Hi!</p>"}\'')
Test the email tool

Time to prove the tool works by sending yourself an actual email.

  • Send a test email to yourself by running this command:
python tools/resend_tools.py send "{\"to\": \"[[YOUR_EMAIL=\"your-email@example.com\"]]\", \"subject\": \"Pipeline Test\", \"html\": \"<p>If you see this, your email tool works!</p>\"}"

Use your real email address

Replace the placeholder with your actual email address. This is where the test email will arrive. Any email address works as the recipient.

  • You should see JSON output with an id field. This confirms Resend accepted the email for delivery.
  • Check your inbox (and spam folder) for an email with the subject Pipeline Test.

Email not arriving?

  • Check your spam or junk folder. Emails from onboarding@resend.dev sometimes get filtered.
  • Make sure your RESEND_API_KEY in .env starts with re_ and is not still set to placeholder.
  • If you see a 403 or 401 error, your API key may be invalid. Go back to the Resend dashboard and generate a new one.
  • On Windows, JSON escaping in the terminal can be tricky. Try using single quotes around the JSON or running the command in PowerShell.

Help me debug my Resend email tool.

Your email outreach tool is live. You now have both halves of the pipeline's external integrations: a CRM for storing leads and an email sender for reaching out. Next up, you'll build the multi-agent coordinator that ties everything together.

Build the Multi-Agent Coordinator

Your individual tools are built and tested. The Airtable CRM tool can create and update lead records. The Resend email tool can send personalized messages.

Now you need a brain to run the whole operation. In this step, you will build a coordinator agent that delegates work to three specialized sub-agents, passes context between them, and reports the final result.

In this step, get ready to:
  • Import the Claude Agent SDK and define your project's imports.
  • Write the coordinator pipeline function with three specialized sub-agents.
  • Add the entry point that configures a test lead and runs the pipeline.
Define the three specialized sub-agents

The Claude Agent SDK uses a pattern called subagent delegation. A coordinator agent receives a task and decides which specialized sub-agent should handle each part.

Your pipeline will have three sub-agents, each with a focused job and limited tools:

  • Research Agent searches the web for company info (tools: WebSearch, WebFetch).
  • CRM Agent creates records in Airtable (tools: Bash, Read).
  • Outreach Agent drafts and sends personalized emails (tools: Bash, Read).
  • Open main.py in your text editor.
  • Add the imports and environment setup by pasting this code:
import asyncio
import os
from dotenv import load_dotenv
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition

load_dotenv()

What do these imports do?

  • asyncio lets you run asynchronous code. The Agent SDK uses async functions because agents may take time to think and act.
  • query is the main function that sends a prompt to the coordinator and streams back messages as agents work.
  • ClaudeAgentOptions configures which tools and sub-agents the coordinator can access.
  • AgentDefinition defines each sub-agent's description, system prompt, and available tools.
  • load_dotenv() loads your API keys from the .env file so the SDK can authenticate.

Why subagent delegation instead of one big prompt?

You could try to put all the instructions into a single agent. But as tasks get complex, a single prompt becomes unreliable.

Subagent delegation keeps each agent focused on one skill with only the tools it needs. The coordinator handles sequencing and context passing. This is the same pattern AI automation agencies use in production.

Write the full coordinator pipeline

The coordinator receives a lead's name, company, and email. It then delegates to each sub-agent in sequence, passing context forward so the outreach email references what the research agent found.

  • Below your imports in main.py, add the run_pipeline function by pasting this code:
async def run_pipeline(lead_name, lead_company, lead_email):
    print(f"\n{'='*60}")
    print(f"  MULTI-AGENT LEAD PIPELINE")
    print(f"  Processing: {lead_name} at {lead_company}")
    print(f"{'='*60}\n")

    # Build the coordinator's instructions with lead details
    prompt = f"""You are a lead processing coordinator. Process this new lead through the full pipeline:

- Name: {lead_name}
- Company: {lead_company}
- Email: {lead_email}

Execute these steps in order:

1. RESEARCH: Use the research-agent to find information about {lead_company} and {lead_name}. Get company description, industry, size, recent news, and anything useful for personalization.

2. CRM UPDATE: Use the crm-agent to create a new lead record in Airtable. Pass the research findings as Research Notes. The command format is:
   python tools/airtable_tools.py create '<json>'
   The JSON must include: Name, Email, Company, Status (set to "Researched"), and Research Notes (summary of findings).

3. OUTREACH: Use the outreach-agent to draft and send a personalized email to {lead_email}. The email should reference specific details from the research to show genuine interest. The command format is:
   python tools/resend_tools.py send '<json>'
   The JSON must include: to, subject, and html fields.

After all steps complete, provide a summary of what was accomplished."""

    # Call the coordinator with all three sub-agents defined
    async for message in query(
        prompt=prompt,
        options=ClaudeAgentOptions(
            allowed_tools=["Agent", "Read"],
            agents={
                "research-agent": AgentDefinition(
                    description="Researches leads and companies using web search. Returns structured findings about the company and person.",
                    prompt="You are a lead research specialist. Search the web to find information about the given company and person. Return structured findings including: company description, industry, estimated size, recent news or announcements, key products/services, and any details useful for personalizing sales outreach. Be thorough but concise.",
                    tools=["WebSearch", "WebFetch"],
                ),
                "crm-agent": AgentDefinition(
                    description="Manages CRM records in Airtable. Creates and updates lead records with research data.",
                    prompt="You are a CRM management agent. Your job is to create or update lead records in Airtable using the command-line tool. Run commands using Bash. The tool is located at tools/airtable_tools.py. To create a lead, run: python tools/airtable_tools.py create '<json_data>'. The JSON should include Name, Email, Company, Status, and Research Notes fields. Make sure to properly escape JSON strings for the command line.",
                    tools=["Bash", "Read"],
                ),
                "outreach-agent": AgentDefinition(
                    description="Drafts and sends personalized outreach emails based on research data.",
                    prompt="You are an outreach email specialist. Draft a professional, personalized email that references specific details about the recipient's company. Keep it concise (3-4 short paragraphs max), friendly, and value-focused. Avoid generic sales language. Then send it using the command-line tool. Run: python tools/resend_tools.py send '<json_data>'. The JSON must have to, subject, and html fields. Use simple HTML with paragraph tags.",
                    tools=["Bash", "Read"],
                ),
            },
        ),
    ):
        # Handle different message types from the streaming response
        if hasattr(message, "result"):
            print(f"\n{'='*60}")
            print("  PIPELINE COMPLETE")
            print(f"{'='*60}")
            print(message.result)
        elif hasattr(message, "content"):
            print(f"  [Coordinator]: {message.content[:100]}...")

What does this function do?

This function has three key sections:

  • The prompt tells the coordinator exactly what to do with the lead data. It specifies the order (research first, CRM second, email third) and the command formats each sub-agent should use.
  • The agents dictionary defines all three sub-agents. Each gets a description (so the coordinator knows when to call it), a prompt (so it knows how to behave), and a limited tool list.
  • The async for loop streams messages from the coordinator as it works. When a final result arrives, it prints the completion banner. Otherwise it shows progress updates.

Why does the coordinator get "Agent" and "Read" as tools?

The Agent tool lets the coordinator delegate to sub-agents. The Read tool lets it read file contents if needed for context. The coordinator itself never searches the web or runs Bash commands. Only its sub-agents do.

Add error handling and status reporting

The pipeline function is ready, but you still need an entry point that configures the test lead and kicks everything off.

  • At the bottom of main.py, below the run_pipeline function, add the entry point:
async def main():
    # Configure your test lead here
    lead_name = "Jane Smith"
    lead_company = "TechFlow Inc"
    lead_email = "your-email@example.com"

    print("NOTE: Replace lead_email in main.py with your real email to receive the outreach message.")
    print()

    await run_pipeline(lead_name, lead_company, lead_email)


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

What does this code do?

  • main() defines a test lead with a name, company, and email address. You will replace the email with your own real address so the outreach email lands in your inbox.
  • asyncio.run(main()) starts the async event loop and runs the pipeline. This is the standard Python pattern for running async code from a regular script.
  • Replace your-email@example.com with your actual email address so you can receive the outreach email in the next step.
  • Save main.py.

You will not see any output yet. The next step runs the full pipeline and verifies everything works end-to-end.

Seeing a syntax error when you save?

  • Check that the triple-quoted f-string for prompt has matching f""" at the start and """ at the end.
  • Verify the async for loop is indented inside the run_pipeline function.
  • Make sure if __name__ == "__main__": is at the top level (no indentation).

Why is my main.py showing a syntax error?

✔️ Awesome, I've got everything!

Make sure you have saved main.py with your real email address filled in.

ⓧ I'd like to double check the full code

Here is the complete main.py file. Compare yours against it:

import asyncio
import os
from dotenv import load_dotenv
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition

load_dotenv()


async def run_pipeline(lead_name, lead_company, lead_email):
    print(f"\n{'='*60}")
    print(f"  MULTI-AGENT LEAD PIPELINE")
    print(f"  Processing: {lead_name} at {lead_company}")
    print(f"{'='*60}\n")

    prompt = f"""You are a lead processing coordinator. Process this new lead through the full pipeline:

- Name: {lead_name}
- Company: {lead_company}
- Email: {lead_email}

Execute these steps in order:

1. RESEARCH: Use the research-agent to find information about {lead_company} and {lead_name}. Get company description, industry, size, recent news, and anything useful for personalization.

2. CRM UPDATE: Use the crm-agent to create a new lead record in Airtable. Pass the research findings as Research Notes. The command format is:
   python tools/airtable_tools.py create '<json>'
   The JSON must include: Name, Email, Company, Status (set to "Researched"), and Research Notes (summary of findings).

3. OUTREACH: Use the outreach-agent to draft and send a personalized email to {lead_email}. The email should reference specific details from the research to show genuine interest. The command format is:
   python tools/resend_tools.py send '<json>'
   The JSON must include: to, subject, and html fields.

After all steps complete, provide a summary of what was accomplished."""

    async for message in query(
        prompt=prompt,
        options=ClaudeAgentOptions(
            allowed_tools=["Agent", "Read"],
            agents={
                "research-agent": AgentDefinition(
                    description="Researches leads and companies using web search. Returns structured findings about the company and person.",
                    prompt="You are a lead research specialist. Search the web to find information about the given company and person. Return structured findings including: company description, industry, estimated size, recent news or announcements, key products/services, and any details useful for personalizing sales outreach. Be thorough but concise.",
                    tools=["WebSearch", "WebFetch"],
                ),
                "crm-agent": AgentDefinition(
                    description="Manages CRM records in Airtable. Creates and updates lead records with research data.",
                    prompt="You are a CRM management agent. Your job is to create or update lead records in Airtable using the command-line tool. Run commands using Bash. The tool is located at tools/airtable_tools.py. To create a lead, run: python tools/airtable_tools.py create '<json_data>'. The JSON should include Name, Email, Company, Status, and Research Notes fields. Make sure to properly escape JSON strings for the command line.",
                    tools=["Bash", "Read"],
                ),
                "outreach-agent": AgentDefinition(
                    description="Drafts and sends personalized outreach emails based on research data.",
                    prompt="You are an outreach email specialist. Draft a professional, personalized email that references specific details about the recipient's company. Keep it concise (3-4 short paragraphs max), friendly, and value-focused. Avoid generic sales language. Then send it using the command-line tool. Run: python tools/resend_tools.py send '<json_data>'. The JSON must have to, subject, and html fields. Use simple HTML with paragraph tags.",
                    tools=["Bash", "Read"],
                ),
            },
        ),
    ):
        if hasattr(message, "result"):
            print(f"\n{'='*60}")
            print("  PIPELINE COMPLETE")
            print(f"{'='*60}")
            print(message.result)
        elif hasattr(message, "content"):
            print(f"  [Coordinator]: {message.content[:100]}...")


async def main():
    lead_name = "Jane Smith"
    lead_company = "TechFlow Inc"
    lead_email = "your-email@example.com"

    print("NOTE: Replace lead_email in main.py with your real email to receive the outreach message.")
    print()

    await run_pipeline(lead_name, lead_company, lead_email)


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

Your multi-agent pipeline is fully written. Next up, you will run it with a real lead and watch all three agents spring into action.

Run and Test the Full Pipeline

Your three sub-agents are built, your tools are tested, and your coordinator knows how to orchestrate the full workflow. It is time to run the entire pipeline end-to-end and watch the agents collaborate in real time.

In this step, you will execute the pipeline with a test lead and verify that every system received the correct data. You will see the coordinator delegate research, CRM updates, and email outreach across your three agents automatically.

In this step, get ready to:
  • Execute the full multi-agent pipeline with a test lead.
  • Verify results in the console, Airtable, and your email inbox.
  • Test with different leads and refine your agent prompts.
Update your email address and run the pipeline

The default test lead in main.py sends the outreach email to your-email@example.com. You need to replace this with your real email address so you can actually receive the personalized message.

  • Open main.py in your text editor.
  • Find this line near the bottom of the file inside the main() function:
lead_email = "your-email@example.com"
  • Replace your-email@example.com with your actual email address, so the line looks like this:
lead_email = "your-actual-email@example.com"
  • Save main.py.
  • Run the full pipeline by running this command:
python main.py

What should I see?

The pipeline takes 30-90 seconds to complete. You will see a banner with "MULTI-AGENT LEAD PIPELINE" followed by status messages as the coordinator delegates to each agent.

The coordinator first sends work to the research-agent, then passes findings to the crm-agent, and finally hands everything to the outreach-agent. When finished, you will see "PIPELINE COMPLETE" followed by a summary of what each agent accomplished.

The pipeline uses real API credits

Each pipeline run costs approximately $0.02-0.10 in Anthropic API usage depending on how much research the agent performs. The Airtable and Resend calls are within their free tiers.

Pipeline fails immediately?

Check that your .env file has all four real keys filled in (not the placeholder values). Make sure your terminal is inside the multi-agent-pipeline folder where .env lives.

If you see an import error for claude_agent_sdk, confirm the package installed correctly by running pip install claude-agent-sdk again.

Help me debug my pipeline error

Verify results across all systems

A successful pipeline run touches three separate systems. Verifying each one confirms that the full agent chain worked from end to end.

  • Check your terminal output. You should see the "PIPELINE COMPLETE" banner followed by a summary mentioning research findings, a CRM record creation, and an email sent.
  • Open your Airtable base at https://airtable.com.
  • Navigate to your Lead Pipeline base and open the Leads table.
  • Confirm a new record exists for "Jane Smith" at "TechFlow Inc" with the Status field set to "Researched" and the Research Notes field populated with findings.
  • Check your email inbox (and spam folder) for a personalized outreach email from onboarding@resend.dev. The email should reference specific details about TechFlow Inc.

What makes a good result?

The research notes should contain real information about the company (or a note that limited information was found). The email should feel personalized, not generic. If the company is very small or new, the research agent may return limited findings. That is normal.

Missing record in Airtable or no email received?

If the Airtable record is missing, your AIRTABLE_TOKEN or AIRTABLE_BASE_ID might be incorrect. Double check them against your Airtable settings.

If no email arrived, check your spam folder first. On the free tier, Resend sends from onboarding@resend.dev which some providers flag as promotional. Also confirm you replaced the placeholder email in main.py with your real address.

Help me figure out why my pipeline results are incomplete

Test with different leads and refine prompts

The best way to improve your pipeline is to test it with different inputs. Real companies with a public web presence give the research agent more material to work with.

  • In main.py, find the main() function and change lead_name and lead_company to a real person at a well-known company.
  • Keep lead_email set to your own address so you receive the outreach email.
  • Save main.py and run the pipeline again:
python main.py
  • Compare the output quality to your first run. Notice whether the research is richer, the CRM notes are more detailed, and the email feels more personalized.

Refine your system prompts

If the research feels too shallow, try adding more specific instructions to the research-agent's prompt field (like "Always check the company's LinkedIn and recent press releases"). If emails sound too generic, add style guidance to the outreach-agent's prompt (like "Open with a specific observation about their product").

Why do some leads produce better results?

The research agent relies on WebSearch to find information. Companies with strong web presence (press coverage, blog posts, social media) give the agent more to work with. Very small or new companies may return limited findings, which cascades into less personalized emails.

Seeing rate limit errors on repeated runs?

Airtable's free tier allows 5 requests per second. If you run the pipeline multiple times in quick succession, wait 10-15 seconds between runs. Resend's free tier allows 100 emails per day, so you have plenty of room for testing.

Help me fix rate limiting errors in my pipeline

Your multi-agent pipeline is fully operational. You have a coordinator that delegates to specialized agents, real business tool integrations, and a repeatable process you can run on any lead. Next up, you will tackle the Secret Mission and add intelligent lead scoring to make routing decisions automatically.

Secret mission

Add Intelligent Lead Scoring and Conditional Routing

Right now your pipeline treats every lead the same. A Fortune 500 CEO gets the same treatment as a freelancer with no web presence. Upgrade your coordinator with smart decision-making that scores leads and routes them down different paths based on research quality and fit.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses free-tier cloud services (Airtable and Resend) with no ongoing costs. Anthropic API charges only apply when you actively run the pipeline.

Resources you used:

  • Local project folder: multi-agent-pipeline/ with main.py, .env, and tools/ directory.
  • Anthropic API key (stored in .env).
  • Airtable account with "Lead Pipeline" base and test records.
  • Resend account with API key.

Keep everything running

No action needed. Choose this if you plan to keep experimenting with your pipeline, testing new leads, or building on the multi-agent architecture.

  • Your Airtable base stays active on the free tier (up to 1,000 records).
  • Your Resend account stays active on the free tier (3,000 emails/month).
  • Your Anthropic API key remains active. You are only charged when running the pipeline.

Pause - I'll come back to this later

Keep your files and accounts but remove active credentials to avoid accidental usage.

  • Open your .env file and replace your ANTHROPIC_API_KEY value with placeholder. This prevents accidental API charges if you run the script.
  • Keep the multi-agent-pipeline/ folder on your Desktop for when you return.
  • Your Airtable and Resend accounts will remain free and ready to use when you come back.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Delete the multi-agent-pipeline/ folder from your Desktop.
  • Revoke your Airtable Personal Access Token at https://airtable.com/create/tokens.
  • Delete test records from your "Lead Pipeline" base, or delete the base entirely from Airtable.
  • Revoke your Resend API key in the Resend dashboard under API Keys.
  • If you no longer need your Anthropic API key for other projects, revoke it in the Anthropic Console.

That's a Wrap!

That's a Wrap!

Nice work! You just built a production-grade multi-agent automation pipeline that processes leads end-to-end using the Claude Agent SDK.

You've learned how to:

  • Built three specialized sub-agents that each handle a different part of the sales pipeline using isolated tools and focused system prompts.
  • Integrated real business tools (Airtable CRM + Resend email) via API to create a working automation you can demo to clients.
  • Created a coordinator agent that orchestrates the full workflow automatically, delegating tasks and passing context between sub-agents.
  • Added intelligent lead scoring that makes smart routing decisions based on research quality and fit.

Ready to quiz yourself?