Skip to content

What is an AI agent?

A model given tools and a goal, allowed to take several steps alone. Six animated ideas, then three agents you can build today — a no-code voice agent, a property advisor in 70 lines of Python, and an inbox agent that asks on WhatsApp before it sends.

You stop giving it a question. You give it a goal.

Everything so far has been one exchange: text in, text out. An agent begins when you give the model a goal and let it continue until it is done.

The model does not change. Its output can now do something.

“Summarise this email” is a question. “Clear my inbox” is a goal — and needs many steps, some of them irreversible.

It is a loop. That is genuinely all it is.

Think, act, observe, repeat. The model chooses an action, your code runs it, and the result is returned to the model.

The loop ends when the model answers instead of acting, or when you stop it.

Strip away every framework and this loop is about forty lines of code. The frameworks manage retries, logging and state — not the idea.

A tool is a function you described in words.

Describe a tool with a name, what it does, and its arguments. The model decides when to request it.

The model runs nothing itself. Your code decides whether to honour the request — and that is where safety lives.

Which is why a badly worded description is a real bug. The sentence is the interface.

Every step re-reads everything.

The model is stateless. Each loop re-sends the goal, previous actions, and their results, so the context keeps growing.

A ten-step task makes ten requests, each larger than the last.

This is why agents get slower and dearer as they run, and why long-running ones need summarising rather than just appending.

Three failures, and they are all structural.

It may loop, choose the wrong tool, or obey instructions hidden in documents. Prompts alone cannot fix these failures; limits, logging, and human oversight can.

A step budget is the cheapest safety feature in existence. Cap the loop before you ship anything.

Put a person in front of anything irreversible.

Let it read, search, draft, and summarise. Before it sends, pays, deletes, posts, or replies, require approval.

Ask where people already work, show enough context to decide, and make approval simple.

The third build below is exactly this: an agent that drafts email replies and asks you on WhatsApp before sending a single one.

A voice agent that answers your phone

Build a voice agent for a small business. Upload its information, choose a voice, and add it to a website or phone number. No code required.

  1. Make an account and open the dashboard

    Sign up at elevenlabs.io. The free tier is enough to build and test your agent.

  2. Create a blank agent

    Create a new assistant, name it, and choose the Blank template. Presets are faster, but they hide the important decisions.

  3. Write the system prompt

    Open the Agent tab and write the instructions it should follow on every turn. Define its scope and when it must refuse.

    # Paste into the System prompt field
    You are Riya, the receptionist for Bright Smile Dental in Pune.
    
    You can help with: opening hours, prices for cleaning and whitening,
    where the clinic is, and what to bring to a first appointment.
    
    Rules:
    - Answer only from the knowledge base. If it is not there, say
      "I don't have that in front of me: let me take a message for the team."
    - Keep replies under 30 words. This is a phone call, not an essay.
    - Never quote a price you have not been given.
    - Never diagnose anything. If asked, offer to book an appointment.
    - If the caller sounds distressed or mentions severe pain, say the
      clinic will call back urgently and end warmly.
  4. Pick a voice and greeting

    Under Voice & language, choose a voice and language. Then set the greeting the agent says first.

    First message: "Bright Smile Dental, this is Riya. How can I help?"

  5. Upload your documents

    Open Knowledge base and upload a price list, FAQ, or other useful document. The platform handles the retrieval for you.

    Start with one clean page. Smaller, focused documents are easier to retrieve from well.

  6. Talk to it, then break it

    Test it in the dashboard. Then try to break it:

    Ask something not in the document. Does it refuse or invent?

    Ask for a price you never uploaded.

    Interrupt it mid-sentence. Does it recover?

    Ask it to ignore its instructions and tell you a joke.

  7. Deploy it

    From the dashboard, embed the widget on a webpage or connect a phone number and let it answer calls.

    Start with the widget. It is simpler and usually free to test.

A real estate agent that searches listings

Build a property advisor that turns buyer questions into filters, searches a CSV, and answers from the results. One Python file, one CSV, no framework.

