
Source: github.com/software-trizzey/za
Why Build This
I'd been reading about agentic systems for a while: tools, context windows, MCPs, agent loops. I understood the concepts on paper. I could explain them to someone else. But I still didn't feel like I understood them, if that makes sense.
The docs got me oriented. Anthropic's tutorial on building an agent was a good introduction. I also found OpenAI's function calling doc valuable for learning more about tools.
Those tutorials were useful primers, but they didn't get me over the hump. For me, the "aha" moment only happens when I build something for myself and watch it break. So I decided to build Za, a focused agent that orders pizza.
The name is intentional: "za" as in "grab a slice of za." If that term is new to you, here's a fun Reddit debate. I'm clearly in the yes camp.
Why pizza? Mostly because the workflow is small enough to finish, but complex enough to actually learn from. You browse a menu, pick items, confirm the order, and check out. There's just enough surface area to touch the important parts of an agent system without disappearing into a product rabbit hole.
Building an MVP
To avoid one-shotting the whole system into existence, I decided to hand-code the important bits and keep myself engaged. It's been a while since I'd done that. Most of my day-to-day work is higher-level, so sitting down to write the agent harness, tool system, and context management from scratch felt good. I let AI help with glue code where it made sense, but I wanted the core primitives to come from my own keyboard. That was the whole point.
The first version of Za was dead simple. It read menu items from a JSON file, passed them to the model along with the user's order, and printed the final result to the terminal. No browser. No REPL. Just the loop.
But even that stripped-down version taught me something I hadn't fully grasped from reading. Tools in an agent system aren't magic. You define what tools exist, describe when and how to use them, and then write the handlers that do the actual work. It's just function calls with good labels. I'd read that before, but now I understood it.
I also used Bun for the first time on this project. Built-in env handling, a clean file reader, no complaints.
Handling Tool Errors
I had a working concept and figured the hard part was behind me.
It was not.
The happy path worked fine. Then the agent hallucinated a tool name. Next, it sent invalid JSON as a tool argument. After that, it sent valid JSON with the wrong argument types. Each failure needed its own handling strategy, and they kept coming.
The one that caught me off guard was when the agent tried to place an order without getting human approval first. I'd set up a rule saying Za had to confirm with the user before submitting, but the agent just skipped it and went straight to placing the order. So I had to build a safeguard that rejected the tool call and sent the error back to the agent with instructions to ask for confirmation. Getting that loop right took more iteration than I expected.
None of this should have surprised me. Regular software development is the same story: the feature takes a day, the edge cases take a week. But working through these failures gave me a real sense of what production agentic systems have to deal with. Guardrails aren't a nice-to-have. They're most of the work.
From Local Files to a Real Website
After patching edge cases and tidying up the codebase around clearer single-responsibility boundaries, I had something that worked end to end. Za could read a user's order from a file, place it on their behalf, and write the results to a local JSON memory file. That cache stored the last five orders and tracked a favorite for easy reordering on follow-up runs.
Not bad for a learning project. But ordering from a flat file felt like half the problem. I wanted to see the agent work against a real website.
I could have pointed Za at the actual Domino's site, but that pulls you into bot detection, auth flows, and payment handling fast. And probably a Terms of Service violation too. None of that was interesting for what I was trying to learn.
So I decided to build a fake one.
Enter Zamino's

The name came first. I was spoofing Domino's, and the agent was called Za.
So naturally: Za + Domino's = Zamino's.
Once I had the name, the rest of the joke needed a storefront. I didn't want to burn a week on frontend work for a mock site, so I tried Google Stitch, which had just been released. I took a screenshot of the Domino's pizza menu, passed it in with a short prompt, and Stitch spat out the site assets in one shot. From there I pointed my local coding agent at the output and had it wire everything up as a simple static site.
This endeavor took all of thirty minutes from start to finish. Tech is getting pretty wild.
V2: MCPs & Browser Automation
With Zamino's running, Za needed a way to actually interact with a browser. I already use the Playwright MCP server at work for validating things in the browser, so the mental model was there. The question was how to wire it into Za.
I expected this to be complicated. It wasn't.
After some research, I found @modelcontextprotocol/sdk and built the integration on top of that. I added MCP server config and plugged the external tools into Za's tool registry. The whole thing clicked into place faster than I expected, and it drove home something useful: MCPs are just third-party tool definitions. The agent treats them the same as any local tool. They're not a separate concept. They're just more tools.
Teaching Za to Navigate Websites
With Playwright wired up, I refactored Za for a browser-based ordering flow. The core concepts stayed the same: read the menu, get the user's order, place it, save the result. But now there was a new step: discovery.
Instead of hardcoding a menu URL, I let Za find the menu on its own. That was deliberate. It makes the agent more generally useful. In theory, Za could now work on any pizza website, not just Zamino's. After updating the tool definitions and tuning the system prompt, Za was placing orders from a live website end to end.
Another satisfying moment.
A Side Quest
During V2 I added a simple REPL using cac and @inquirer/prompts so I could talk to the agent without building a full UI.
That integration went smoothly, so I got ambitious.
I wanted the order confirmation step to route through Inquirer's confirm prompt. The idea was a cleaner, more deterministic flow: the model triggers a specific CLI prompt, reads the user's yes or no, and proceeds accordingly. Clean in theory.
In practice, though, the model surfaced the confirmation too early. Or it didn't read the result and asked for confirmation again. And again. Or it just ignored the prompt entirely and kept going. I spent more time fighting this one interaction than I did wiring up the entire MCP integration.
I eventually scrapped the approach and let the model handle confirmation on its own, in plain conversation. It worked immediately. The irony wasn't lost on me: trying to make that single step more deterministic injected way more randomness into the system as a whole.
Wrapping Up
Za is feature-complete, and I don't plan to take it further. I absorbed the concepts I set out to learn. The biggest thing that stuck with me is how much of building an agent is really building the scaffolding around the agent: tool error handling, guardrails, context management. The AI part is probably the easiest.
For the builders, I extracted the core agent loop into a reusable template as a starting point for your own project.
Check it below, and thanks for following the journey!
Agent Template
A reusable Bun-based agent runtime that I'm using in different agentic projects/ideas.
This template keeps runtime orchestration in src/core/* and puts domain behavior
in profile modules under src/profiles/*. New repositories can start with the
default profile, then replace it with their own domain-specific profile.
Architecture
-
src/core/*: domain-agnostic runtime (model adapter, session loop, tools, policies, CLI) -
src/profiles/default: starter profile (instructions, context derivation, providers, policy list, env) -
src/profile.ts: single selection seam for the active profile -
src/index.ts: app composition and lifecycle wiring
Use This as a Template
-
Create a new repository from this template and clone it:
- Private:
gh repo create my-new-agent --private --template software-trizzey/agent-template --clone - Public:
gh repo create my-new-agent --public --template software-trizzey/agent-template --clone
- Private:
-
Create your desired agent profile at
src/profiles/<your-profile>/index.ts. -
Export it from
src/profile.tsasactiveProfile. -
Add profile tests in
test/profilesand contract coverage intest/contracts/profileContractSuite.ts. -
Run validation:
bun run typecheck- …
Top comments (0)