# Worked example: a first-reply support agent, start to URL (/docs/agent/worked-example)

---
type: Example
title: "Worked example: a first-reply support agent, start to URL"
description: "The book's own Tier-1 support agent taken through every page of this room: the three gates, the spec, the first test, the agent and its two tools, the harness, the eval that catches the wrong-account reply, and the done-when checks at the live URL."
status: stable
order: 17
generated: { by: "process:claude-code", at: 2026-09-09T07:45:00Z }
verified:
  - { by: "human:rrizwan98", at: 2026-09-03T08:20:00Z }
  - { by: "human:rrizwan98", at: 2026-09-09T07:47:26Z }
sources:
  - id: af-28
    title: "Is This an Agent Problem? The AI Agent Factory, Panaversity, 2026"
    resource: https://agentfactory.panaversity.org/docs/is-this-an-agent-problem
  - id: af-49
    title: "Eval-Driven Development for AI Employees: A Multi-Track Crash Course. The AI Agent Factory, Panaversity, 2026"
    resource: https://agentfactory.panaversity.org/docs/eval-driven-development-crash-course
  - id: af-50
    title: "Deploy Your Agent Harness to the Cloud: A Crash Course. The AI Agent Factory, Panaversity, 2026"
    resource: https://agentfactory.panaversity.org/docs/deploying-agents-crash-course
  - id: af-50-companion
    title: "deploying-agents-base, the companion code for the deployment crash course (src/maya_harness/agent.py). Panaversity, 2026"
    resource: https://github.com/panaversity/agentfactory-manufacturing/releases/latest/download/deploying-agents-base.zip
ksor:
  audience: [public]
  owner: human:rrizwan98
  approval: { by: "human:rrizwan98", at: 2026-09-09T07:47:26Z }
trust_tier: human-reviewed
build_id: sha256:4d4bff09bd0a1dcff0a0a50f20f7ef514fc471e2b1f9e81e0e7b6150b93c0793
dirty: true
---

The book carries one agent through its deployment course: Maya, a Tier-1
support agent for a software product. She answers a customer's first
message: she looks up the account, then drafts a reply. Her two tools are
deliberately simple stand-ins so the harness runs without any external
system behind it. [^af-50-companion] This page takes that same agent
through this room's pages in order, so you can see the shape of each step
on one small case before you do it on yours.

## Gates

**Gate 1.** Customer messages arrive in different words about the same
thing; there is no fixed rule that maps a message to its answer, so this is
AI work, not a tool. And the work is not only to reply in kind: it has to
look something up and then act on what it found, which is what makes it an
agent shape rather than a chatbot. [^af-28]

**Gate 2.** The companion runs Maya one message at a time, with a person
reading the draft; that is Mode 1, with a human at the end of every run. The
third time the same task is done the same way, run Gate 2: if all three
dials are up, often, same shape, worth it, that is the cue to cross it into
a worker. [^af-28]

**Gate 3.** Works from: the customer's first message, and the account id
when the message names one. Want at the end: a short reply in the requested
tone. Done when: the draft exists, and when an account was named, the draft
rests on that account's plan and status rather than on a guess. [^af-28]

## Spec, in the six sections

```markdown
# spec.md, First-reply support agent

## Goal
Answer a customer's first message with a correct, kind draft so a person
can send it instead of writing it.

## User Scenarios
- A message naming a known account gets a draft that reflects that
  account's plan and status.
- A message naming an unknown account gets a draft that says no account
  was found for that id.
- A message naming no account gets a draft without any lookup.

## Functional Requirements
FR-1 When a message references an account, look it up before drafting.
FR-2 The draft names what was found: plan and status, or "no account".
FR-3 The draft is short and in the requested tone; the default is friendly.

## Edge Cases & Rules
- The lookup returns more than one match: do not proceed with action
  tools until the matching account is identified; if it cannot be, stop
  and hand the message to a person.

## Out of Scope
- Sending the reply. Refunds, plan changes, or any change to an account.

## Acceptance Criteria
- [ ] acct_1001 in the message: the draft mentions plan Pro and status active.
- [ ] An unknown id: the draft says no account was found.
- [ ] No account in the message: no lookup call appears in the run.
- [ ] Two matches: no draft is produced; the run stops for a person.
```

The multiple-match rule is the book's own, from the eval walkthrough on the
[evals page](evals-before-shipping.md): the fix for the wrong-customer
refund was one targeted rule, "when the lookup returns multiple results, do
not proceed with action tools until you have identified which account
matches; if you cannot, escalate to a human". [^af-49] Here it is written
into the spec before any code exists.

## The first test, before the tool body

Keep the lookup a plain function with a tiny interface, so the test calls
only that and never touches the network, as on the
[tests page](tests-first.md). The two tests come straight from the first two
acceptance criteria:

