Building with Claude
This is where you build your own product on Claude rather than using someone else’s app. It’s the Claude-track counterpart to the Copilot Capstone and Building Your Own Agents — the Messages API, the agentic loop, tools, and the production levers that make it reliable.
This is how you build your own app powered by Claude. You send Claude messages, hand it a box of tools it’s allowed to use, and let it work in a loop: it asks to use a tool, you run the tool and give back the result, and it decides what to do next — over and over — until it says “I’m done.”
The Messages API — the foundation
☺ Like you’re 10: Talking to Claude is like passing notes through one special mail slot: you push in the whole conversation so far, and Claude slides one note back. Every trick in this lesson is just fancier ways of using that same slot.
Everything Claude does over the API goes through one endpoint: you send a list of messages (and an optional system prompt), and get a reply back. That’s it.
import anthropic
client = anthropic.Anthropic() # reads your API key from the environment
resp = client.messages.create(
model="claude-sonnet-4-x", # pick a tier; versions move
max_tokens=1024,
system="You are a concise coding assistant.",
messages=[{"role": "user", "content": "Explain MCP in one sentence."}],
)
print(resp.content[0].text)
print(resp.stop_reason) # why it stopped: "end_turn", "tool_use", ...The reply’s content is a list of blocks (text, or tool calls), and stop_reason tells you why Claude stopped. Those two facts are the whole basis of building an agent.
Benny the Beaver: I’m building an app on the Claude API. I put the whole conversation into one messages list and send it off.
Pip the Hummingbird: I fly that list to Claude and carry the reply back — a stack of content blocks plus a stop_reason.
Foxy: But how do I know if it’s finished, or if it wants a tool?
Professor Owl: You read the stop_reason, never the words. tool_use means run the tool and loop again; end_turn means Claude’s done.
The agentic loop: driven by stop_reason
☺ Like you’re 10: Imagine a board game where Claude either says “your turn — go grab me this piece” or “I’ve finished my move.” You keep taking turns and fetching pieces as long as it asks, and you only stop when it says it’s done.
You don’t ask Claude to “be an agent.” You run a loop and let stop_reason steer it: keep going while Claude wants a tool, stop when it’s finished.
Stop on stop_reason, not by reading Claude’s text for the word “done” and not with a fixed iteration cap as the primary stop (a cap is only a safety net). This is tested directly in CCAR-F, and it’s the same rule from Agentic AI.
Tool use
☺ Like you’re 10: A tool is like a labelled button you hand Claude — the label explains what the button does and what to type before pressing it. Claude can’t press it itself, so it asks you to, and you tell it what happened.
A tool is a name, a description, and a JSON schema for its inputs. The description is what Claude reads to decide when to use it — so write it well.
tools = [{
"name": "get_weather",
"description": "Get the current weather for a city. Use for any 'weather in X' question.",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string", "description": "City name, e.g. 'Pune'"}},
"required": ["city"],
},
}]
# In the loop, when resp.stop_reason == "tool_use":
# 1. find the tool_use block (it has .name, .input, .id)
# 2. run your real function with .input
# 3. send a new user message containing a tool_result block (tool_use_id = .id)
# 4. call messages.create again — Claude continues with the resultUse tool_choice to steer: {"type": "auto"} lets Claude decide, {"type": "any"} forces some tool, or name one to force it.
The production levers
☺ Like you’re 10: These are three shortcuts, like a bike getting gears. One shows the answer word-by-word so it feels fast, one saves stuff you keep repeating so you don’t redo it, and one runs a big pile of jobs overnight for cheaper.
Three features turn a demo into something you can run at scale:
| Lever | What it does | Use it when |
|---|---|---|
| Streaming | Sends the reply token-by-token over SSE. | Any chat UI — it feels instant. |
| Prompt caching | Caches a stable prefix (big system prompt, docs) so you don’t re-pay for it each call. | Reused context; not high-churn content. |
| Message Batches | Runs many requests asynchronously for roughly half the cost. | Bulk, non-interactive jobs. |
Structured output
☺ Like you’re 10: Instead of letting Claude write a messy paragraph, you give it a form with labelled blanks to fill in — like a fill-in-the-blanks worksheet. If it colours outside the lines, you check it and politely ask it to try again.
Need clean JSON, not prose? Define a tool whose schema is your output shape and force it with tool_choice, then validate what comes back and re-prompt on a mismatch. Enforcing structure in code beats hoping the prompt is obeyed — the same “programmatic over prompt-based” principle from the CCAR-F track.
Don’t rebuild the loop: the Agent SDK
☺ Like you’re 10: You could build a robot friend from loose LEGO bricks yourself, or grab the ready-made kit that already snaps together. The Agent SDK is that kit — the same one Claude Code is built from — so you skip the fiddly plumbing.
The Claude Agent SDK packages the whole loop — tool execution, permissions, subagents, and lifecycle hooks — so you build on the same runtime that powers Claude Code instead of hand-rolling the plumbing. Reach for the raw Messages API when you want full control; reach for the SDK when you want a production agent fast.
You can make a Messages API call, run a tool loop keyed on stop_reason, define a tool with a JSON schema, and name the three production levers (streaming, caching, batching). This closes the Claude track — from chat, to Claude Code, to your own build. For the credential, head to the CCAR-F hub.
Check your answers
- Make a Messages API call: Call
client.messages.create()with a model tier,max_tokens, an optionalsystemprompt, and amessageslist. The reply’scontentis a list of blocks (text or tool calls) andstop_reasontells you why Claude stopped. - Run a tool loop keyed on
stop_reason: Loop whilestop_reason == "tool_use"— run the requested tool and append atool_result— and stop when it’send_turn. Read thestop_reason, never Claude’s words, and don’t use a fixed iteration cap as the primary stop (it’s only a safety net). - Define a tool with a JSON schema: A tool is a
name, adescription, and a JSONinput_schemafor its inputs. The description is what Claude reads to decide when to use it, so write it well; usetool_choiceto steer whether a tool is optional, forced, or a specific one. - Name the three production levers: Streaming (sends the reply token-by-token so a UI feels instant), prompt caching (caches a stable prefix so you don’t re-pay for it each call), and Message Batches (runs many requests asynchronously for roughly half the cost).