A Practical Mental Model for Agents

January 3, 2026

I keep having the same conversation.

Someone says "we're building an agent," and I get excited. Then we spend the next week writing prompt templates, YAML configs, scattered .md files for "memory," and a growing pile of print statements because there's no other way to figure out what the thing is actually doing.

I've done this dance too many times in the past. Each time I think we'll do it better. Each time we end up with the same mess.

The word "agent" is doing too much work. It covers everything from "chatbot with tools" to "autonomous system that books flights." And because there's no shared mental model, every team reinvents the same mistakes. I've certainly reinvented my share.

So here's the mental model I keep coming back to. It's grounded in the research—I've read more agent papers than I'd like to admit—but optimized for the question I actually care about: how do you make this thing shippable?

An agent is a control loop

The simplest reframe, and the one that finally made things click for me: an agent is not "a model that answers questions." An agent is a control loop wrapped around a model.

Decide what to do next. Act—call a tool, query a system, write to a file. Observe the result. Update state and repeat.

That's it. Decide, act, observe, update.

This is why agents feel different from a chatbot. They're not single-pass inference. They're iterative systems that accumulate context and change behavior over time. (This is also why they're harder to debug. More on that in a bit.)

Planning, by the way, is just a strategy inside the Decide step. I spent way too long thinking of planning as a separate component before realizing it's just... how the loop chooses what to do next. Nothing magical.

Once you see agents as loops, a lot of things click into place.

Tools are where it touches reality

Tools are how the loop interacts with the world. APIs, databases, browsers, shells, internal systems.

Two things matter in practice. I learned both the hard way.

First, tool contracts. The inputs and outputs need to be structured enough to be dependable. If your tool returns freeform text that the model has to parse... you're going to have a bad time. I once spent two days debugging an agent that was failing because a tool sometimes returned "Success" and sometimes returned "Done." The model didn't know they meant the same thing. Two days.

Second, failure modes. Timeouts. Partial success. Schema drift. Rate limits. Permission errors. The usual suspects. The stuff we already know how to handle from building any distributed system—except now it's wrapped in enough abstraction that we forget it's there.

If you want a reliable agent, you don't "prompt harder." You harden the tool boundary the same way you'd harden any integration surface. This part isn't new. It's just good engineering that we somehow forget the moment someone says "AI."

State is not a vibe

The loop needs state. Otherwise each step is just a new demo.

State includes the current objective, what's already been tried, tool results, intermediate conclusions, open questions, constraints. The working memory for this run.

Here's what matters, and I cannot stress this enough: state is not a vibe. It's a concrete object. You should be able to inspect it, validate it, persist it.

I've seen teams describe their agent's state as "the conversation so far." That's not state. That's hope. When your agent does something weird at step 47, you need to be able to look at an actual object and ask: what did it believe at step 46? What changed?

If you can't explain what state the agent believes it's in, you can't debug it. You're just guessing.

Memory is a risk surface

When people say "memory," they usually mean "the agent can remember stuff." That sounds like a feature. In engineering terms, it's a risk surface.

Memory is data that persists across loop iterations or sessions. It can help—but it also introduces failure modes you didn't have before.

Stale facts. Contamination from irrelevant context. Security and privacy issues. Malicious instructions that survive across runs. (That last one keeps me up at night. Prompt injection is bad enough in a single request. Prompt injection that persists in memory? Yikes.)

A useful reframe I've adopted: memory is an interface. Treat it like one. Version it. Validate it. Constrain it. Don't just bolt it on and hope. I've bolted it on and hoped. It didn't go well.

Feedback is how agents improve without retraining

Here's where it gets interesting—and where I actually get excited about this stuff.

Agents can get better without changing model weights. They improve by feeding outcomes back into the loop.

There are a few common patterns. Reflection—generate a critique of what just happened and adjust the next step. Search—try alternate actions and compare outcomes. Evaluation—score outputs against tests, policies, or rubrics and gate progress.

This is where agent behavior starts to look like engineering. Tight loops. Explicit checks. Measurable deltas. You can actually reason about whether it's getting better, instead of just vibing on whether the outputs "feel right."

The Reflexion paper blew my mind when I first read it. The idea that you could get meaningful improvement just by having the model reflect on its own failures? Without any gradient updates? It felt like cheating. (It's not. It's just good feedback design.)

The stack

If you want a compact model that covers most agentic systems, it's this:

Loop. Tools. State. Memory. Feedback.

The loop is the control structure—decide, act, observe, update. Tools are interfaces to external systems. State is the working set for the current run. Memory is what persists beyond the current step. Feedback is how the agent adapts.

You can swap implementations. But these parts show up again and again. Once I started looking for them, I couldn't unsee them.

Why this model helps

When something goes wrong—and something always goes wrong—this framing gives you a place to look.

Agent is inconsistent? State and memory are probably unstable. Fails in production but works in demos? Tool boundary and failure modes. (This one is my personal nemesis.) Can't debug regressions? You're missing durable artifacts of the loop. Feels unsafe? You don't have explicit gates on actions.

These aren't mysteries. They're system design problems. And system design problems have solutions. We've been solving them for decades. We just need to remember what we already know.

The minimum shippable agent

If you want to ship an agent without turning it into a research project, start here:

  • Typed tool contracts: structured inputs and outputs, not freeform parsing.
  • Inspectable state: a real state object you can validate and persist.
  • Trace by default: record prompts, tool calls, outputs, and decisions as artifacts.
  • Gates before actions: explicit policy checks that can allow, require review, or block.

If you have those four, you can debug regressions, measure improvement, and control risk. Everything else is iteration.

This is basically the checklist I wish someone had given me 6 months ago. Would've saved me a lot of late nights staring at logs that didn't tell me anything.

The real takeaway

If you treat agents as a control-loop system—not a magical chatbox—you get leverage.

Define tool contracts. Make state inspectable. Constrain memory. Add explicit feedback and gates. Measure behavior over time.

That's how agents become software you can actually ship.

And honestly, that's the part I find exciting. Not the magic. The engineering. Taking something that feels unpredictable and making it hold up under real constraints. That's the work I want to do. That's the work I'm doing with Noesis.

If you're building agents and fighting the same battles, I'd love to hear about it.

References