---
title: "Most AI agents shouldn't be agents"
description: "Over a few months I cut the number of agents in my production workflows by about five. What replaced them: a tiny model routing the request, the steps written out instead of decided at runtime, and the conversation memory loaded and stored by hand. Here is the canvas."
date: 2025-10-29
language: en
canonical: https://gduv.club/articles/ai-workflows-instead-of-agents
source: gduv.club
---
When agents landed in n8n I built a lot of them. You drop an agent node, you hang three tools off it, and it figures out which one to call. It felt like the whole thing had just become easy.

Over the following months I took most of them back out. In the workflows that had to scale and actually run in production, I'm down to about a fifth of the agents I had.

Not because agents are bad. Because for most of what I was doing, the agent was deciding things at runtime that I already knew at build time, and charging me for the privilege on every single turn.

[99% AI Agents Shouldn't Exist. Do This Instead (+ reliable, faster, cheaper)!](https://www.youtube.com/watch?v=BHdJFnx2wrc)

## The canvas I'm picking on

A support chatbot. Chat trigger, an agent on a large model, and one tool: an HTTP request to a retrieval endpoint that answers questions from my documents. Someone asks about the product, the agent calls the tool, reads what comes back and writes the reply.

**AI knowledge agent**

**When chat message received** → **AI Knowledge Agent** (gpt-4.1, Chat Model: OpenAI Chat Model (gpt-4.1); Memory: Simple Memory; Tool: Query knowledge base (HTTP request. The agent writes the query and decides when to call it))

_Four nodes. It is genuinely fast to build, which is most of why this shape is everywhere._

## Someone says hi, and it costs you a frontier model

Send "hi" to that agent. You get a friendly reply in about 1.3 seconds, written by one of the largest models available.

No tool was called. Nothing was retrieved. The entire system prompt went in as input tokens, including every line describing tools that were never touched.

| Step | What happens | Cost |
| :--- | :--- | ---: |
| The message arrives | "hi" | 1 token |
| The full system prompt goes in | Every tool description, every rule about when to call what | all of it |
| The large model decides | It decides not to call anything |  |
| The large model answers | "Hey, how can I assist you today?" |  |

_My prompt here was light. With five tools attached and the order they should be called in written out, the middle row is where most of the bill is._

The greeting is the easy case to see, but it's not a rare one. On a chatbot, a large share of messages need nothing retrieved and nothing looked up.

## The same thing, written out

Here is the replacement. It's more nodes, and every one of them is a decision I made once instead of a decision a model makes on every message.

**Smart and efficient RAG chatbot**

**When chat message received** → **Find past messages** (Memory manager, load mode, Memory: Simple Memory) → **Intent router** (Text classifier, two categories, Chat Model: Very small model (gpt-4.1-nano))

- _no knowledge retrieval needed_ → **Simple response** (Basic LLM chain on the same tiny model)

- _knowledge retrieval is needed_ → **Prepare retrieval query** (Basic LLM chain, gpt-4.1-mini) → **RAG via Lookio** (Plain HTTP request. No model in this step at all) → **Write the final response** (Basic LLM chain, gpt-4.1. The only large model on the canvas)

**Respond to Chat** → **Store messages** (Memory manager, insert mode)

_Three models across the canvas, each sized for its own step. The large one runs on exactly one node, and only on the messages that reach it._

### Route first, with a model that costs nothing

The Text Classifier's only job is to decide which path the message takes. Two categories, each with a description:

- **No knowledge retrieval needed.** Greetings, confirmations, small talk. Anything answerable without consulting anything.
- **Knowledge retrieval is needed.** The message needs information from the documents before it can be answered.

Classification is a light task and a small model is very good at it. It's fast enough that the step barely registers in the total latency, and cheap enough that I stopped thinking about it.

The cheap path is a basic LLM chain on that same tiny model with a short system prompt. "Hi" now comes back in about two seconds, reading exactly the same, on a model that costs a fraction per token of the one it replaced.

**The router needs the conversation, or it gets the follow-ups wrong**

"And for the second point?" needs retrieval, and reads like small talk on its own. So the classifier's prompt has the past messages appended, formatted as `human:` and `ai:` lines, with an instruction to focus on the new message and treat the rest as context. Without that, follow-up questions get routed to the cheap path and the bot answers from nothing.

One model node feeds both the router and the simple reply. They're different jobs but the same size of job, and having one place to change the model means I actually try a different one occasionally instead of editing two nodes and forgetting the second.

I use the same approach for the chat sidebar: [generate conversation titles on a schedule with a small model](/articles/ai-chat-conversation-titles) instead of making a title call on every message.

### Set the trigger to respond from a node

Small thing that blocks the whole design if you miss it. A chat trigger answers from the last node by default, and this workflow has two paths that both need to answer.

Set the trigger's response mode to respond from a node, then put a **Respond to Chat** node where the branches meet. Without it there's no way to have a cheap path and an expensive path that both reply, which is the entire point of routing.

### The retrieval path, as three nodes

Watch what the agent did on a real question. The large model got called, spent 600ms, and decided to call the tool. Good decision. But the thing it produced with all that intelligence is one short query string, roughly a cleaned up version of what the user just typed. And to write it, the entire system prompt went in again.

**Jobs in the turn** (All on the large model)

- Decide that retrieval is needed
- Write a short search query
- Write the final answer from what came back

**What each one needs** (If you pick per step)

- A classifier, tiny model
- A rewrite, small model
- A large model, and here it earns it

_Only the last one benefits from the expensive model, and in the agent shape all three run on it._

So the path is three nodes rather than one agent.

**Prepare retrieval query** is a basic LLM chain on a mini model. Its system prompt: from the user message, formulate the short and concise query to send to the knowledge retrieval tool, and output it directly as a question. In my tests the small model wrote the same query the large one had.

**RAG via Lookio** is a plain HTTP request. The query goes in the body, the assistant ID and the mode are fixed values I chose rather than parameters a model can drift on. Most tools you attach to an agent are an API underneath, and the platform usually gives you the node ready to paste.

**Write the final response** gets the original message and the retrieved content, labelled, and writes the reply. This one stays on the large model, because the wording of the final answer is where the quality shows.

### Memory is yours to carry now

This is the part the agent was doing for you, and the part you have to put back.

An agent node takes a memory sub-node and handles it. Split the agent into steps and nothing does, so the conversation gets loaded once at the top and stored once at the bottom.

| Step | What happens | Cost |
| :--- | :--- | ---: |
| Find past messages | Memory manager in load mode, right after the trigger | once |
| Every prompt appends it | The router, the simple reply and both retrieval steps each get the history | ×4 |
| Respond to Chat | The reply goes back to the user |  |
| Store messages | Memory manager in insert mode: the user message and the answer | once |

_More wiring than a memory sub-node, and the same buffer underneath. What you get for it is that you can see, per step, exactly how much history that step was given._

It's more work. It's also the moment you notice that an agent was pushing the whole conversation into every single turn, and that a query-rewriting step needs the last two messages rather than the last twenty.

## What you get back

**Agent** (Decides at runtime)

- Large model on every turn, greeting or not
- System prompt and tool descriptions in every call
- It can skip a tool and answer from memory
- It can reorder things and you find out in production

**Explicit steps** (Decided at build time)

- Large model on one node out of five
- No tool descriptions anywhere
- The retrieval always runs, because it is a step
- The order is the order you drew

_The predictability is the one I'd have ranked last a year ago and rank first now._

The one that matters most to me is the third line. An agent skipping a tool and answering from what it happens to know is the failure you can't reproduce and can't test for. A workflow can't do it. The node is there, it runs.

## When I still reach for an agent

When I genuinely don't know the order. Open-ended research, where the second query depends on what the first one found. Anything where the number of steps isn't knowable in advance.

That's the test I use now: can I draw this on paper before it runs? If I can, an agent is paying a model to rediscover my drawing on every request.

## The same question, one level up

This is the habit that transfers. When I'm in Claude Code or Codex, a lot of what I do is the same shape as that support bot, and the expensive parts are the same parts: the whole context going in again for a step that needed a fraction of it, a model rediscovering something I already knew.

Knowing what a tool call costs, because you once wired one up by hand and watched the token count, is what lets you see it happening inside a harness that shows you almost nothing.

Go open your own agents and look at the executions. For each one, ask whether you could have drawn the steps yourself before it ran. Most of mine, I could.

## Sources

- [n8n: Text Classifier node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.text-classifier/)
- [n8n: Basic LLM Chain node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.chainllm/)
- [n8n: Chat Memory Manager node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.memorymanager/)
- [OpenAI: model pricing](https://openai.com/api/pricing/)