Most automation decisions get made backwards. Someone sees a slick demo, picks a tool, then bends the problem to fit it. Six months later you're maintaining a Frankenstein workflow nobody understands.
The real question isn't "n8n or custom code?" It's "how much determinism do I need, and how often will this logic change?" Answer those two, and the stack picks itself.
Let's break it down like engineers, not vendors.
What n8n is actually good at
n8n is a workflow orchestrator. You get a visual canvas, 400+ integrations, and a fair-code license you can self-host. It shines when your automation is a deterministic sequence of known steps.
- New Stripe payment → enrich in Clearbit → create HubSpot deal → Slack the sales team.
- Form submission → validate → write to Postgres → send confirmation email.
- Nightly cron → pull API data → transform → push to a warehouse.
These are pipelines. The branches are finite. The failure modes are predictable. You could write this in code, but you'd spend a week rebuilding retry logic, credential management, and a scheduler that n8n gives you out of the box.
The unlock: n8n lets you drop into JavaScript when the nodes run out of road.
// n8n Code node - normalize messy lead data before it hits your CRM
const items = $input.all();
return items.map(item => {
const data = item.json;
const email = (data.email || '').trim().toLowerCase();
const domain = email.split('@')[1] || null;
return {
json: {
email,
domain,
isFreemail: ['gmail.com', 'yahoo.com', 'outlook.com'].includes(domain),
fullName: [data.first, data.last].filter(Boolean).join(' '),
score: domain && !['gmail.com'].includes(domain) ? 'warm' : 'cold',
},
};
});
That's the sweet spot: visual glue for the boring parts, code for the sharp edges.
Where n8n starts to hurt
n8n assumes you know the path. AI agents don't work that way. The moment your automation needs to reason, choose tools dynamically, or loop until a goal is met, you're fighting the tool.
Signs you've outgrown a workflow tool:
- Your canvas has 40+ nodes and three people are afraid to touch it.
- You're simulating agent behavior with nested If nodes and merge loops.
- The logic depends on model output that changes shape every run.
- You need fine-grained control over context windows, token budgets, or streaming.
n8n has AI Agent nodes and LangChain integration, and they're genuinely useful for simple tool-calling. But complex agentic behavior inside a visual editor becomes debugging hell. You can't set a breakpoint on a hunch.
What "custom AI agent" really means
A custom agent is code that decides what to do next. It holds state, calls tools, evaluates results, and retries with a different approach. You reach for this when the decision-making is the product, not the plumbing.
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "search_orders",
"description": "Look up customer orders by email",
"parameters": {
"type": "object",
"properties": {"email": {"type": "string"}},
"required": ["email"],
},
},
}]
def run_agent(user_message):
messages = [{"role": "user", "content": user_message}]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
# The model decides IF and WHEN to call search_orders.
# That control loop is the whole point.
return response.choices[0].message
With code you own the retry policy, the eval harness, the observability, and the ability to swap models without rebuilding a canvas. You also own the maintenance burden. That's the trade.
The build vs buy math nobody shows you
Forget list prices. The real cost signals are:
Change frequency. If the logic changes weekly, n8n's visual editing wins hard. Non-engineers can adjust a workflow. Nobody's editing your Python agent without a deploy.
Volume. At low volume, n8n's per-execution overhead is invisible. At millions of runs, self-hosted code is dramatically cheaper and faster.
Team. One engineer plus an ops team? n8n. A full platform team? Custom pays off.
Failure cost. If a wrong decision costs money or trust, you want the testability of code and the eval discipline that comes with it.
The framework
Map your task on two axes.
Determinism
High determinism (known steps, predictable branches) → n8n. Low determinism (reasoning, dynamic tool selection) → custom agent.
Change velocity
High change velocity by non-engineers → n8n. Stable, engineering-owned logic → custom.
Most real systems land in the corners:
- High determinism + high change → n8n, no debate.
- Low determinism + stable → custom agent.
- Low determinism + high change → this is the trap. Split it. Let n8n handle orchestration and triggers, and call your agent as an HTTP endpoint from a single node.
That last pattern is the one we deploy most for clients. n8n does the boring, reliable orchestration. The agent does the thinking. Neither tool pretends to be the other.
The honest default
Start with n8n. Ship the workflow this week. When a specific step needs reasoning that keeps breaking, carve that step out into a service and call it. Don't rewrite the whole pipeline to add one smart decision.
The teams that win aren't the ones with the fanciest agents. They're the ones who matched the tool to the shape of the problem and shipped before the demo excitement wore off.
Originally published at getmichaelai.com
Top comments (0)