AI Agents & Workflows

The bar: you can build an autonomous agent that plans a task, calls real tools to do the work, remembers what it learned, and hands the result back to a human without going off the rails.

A chatbot answers one question and forgets you exist. An agent is different. It holds a goal, decides what to do next, reaches out to the world to do it, checks its own work, and keeps going until the job is done. That loop — reason, act, observe, repeat — is what turns a language model into something that ships work instead of just talking about it.

This is where a lot of software is headed, and it's within reach. You already know how to call an API and write a function. An agent is mostly those two skills wired into a loop with guardrails. The hard part isn't the model; it's the engineering discipline around it — clear tool contracts, real error handling, honest memory, and a human in the loop when the stakes are high.

20.1 Agent Architecture

Objective: design goal-driven agents whose behavior you can actually reason about.

Every serious agent breaks into four moving parts. Perception is how the agent takes in the world — the user's request, the results of a tool call, the current state of a document. Reasoning is where it plans: given the goal and what it knows, what's the next step? Action is tool use — the agent reaches out and does something, like running a search or writing to a database. Reflection closes the loop: the agent evaluates its own output, notices when it's wrong or stuck, and corrects.

Think of these as the OODA loop with a language model in the middle. The reason to keep them separate in your code is debuggability. When an agent misbehaves, you need to know whether it misread the input, planned badly, called the wrong tool, or failed to catch its own mistake. If all four are tangled into one prompt, you're blind.

The common trap for self-taught engineers is skipping reflection entirely. They build perception-reason-act and ship it. Then the agent confidently returns garbage because nothing ever checked the work. Build the self-evaluation step in from day one, even if it's just a second model call asking "did this actually satisfy the goal?"

  • Drill: sketch a single agent as four labeled functions — perceive, reason, act, reflect — and trace one task through all four.
  • Drill: add a reflection step that re-reads the goal and the result and returns done or retry.
  • Drill: log every reasoning step so you can replay a run and see where it went wrong.

20.2 Tool Use

Objective: connect an agent to external systems so it can do real work, not just describe it.

A tool is a function the agent is allowed to call, described well enough that the model knows when and how to use it. The tool interface is a contract: a clear name, a plain-language description of what it does, and a typed schema for its arguments. Vague descriptions produce wrong calls, so write them like documentation for a junior engineer.

Execution and result handling is where reality bites. The tool returns data, and the agent has to fold that result back into its context to decide what's next. Error recovery matters just as much — tools fail, APIs time out, arguments come back malformed. A production agent catches the error, feeds it back to the model as an observation, and lets the agent try a different approach instead of crashing. Tool chaining is the payoff: search returns a list, the agent picks one, fetches its contents, then summarizes — three tools, one goal.

The mistake here is trusting tool output blindly. Never let an agent execute arbitrary shell or SQL that a model composed without validation. Constrain what each tool can do, validate arguments before you run them, and treat every result as untrusted input.

  • Drill: define a tool with a name, description, and JSON schema for its parameters.
  • Drill: wrap a tool call in try/except and return the error text to the agent as an observation.
  • Drill: chain two tools where the output of the first becomes the input of the second.

20.3 Memory Systems

Objective: give agents memory so they carry context across steps and across sessions.

Agents need four flavors of memory. Short-term is the conversation context — the recent messages and tool results the model can see right now. It's fast but bounded; the context window fills up, and old detail falls off. Long-term memory is persistent storage you write to and read back later, so an agent remembers a user or a project across sessions.

Episodic memory records specific experiences — "last time I tried this approach, it failed" — so the agent learns from its own history. Semantic memory stores structured knowledge, often as a knowledge graph, so the agent can reason over relationships instead of raw text.

The classic failure is stuffing everything into the prompt and calling it memory. That blows your context window, costs money, and buries the signal. Real memory means deciding what to persist, retrieving only what's relevant to the current step, and summarizing the rest. Retrieval is the skill, not hoarding.

  • Drill: keep a running conversation buffer and trim it when it exceeds a token budget.
  • Drill: persist agent state to a store and reload it at the start of the next run.
  • Drill: write an episodic log of attempted actions and their outcomes, then feed relevant past attempts back into the plan.

20.4 Frameworks

Objective: use agentic frameworks where they help, and know when to skip them.

LangChain gives you chains and pre-built agents — composable steps for prompting, tool calls, and retrieval. It's fast for getting started and standardizes a lot of plumbing. LangGraph models an agent as a state machine: nodes are steps, edges are transitions, and you get explicit control over loops, branches, and where a human intervenes. That structure shines when your agent's flow is more than a straight line.

Sometimes the right answer is a custom agent loop — a plain while loop that calls the model, parses its chosen action, runs the tool, and appends the result. It's a few dozen lines, and you understand every one of them. Frameworks add power and abstraction; they also add dependencies, version churn, and magic you have to debug when it breaks.

The framework-vs-custom decision comes down to honesty about your needs. If you're wiring one agent with three tools, a custom loop is clearer and lighter. If you're orchestrating branching state and multiple agents, a framework earns its weight. Reach for the standard library of your own code before you pull in a heavy dependency.

  • Drill: build the same simple agent twice — once with a framework, once as a raw while loop.
  • Drill: model a branching workflow in LangGraph with an explicit human-review node.
  • Drill: write a one-paragraph decision note justifying framework or custom for a given task.

20.5 Multi-Agent Systems

Objective: coordinate several specialized agents to solve problems one agent can't.

Instead of one generalist, you split the work. Specialization means each agent has a narrow job — a researcher, a writer, a critic — with its own tools and prompt. Narrow agents are easier to prompt well and easier to trust. Communication protocols define how they pass work: structured messages, shared state, or a queue, so one agent's output is another's input without ambiguity.

Coordination is the orchestration layer — who runs when, how results merge, how you avoid two agents stepping on each other. And human-in-the-loop is the guardrail that keeps autonomous systems safe: at defined checkpoints, a person approves, edits, or rejects before the agent proceeds. For anything that touches production, real users, or money, that checkpoint is non-negotiable.

The rookie mistake is going multi-agent too early. Coordination is genuinely hard, and most problems don't need it. Prove you can't solve it with one well-built agent before you add a second. When you do go multi-agent, keep the protocol between them boring and explicit.

  • Drill: split a task across two specialized agents that pass structured messages.
  • Drill: add a coordinator that decides which agent runs next and merges their output.
  • Drill: insert a human approval gate before any irreversible action.

20.6 Hands-On Lab: Research Agent

Objective: build an autonomous research assistant that plans, searches, analyzes, remembers, and reports its progress.

What you ship

A working research agent that a person can hand a question and walk away from. It should:

  • Separate planning from execution — first decide what to research, then carry it out step by step.
  • Implement search and analysis tools the agent calls to gather and make sense of sources.
  • Add persistent memory so findings survive across steps and the agent doesn't repeat work.
  • Create a progress UI that shows what the agent is doing in real time — current step, tools called, findings so far — so a human can watch and intervene.

On the VWC codebase

When you wire an agent into the Vets Who Code app, the tools it calls live in src/pages/api route handlers — the app runs the Pages Router, not the App Router the framework tutorials assume. Any persistent memory goes through the shared Prisma singleton (import prisma from "@/lib/prisma"), never a new PrismaClient() per file. A progress UI that fetches server-side data pulls it through getServerSideProps, and remember the tw- prefix on every Tailwind class or your styles silently do nothing.