"I already built this in Zapier, why would I need an agent?" A year ago the answer was easy. Not anymore — Zapier shipped AI Orchestration in July 2026, Make shipped AI Agents in its canvas earlier the same year. "Does a model decide something" stopped being the dividing line.
This post continues my series on building agents. The previous one covered writing a good agent prompt.
What's actually still different
A trigger-action platform's workflow shape — which apps, roughly what order — is drawn by a human ahead of time in the builder, even when one step now delegates to a model. An agent you build yourself, the way this series has, decides that whole shape at runtime: which tools apply, in what order, for this specific message.
flowchart TD
subgraph Fixed automation
F0[Any trigger] --> F1[Step A, always] --> F2[Step B, always]
end
subgraph Agent
A0[Any message] --> AD{Model reads it}
AD -->|only A applies| A1[Step A]
AD -->|only B applies| A2[Step B]
AD -->|both apply| A3[Step A and Step B]
AD -->|neither applies| A4[No tool, just reply]
end
Proving it in code
// agent/fixed-vs-agent.ts
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const expenses: { item: string; amount: number }[] = [];
const log: string[] = [];
function addExpense(item: string, amount: number) {
expenses.push({ item, amount });
return { ok: true, recorded: expenses.length };
}
function getTotal() {
return { total: expenses.reduce((sum, e) => sum + e.amount, 0) };
}
function notify(message: string) {
log.push(message);
return { sent: true };
}
function runFixedAutomation(item: string, amount: number) {
const step1 = addExpense(item, amount);
const step2 = notify(`Recorded: ${item} (${amount})`);
return { step1, step2 };
}
const tools: Anthropic.Tool[] = [
{
name: 'add_expense',
description: 'Record one expense. Use it whenever the user says they spent money.',
input_schema: {
type: 'object',
properties: { item: { type: 'string' }, amount: { type: 'number' } },
required: ['item', 'amount'],
},
},
{
name: 'get_total',
description:
"Return the exact sum of every expense recorded so far. Use this whenever the user asks for a total, a sum, or how much they've spent overall.",
input_schema: { type: 'object', properties: {} },
},
{
name: 'notify',
description: 'Send the user a short notification. Use this only when the user explicitly asks to be notified or reminded.',
input_schema: {
type: 'object',
properties: { message: { type: 'string' } },
required: ['message'],
},
},
];
function execute(name: string, input: Record<string, unknown>): unknown {
if (name === 'add_expense') return addExpense(String(input.item), Number(input.amount));
if (name === 'get_total') return getTotal();
if (name === 'notify') return notify(String(input.message));
return { error: 'unknown tool' };
}
async function runAgent(userText: string): Promise<string> {
const messages: Anthropic.MessageParam[] = [{ role: 'user', content: userText }];
for (let step = 0; step < 4; step++) {
const response = await client.messages.create({
model: 'claude-sonnet-5',
max_tokens: 300,
tools,
messages,
});
messages.push({ role: 'assistant', content: response.content });
if (response.stop_reason !== 'tool_use') {
const block = response.content.find((b): b is Anthropic.TextBlock => b.type === 'text');
return block?.text ?? '';
}
const results: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === 'tool_use') {
results.push({
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify(execute(block.name, block.input as Record<string, unknown>)),
});
}
}
messages.push({ role: 'user', content: results });
}
return 'Stopped: step limit reached';
}
runAgent('I spent 500 on lunch.').then(console.log);
runFixedAutomation always runs both steps, regardless of content. runAgent, same three tools, picks a different path for "I spent 500 on lunch" (just add_expense), "what's my total" (just get_total), and "I spent 200 on tea, notify me" (both) — decided by the model each time, not written into the function.
The near-miss writing this
The first draft argued no-code tools can't reason, full stop. Checking current reality before publishing caught that it was already wrong — both platforms shipped real agent features this year. The fix was rebuilding the comparison around what's still true, not whether a model is involved at all.
Common mistakes
- Assuming "no-code" still means "no reasoning."
- Treating a platform's AI Agent feature as equivalent to building your own.
- Writing custom code for a job the platform's library already covers.
- Forcing a complex, stateful job into a platform's bounded actions.
Read the full version
I write each post in English and Bangla. The full version is on my blog: AI Agent vs Zapier/Make
Where's your line between "use the platform" and "build it yourself"?
Top comments (0)