DEV Community

Cover image for AI Agent vs Zapier/Make in 2026: The Comparison That's Actually Still True
Zahid Hasan Tonmoy
Zahid Hasan Tonmoy

Posted on Originally published at zahidhasantonmoy.vercel.app

AI Agent vs Zapier/Make in 2026: The Comparison That's Actually Still True

"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);
Enter fullscreen mode Exit fullscreen mode

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)