Your First Tiny Agent
A complete think-act-observe loop: fake model, two tools, a transcript, a step budget, and a final answer for job 17.
This lesson puts the whole Getting Started track into one small program. You will run a file-answering agent with no paid API. A fake model chooses tools from simple rules. The surrounding code is production-shaped: named tools, a transcript, a budget.
A transcript is the ordered list of messages the policy is allowed to see: the user goal, then tool results, then the next decision. The fake model is the policy. Later it becomes an LLM that must return JSON matching a tool name and arguments. The loop does not change.
People confuse this exercise with “a toy, so it does not count.” The toy is the model. The loop, the tools, the budget, and the finish action are the real thing. If you skip them and jump to a vendor SDK, you will still need them, only hidden.
The pieces in this program
| Piece | In this box | Later |
|---|---|---|
| Goal | “What is the status of job 17?” | Any checkable question |
| Environment | JOBS dictionary | A real job API |
| Actions | get_job, finish | Search, SQL, shell, tickets |
| Policy | fake_model | An LLM call that returns JSON |
| Memory | transcript list | Traces in a database |
| Budget | max_steps | Max steps plus max tokens plus spend |
A fake model picks the next tool. The loop, the transcript, and the budget are the real product.
A tiny agent with two toolsGoal. Ops wants the status of job 17. Done means a final sentence that used the job record, not a guess.
Environment. Two jobs exist: 17 failed with a vendor timeout, 42 is ok. Unknown ids return an error object.
Actions. get_job reads. finish stops with an answer. There is no third tool yet. That is a feature.
Policy. If the transcript has no tool result yet, call get_job for 17. If it has a result, call finish with a sentence built from status and error.
Memory. Each tool result is appended. The fake model looks at the last tool message. A real model would look at all of them.
Budget. Four steps. If finish never comes, return budget exceeded.
An ops person at 4 p.m.
Priya on-call sees “job 17 failed” in a dashboard with no error string. She should not paste a production key into a chat box. She should run an agent (or a workflow) that is allowed to call get_job and nothing else expensive.
Step 1: policy chooses get_job with job_id 17. Observation: status failed, error timeout talking to vendor. Step 2: policy chooses finish with a sentence that includes both. Stop. Two steps, under budget. If JOBS had no 17, the observation would be an error, and a better policy would finish with “not found” instead of inventing a status. Our fake model is naive: it still formats the error dict. That honesty is useful. You can see the weakness and fix the policy. A giant framework would hide the same weakness in three classes.
Write the stop rule in your head before you run: success is finish; failure is max_steps; there is no human handoff in this tiny version, but you can imagine ask_human as a third tool.
Run to execute this in your browser. Nothing is sent to a server.
The first printed step is get_job with job_id 17, and the result dict with status failed and the timeout error. The second step is finish with an answer string, and a result dict that has final. Then ANSWER: repeats that sentence: job 17 is failed, timeout talking to vendor. Then a probe of job 99 prints not found — the tool is honest when the environment has no record. The fake model always asks for 17, even if you change the goal string. That is the next weakness to fix (the exercise below).
What you just learned (the whole course in miniature)
- Goal — a question with a checkable answer
- Tools —
get_job,finish - Policy —
fake_model(later: a real LLM) - Transcript — the memory of the loop
- Budget —
max_steps
When you swap fake_model for an API call that must return JSON matching tool plus args, you have a real agent. The rest of Joeven is how to make that swap reliable: schemas, evals, RAG, planning, multi-agent, and production.
Exercise
Change the fake model so it can answer job 42 as well, by reading the goal string for a number. Then add a third tool list_failed_jobs. Keep max_steps. Do not add a paid API. If you parse no number, finish with a short “missing job id” instead of guessing 17.
Next track: Python, because the quality of your agents will never exceed the quality of your functions.
What goes wrong
- No
finishtool. The loop cannot stop except by blowing the budget. Always give the policy a legal way to end. - Guessing job 17 forever. The policy ignores the user’s number. That is our fake model’s bug. Fix it in the exercise.
- Swallowing
not found. The agent writes “all good” after an error dict. Append the error. Then finish with failure text. - Budget too high, no goal check. Four steps is small. Four thousand with a paid search tool is a bill.
- Printing secrets in the answer. Job error strings might contain tokens. Redact in real tools.
- Calling tools that are not in
TOOLS. A real model will try. Your execute line should catchKeyErrorand return an error result, not crash without a trace. - Replacing the loop with a chat SDK and no transcript. You cannot test. You cannot eval. You cannot bill per step.
- Skipping Python next. This box used functions, dicts, and a
forloop. The next track makes those boring and solid.
Where this goes next
Python is next on purpose. Then math, ML, transformers, LLMs. Then prompt, tools, RAG, agents. Then multi-agent, eval, production, projects. This tiny agent is the costume rack empty: no RAG, no swarm, no vendor. Keep it in your head when a framework diagram has twelve boxes. Twelve boxes still need a goal, tools, a transcript, and a budget.
How agents use this
Ship the shape you just ran. Swap the fake model last.
- Code:
TOOLSas a dict of callables.run_agent(goal, max_steps).fake_modelisolated so tests can inject a script. Parsejob_idwithint(...)and handle bad values. Build answer strings with concatenation orstrjoins, not with silent guesswork. - Logs: one print per step with name, args, and result — the classroom already does this. In production, write the same fields to a trace store, plus a run id.
- Tests: job 17 returns a sentence that contains
failedandtimeout. Job 99’s tool returnsnot found.max_steps=1returnsbudget exceededbecausefinishnever runs. A decision with a fake tool name does not kill logging. - Stop conditions:
finishreturns the final string;max_stepsreturnsbudget exceeded; later,ask_humanreturns control. Do not stop because the model wrote “done” insideget_job.
You now have the whole Joeven argument in one function: the model is replaceable; the loop is the product.
Check your understanding