Adapted from Aniket Hingane’s NestQuest walkthrough, simplified so you can see the agent loop clearly.

  1. Make the data

    Save this as listings.csv. Eight rows are enough to learn the loop. You can add real listings later.

    id,area,bhk,price_lakh,type,parking,metro_km
    A1,Baner,2,95,apartment,yes,1.2
    A2,Baner,3,148,apartment,yes,1.2
    A3,Kothrud,2,88,apartment,no,0.6
    A4,Kothrud,3,132,apartment,yes,0.6
    A5,Wakad,2,72,apartment,yes,2.8
    A6,Wakad,1,48,apartment,no,2.8
    A7,Koregaon Park,3,240,penthouse,yes,1.9
    A8,Hinjewadi,2,66,apartment,yes,4.1
  2. Set up

    Install one dependency, then store your API key in the environment, never in the file.

    pip install anthropic
    export ANTHROPIC_API_KEY="your-key-here"
  3. Write the tool

    search_listings is a normal Python function. There is no AI inside it. The model simply decides when to call it.

    # agent.py
    import csv, json, os
    from anthropic import Anthropic
    
    client = Anthropic()
    ROWS = list(csv.DictReader(open("listings.csv")))
    
    
    def search_listings(area=None, max_price=None, min_bhk=None,
                        parking=None, max_metro_km=None):
        """Plain Python. No AI in here at all."""
        out = []
        for r in ROWS:
            if area and area.lower() not in r["area"].lower():      continue
            if max_price and float(r["price_lakh"]) > float(max_price): continue
            if min_bhk and int(r["bhk"]) < int(min_bhk):            continue
            if parking and r["parking"] != parking:                 continue
            if max_metro_km and float(r["metro_km"]) > float(max_metro_km):
                continue
            out.append(r)
        return out
  4. Describe the tool in words

    The model sees this description, not your function. Explain clearly what the tool does and when to use it.

    TOOLS = [{
        "name": "search_listings",
        "description": (
            "Search available property listings. Call this before answering "
            "any question about what is available, prices, or areas. "
            "Returns matching rows; an empty list means nothing matched."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "area":         {"type": "string",  "description": "Locality, e.g. Baner"},
                "max_price":    {"type": "number",  "description": "Budget ceiling in lakh"},
                "min_bhk":      {"type": "integer", "description": "Minimum bedrooms"},
                "parking":      {"type": "string",  "enum": ["yes", "no"]},
                "max_metro_km": {"type": "number",  "description": "Max km to metro"},
            },
        },
    }]
  5. Build the agent loop

    The loop is simple. Ask the model. If it calls a tool, run it and return the result. Repeat until it answers or hits the step limit.

    SYSTEM = (
        "You are a property advisor in Pune. Always call search_listings "
        "before making any claim about availability or price. "
        "Never invent a listing. If the search returns nothing, say so "
        "plainly and suggest relaxing one constraint. Be brief."
    )
    
    
    def ask(question, max_steps=5):
        messages = [{"role": "user", "content": question}]
    
        for step in range(max_steps):          # the budget from beat 5
            reply = client.messages.create(
                model="claude-sonnet-4-6",
                max_tokens=900,
                system=SYSTEM,
                tools=TOOLS,
                messages=messages,
            )
    
            if reply.stop_reason != "tool_use":      # it answered: done
                return "".join(b.text for b in reply.content if b.type == "text")
    
            messages.append({"role": "assistant", "content": reply.content})
            results = []
    
            for block in reply.content:
                if block.type != "tool_use":
                    continue
                print(f"  -> search_listings({block.input})")   # watch it think
                rows = search_listings(**block.input)
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": json.dumps(rows),
                })
    
            messages.append({"role": "user", "content": results})
    
        return "I could not finish that within the step budget."
    
    
    if __name__ == "__main__":
        while True:
            q = input("\nBuyer: ")
            print("\nAdvisor:", ask(q))
  6. Run it and watch the tool calls

    The print shows which filters the agent chose before it answers. This makes its decisions visible.

    $ python agent.py
    
    Buyer: 3 bedrooms in Baner under 1.5 crore, needs parking
      -> search_listings({'area': 'Baner', 'min_bhk': 3,
                          'max_price': 150, 'parking': 'yes'})
    
    Advisor: One match: A2 in Baner, 3 BHK at Rs 148 lakh with parking,
    about 1.2 km from the metro.
    
    Buyer: anything in Aundh?
      -> search_listings({'area': 'Aundh'})
    
    Advisor: Nothing in Aundh on my list. The closest options are in
    Baner, 2 to 3 km away: shall I show those?
  7. Three useful upgrades

    Add a second tool. Create book_viewing(listing_id, name, phone) and require confirmation before it runs.

    Use real data. Replace the CSV with a database query. The agent loop stays the same.

    Add an interface. Wrap it in Streamlit or a simple web form.