```python
# tests/test_accounts.py, written FIRST
from chat_agent.accounts import find_account


def test_known_account_reports_plan_and_status():
    assert "plan Pro" in find_account("acct_1001")
    assert "status active" in find_account("acct_1001")


def test_unknown_account_says_so():
    assert find_account("acct_9999") == "No account found for id acct_9999."
```

Watch both fail, write the function, watch both pass. The `@function_tool`
below is a thin wrapper over that function.

## The agent and its two tools

The companion's agent, as shipped: [^af-50-companion]

```python
# src/maya_harness/agent.py, from the book's companion
from agents import Agent, function_tool

from .settings import settings

# A tiny in-memory account table. In production this would be a CRM call.
_ACCOUNTS = {
    "acct_1001": {"name": "Ada Lovelace", "plan": "Pro", "status": "active"},
    "acct_1002": {"name": "Alan Turing", "plan": "Free", "status": "past_due"},
}


@function_tool
def lookup_account(account_id: str) -> str:
    """Look up a customer account by id and return its plan and status."""
    account = _ACCOUNTS.get(account_id)
    if account is None:
        return f"No account found for id {account_id}."
    return (
        f"Account {account_id}: {account['name']}, plan {account['plan']}, "
        f"status {account['status']}."
    )


@function_tool
def draft_reply(customer_message: str, tone: str = "friendly") -> str:
    """Draft a short support reply to a customer message in the given tone."""
    return (
        f"[{tone} draft]\n"
        f"Hi there, thanks for reaching out. About: '{customer_message}'. "
        f"Here is what I can do to help."
    )


def build_agent() -> Agent:
    """Construct Maya. Model defaults to gpt-5.4-mini (the SDK default)."""
    return Agent(
        name="Maya",
        instructions=(
            "You are Maya, a Tier-1 support agent. Be brief and kind. "
            "When a customer references an account, call lookup_account first. "
            "Then call draft_reply to compose your answer."
        ),
        model=settings.model,
        tools=[lookup_account, draft_reply],
    )
```

One line in that file has aged: the docstring names `gpt-5.4-mini` as the SDK
default, which was true for the SDK release the companion was written
against; the default has since changed, and the probe on the
[build page](build-the-text-agent.md) prints the one your installed SDK will
use. Leave the model unset, as the previous pages do, and the code stays
correct across releases.

Two things to notice against the earlier pages. The instructions carry the
order of operations in words, and the spec's multiple-match rule belongs
there too as one more sentence; both are prompts, written under the
[prompting guide](../prompting/overview.md). And neither tool changes anything in the
world, so nothing here needs `needs_approval`; the moment a tool that sends
the reply or changes the account is added, that tool gets the gate.

## The harness and the container

Nothing changes from the [FastAPI page](fastapi-docker-vercel.md): the same
`main.py` with `GET /health` and `POST /runs`, the same Dockerfile, the same
`Dockerfile.vercel` copy, `PORT` set to `8000` and `OPENAI_API_KEY` on the
project. The book's boot condition holds here too: with no key, `/health`
still answers and `/runs` answers 503 with the message to add one.
[^af-50-companion]

## The eval that the tests cannot write

The golden set starts with the four acceptance criteria as four examples,
tagged easy (known account), easy (no account), medium (unknown id) and hard
(two matches), with a few reworded variants of each so the set is not one
sentence per case. The hard case is the one that matters: a run can produce
a perfect-looking draft for the wrong account, and only a tool-use eval that
asks "was the right argument passed?" catches it. [^af-49] Grade every
example at two levels, output and tool calls, as a pass rate across several
runs, and hold the change until the whole set passes, not just the case you
were fixing. [^af-49]

## At the URL

The [end-to-end page](test-end-to-end.md) in one line each: the smoke test
with `BODY` set to "Hi, I cannot log in to account acct_1001." prints a
draft that mentions Pro and active; the same test passes inside the
container; `/health` answers `ok` from your phone at the deployed URL; after
the quiet window the first request is slower and then `ok` again; the
golden set passes against the live `/runs`; and a message with two matching
accounts stops without a draft. [^af-50]

That is the whole room on one small agent. Yours will have different tools
and a different spec, and exactly the same steps. Where this agent needs
more than a session file, [give it a record](give-it-a-record.md) says what
it grows into; how to write the skill that runs it is on
[write a skill](write-a-skill.md).

[^af-28]: Is This an Agent Problem?, the three gates. The AI Agent Factory, Panaversity, 2026.
[^af-49]: Eval-Driven Development for AI Employees: A Multi-Track Crash Course, Concepts 3, 11 and 12. The AI Agent Factory, Panaversity, 2026.
[^af-50]: Deploy Your Agent Harness to the Cloud: A Crash Course, Decisions 1 to 3. The AI Agent Factory, Panaversity, 2026.
[^af-50-companion]: deploying-agents-base companion: `src/maya_harness/agent.py`, `src/maya_harness/main.py`, `README.md`. Panaversity, 2026.
