AI agent development without turning prod into a science fair

Table of Contents
Table of Contents
AI agent development that has to live in production is not a loop that can call anything. It is one job a user can name, a short list of tools your code owns, and a stop rule before the bill or the blast radius gets weird.
I have shipped the science fair version. An agent with twenty tools, no step cap, and a prompt that said “be helpful.” It looked clever in a demo. It stalled on the first real ticket that needed a write, a refund, or a private row. Then someone asked why the model was calling tools we never reviewed.
I’m Aris Setiawan. I ship Next.js and AI product work for client builds. I already wrote AI chatbot development that fits a real Next.js app, AI integration services: what to scope before you buy, and the API routes I trust. This post is AI agent development when the agent has to sit in that same product, not on a slide.
The offer page is Hire an AI developer. This is the practice, not a hire brochure.
Scope the agent first
I do not start with a framework. I start with three names on a page.

Job. What one outcome does the user get? Summarize this ticket. Draft a reply from these docs. Find the order and open a support case. If you need three outcomes, that is three agents or one agent with three explicit modes. “Help with ops” is not a job.
Tools. Which functions can it call? getOrder. searchDocs. createTicket. Each one has a schema, an auth check in your handler, and a reason it exists. I do not expose “run any SQL” or “call any internal API” because the model sounded confident in a walkthrough.
Stop. Max steps. Max tokens. When a human must review before a write. An agent without a stop is a cost and an incident waiting for a fuzzy prompt. I write the stop in the same brief as the job.
If those three are blank, I do not build. Chat without tools is still fine. An unbounded agent is not progress. It is surface area.
Model proposes. Code runs.
The loop I trust is boring on purpose.

The user sends a message into your route. The model proposes a tool call. Your handler checks auth, runs the tool, and returns the result. The model may propose again. You cap the rounds. The UI streams what the user needs to see.
The model never holds the database client. The model never holds the production key. The model never “just” calls a sibling service because the prompt asked nicely. Your code runs the tool. That is the whole product boundary.
I keep the agent next to the same App Router tree I use for chat. One POST owns the session. Tools live as named functions with schemas. Logs keep every proposal and every result. When something goes wrong, I want a line I can read, not a mystery in a vendor dashboard.
A tiny sketch of the shape:
// app/api/agent/route.ts
export async function POST(req: Request) {
const session = await auth();
if (!session?.user) {
return new Response("Unauthorized", { status: 401 });
}
const { messages } = await req.json();
// stream with a short tool list + maxSteps
// each tool handler checks session.user again
}
The framework can change. The rule does not: propose in the model, run in your code, stop when the budget says stop.
What I refuse
This is the part demos skip.

Unbounded tools. If the agent can call everything the signed-in user can call, you did not design an agent. You wrapped a confused admin panel in a prompt. Start with read tools. Add write tools only when the blast radius fits in one sentence.
Client keys. The browser does not hold the model key. The agent route fails closed without a session when the data is private. Rate limit per user. Same rules as the chatbot post.
Science-fair demos in prod. A weekend agent that emails customers, refunds orders, or edits CRM rows with no owner and no step cap is not a launch. It is a pager. Keep demos on a branch or a staging project with fake data.
No owner. Someone on your team owns who the agent acts as, which tools are live, and what happens when a tool fails. If that person is “the model,” you do not have an owner.
Cost without a cap is the quiet version of the same refuse list. Max steps. A cheaper model for routing. The hard model only for the final answer when you actually need it. Put those numbers next to the job, not in a post-launch surprise.
What I skip
I skip a custom model when retrieval plus a short tool list will do. Most product agents need your docs, your orders, your tickets. They need clean tools and a prompt that names the job.
I skip blending this with Cursor mentoring. Different offer. Different URL. This post is Build: wiring an agent into the product you ship.
I skip promising autonomy as the success metric. Correct, bounded, and cheap enough beats a long loop that sounds smart and writes the wrong row.
I skip a second hire page on this keyword. The commercial door stays Hire an AI developer. This URL is the practice page for AI agent development.
What I will take
I will take an existing Next.js app with auth already shipping, and a one-page brief that names the job, the tool list, and the stop. We ship read tools first. We put writes behind a review or a tight schema. We log every call.
I will take a demo agent that needs to become a product. Pull the key out of the client. Cut the tool list. Add maxSteps. Move the handlers behind the session. That week usually beats another framework bake-off.
If that is the ticket on your desk, the door is Hire an AI developer. Send the job, the tools, and the stop. I will tell you what I would wire first.