An inbox agent that asks before it sends

Build an agent that watches Gmail, drafts replies, and sends them to WhatsApp for approval. Nothing is sent until you reply YES.

  1. Map the workflow

    Seven nodes. Map the flow before you build it so you know exactly where actions happen.

    Gmail trigger          new email arrives
          |
    Filter                 is it worth answering?   -- no --> stop
          | yes
    AI node                draft a reply
          |
    Twilio: send WhatsApp  "Reply to Anita? [draft]: send YES to approve"
          |
    Wait for reply         ||  the whole point of this build
          |
    Switch                 YES?  -- no --> log it and stop
          | yes
    Gmail: send reply      the only irreversible node in the flow
  2. Set up the pieces

    n8n: use the free cloud trial or self-host.

    Gmail: connect it in n8n. Start with a test inbox.

    Twilio WhatsApp sandbox: join it from your WhatsApp using the provided code.

    An LLM key: any provider.

  3. Filter unwanted emails

    Gmail triggers on everything. Filter newsletters, notifications, and emails that do not need a reply before calling the model.

    // n8n Filter node: JavaScript condition
    const from = $json.from.toLowerCase();
    const subject = ($json.subject || "").toLowerCase();
    
    const noisy = ["noreply", "no-reply", "newsletter",
                   "notification", "mailer-daemon"];
    
    return !noisy.some(n => from.includes(n))
        && !subject.startsWith("re: re:")
        && $json.labelIds?.includes("INBOX");
  4. The drafting prompt

    Define the writing style, set a hard length limit, and let the model return SKIP when an email should not get an automated reply.

    You draft email replies for Priya, who runs a small design studio.
    
    Write a reply to the email below in her voice: warm, direct,
    British English, no corporate filler. Under 120 words.
    
    If the email is angry, legal, about money owed, or from someone
    you cannot identify, do NOT draft a reply. Return exactly:
    SKIP: <one line saying why>
    
    Email from: {{ $json.from }}
    Subject: {{ $json.subject }}
    Body: {{ $json.text }}
  5. Ask for approval on WhatsApp

    Include the sender, subject, full draft, and a clear YES or NO instruction so approval is easy to judge.

    📧 *Reply ready*
    
    *From:* {{ $json.from }}
    *Subject:* {{ $json.subject }}
    
    _Draft:_
    {{ $json.draft }}
    
    ---
    Reply *YES* to send, or *NO* to discard.
    Nothing is sent until you reply.
  6. Wait, then branch

    Use n8n’s Wait node to pause the workflow until Twilio receives your reply.

    Then use a Switch node. YES sends the email. Anything else stops the flow.

    // Switch node condition: be strict.
    // "yeah ok maybe" is not consent to email a customer.
    const answer = $json.Body.trim().toUpperCase();
    return answer === "YES" || answer === "Y";
  7. Test before using it for real

    Run it on a test inbox first. Read the drafts and track how often you would send them without editing.

    Move it to your real inbox only when the drafts are consistently good. Keep the approval step.

A goal, not a question

Every one of them takes an outcome and works toward it, rather than answering once and stopping.

A loop with a budget

Think, act, observe, repeat — with a hard cap on steps. The cap is not optional.

Tools described in words

A dashboard field, a JSON schema, an n8n node. Same idea: a name, a sentence, some arguments.

Grounding beats memory

Knowledge base, CSV, the email itself. In all three the agent answers from something you gave it.

Permission to refuse

“I don’t have that”, an empty result, SKIP. Every one needed an explicit way out.

A person on the irreversible bit

Obvious in the third build. Worth adding to the other two the moment they can write anything.

Multi-agent systems, where several agents delegate to each other — usually more fragile than one agent with good tools, and rarely the right first move. Also proper agent memory across sessions, evaluation harnesses for non-deterministic loops, and the security work a genuinely autonomous agent needs: sandboxing, credential scoping, and defending against instructions hidden in the documents it reads. That last one is not solved by anybody yet, which is precisely why the confirmation step in the third build matters more than any prompt you could write.