Technical guide · Agentic AI

Agentic AI Development: A Practical Workflow Guide

Most agentic AI projects fail not because the models are weak, but because the workflow is too clever. Here's a keep-it-simple playbook for building agents that actually ship — with the orchestration patterns we use in production.

What "agentic" actually means

An AI agent is a loop: a language model decides what to do next, calls a tool, observes the result, and decides again — until the task is done. That's it. No magic. The "agentic AI development" work is designing the loop, the tools, and the guardrails around it.

The minimum viable agent is roughly 40 lines of code:

// agent.ts — the whole loop
import OpenAI from "openai";
const client = new OpenAI();

const tools = [
  {
    type: "function",
    function: {
      name: "search_docs",
      description: "Search internal docs by query.",
      parameters: {
        type: "object",
        properties: { query: { type: "string" } },
        required: ["query"],
      },
    },
  },
] as const;

async function runAgent(userMessage: string) {
  const messages: any[] = [
    { role: "system", content: "You are a helpful engineering assistant." },
    { role: "user", content: userMessage },
  ];

  for (let step = 0; step < 8; step++) {
    const res = await client.chat.completions.create({
      model: "gpt-4.1-mini",
      messages,
      tools,
    });
    const msg = res.choices[0].message;
    messages.push(msg);

    if (!msg.tool_calls?.length) return msg.content;

    for (const call of msg.tool_calls) {
      const args = JSON.parse(call.function.arguments);
      const result = await dispatch(call.function.name, args);
      messages.push({
        role: "tool",
        tool_call_id: call.id,
        content: JSON.stringify(result),
      });
    }
  }
  throw new Error("Agent exceeded step budget");
}

The four pieces every agent needs

  1. Tools — typed functions the model can call.
  2. A loop — bounded, observable, resumable.
  3. Memory — what to carry forward, and what to drop.
  4. Evals — a way to know it still works tomorrow.

If you can name each piece in your own system, you're 80% of the way to a maintainable agent. If you can't, no framework will save you.

Orchestration: pick the simplest shape that works

Three orchestration shapes cover almost every real workflow. Pick the smallest one.

1. Single agent + tools

One model, a handful of tools, a bounded loop. This handles more use cases than people expect — support triage, doc Q&A, code review, data lookups. Start here always.

2. Router → specialist

A cheap classifier picks a specialist agent (billing, code, data). Each specialist is still a single agent with its own tools. Cost drops, latency drops, evals get easier.

async function route(input: string) {
  const label = await classify(input); // small/cheap model
  switch (label) {
    case "billing": return billingAgent.run(input);
    case "code":    return codeAgent.run(input);
    default:        return generalAgent.run(input);
  }
}

3. Plan → execute → verify

For multi-step work (migrations, refactors, research), have one call produce a plan, execute steps with a worker agent, then a verifier agent checks each output. Three named roles beat a swarm of anonymous "agents" every time.

const plan = await planner.produce(task);      // returns steps[]
const outputs = [];
for (const step of plan.steps) {
  const out = await worker.run(step, { context: outputs });
  const ok  = await verifier.check(step, out);
  if (!ok.pass) outputs.push(await worker.retry(step, ok.feedback));
  else outputs.push(out);
}

Tools: the boring part that decides everything

Rules we hold to, no exceptions:

  • One job per tool. "do_stuff" is not a tool.
  • Validate inputs with a schema. Zod, Pydantic — pick one.
  • Return structured JSON with a stable shape. No prose.
  • Fail loudly and specifically. The model can recover from clear errors.
  • Idempotent when possible. Agents retry.
import { z } from "zod";

const SearchInput = z.object({ query: z.string().min(1).max(200) });

export async function search_docs(raw: unknown) {
  const { query } = SearchInput.parse(raw);
  const hits = await vectorStore.search(query, { k: 5 });
  return {
    ok: true,
    hits: hits.map(h => ({ id: h.id, title: h.title, snippet: h.snippet })),
  };
}

Memory: keep it small, keep it typed

"Memory" for most apps is three buckets: short-term (this run), task state (checkpointed), and long-term (a searchable store). Don't stuff everything into the prompt — summarize between turns and store the raw somewhere queryable.

type AgentState = {
  runId: string;
  step: number;
  scratchpad: string;        // rolling summary, capped
  artifacts: Record<string, unknown>; // structured outputs
};

// Persist after every step so a crash is a resume, not a restart.
await db.state.upsert({ where: { runId }, data: state });

Guardrails you'll wish you had on day one

  • Step budget + wall-clock timeout on every run.
  • Token budget tracked per run, hard-fail on overrun.
  • Tool allow-list per agent role.
  • Structured logs of every model call and tool result — you'll live in these.
  • Human-in-the-loop checkpoints for destructive actions.

Evals: the thing that separates demos from products

Start with 20 hand-written examples of real inputs and expected behavior. Run them on every prompt or model change. That's it — you don't need an eval framework to start, you need a spreadsheet and discipline.

// eval.ts
const cases = [
  { input: "reset my password", expect: /reset|link sent/i },
  { input: "cancel subscription", expect: /cancelled|confirm/i },
  // ...18 more
];

for (const c of cases) {
  const out = await runAgent(c.input);
  console.log(c.input, "→", c.expect.test(out) ? "PASS" : "FAIL");
}

Frameworks: use one when the pain is real

LangGraph, CrewAI, OpenAI Agents SDK, Mastra, Vercel AI SDK — all fine. But adopt one because you hit a real problem (durable execution, graph orchestration, human approval steps), not because it looked good in a demo. Every framework is another abstraction you'll debug at 2am.

A production checklist

  • Every tool has a schema, a timeout, and a test.
  • Every run is checkpointed and resumable.
  • Every model call is logged with inputs, outputs, tokens, latency.
  • You have ≥20 evals that run in CI.
  • Destructive actions require a confirmation step.
  • You can answer "what did the agent do at 3:14pm?" in under a minute.

The takeaway

Agentic AI development is 20% model choice and 80% systems engineering. Small loop, typed tools, checkpointed state, real evals. Keep it boring. Ship it. Then get fancy only where the data says you have to.