Back to blog

7 min read · 2026-05-08

Using n8n as your AI tool layer

We host 50+ n8n workflows that the agent calls as tools. Here's why, and the painful gotchas we hit getting it stable.

Why n8n at all

Vex needs to do real work — search the web, scrape pages, render PDFs, send email, fetch RSS, query calendars. Each of those has good libraries you could `npm install` and call directly. But every time we write that code in our agent loop, we tightly couple it: changing the search backend means rebuilding the agent.

n8n is a workflow engine that exposes each capability as an HTTP webhook. You build the workflow once with their visual editor (or import JSON), exposePOST /webhook/pulse-search, and now anyone — Vex, your other apps, cron jobs — can call it. The agent doesn't care what's behind the webhook; it just sees a tool.

The pattern

// Vex's tool dispatch
case 'call_workflow': {
  const r = await fetch('http://127.0.0.1:5678/webhook/' + slug, {
    method: 'POST',
    body: JSON.stringify(args),
  });
  return await r.json();
}

That's it. The model sees one tool — call_workflow(slug, args) — and gets all 50+ workflows for free. The downside: the model has to know the slug, so we keep a registry of workflow names + descriptions in the system prompt.

The gotcha that wasted 8 days

n8n caches workflow definitions at runtime. If you UPDATE the workflow_entity table directly in the SQLite DB (which we did to fix a routing bug across 10 agent workflows simultaneously), the cached version keeps running. The user keeps seeing the old, broken behavior. Restarting n8n doesn't help — the cache rebuilds from a different table, workflow_published_version, which we forgot to update.

The fix that actually worked: hit n8n's REST API directly.

// 1. GET the current workflow
const wf = await fetch(`/api/v1/workflows/${id}`).then(r => r.json());

// 2. Strip read-only fields (active, createdAt, etc)
delete wf.active;
delete wf.createdAt;
delete wf.updatedAt;

// 3. PUT it back unchanged
await fetch(`/api/v1/workflows/${id}`, {
  method: 'PUT',
  body: JSON.stringify(wf),
});

That sequence forces n8n to recompile and refresh both caches. Once we did that for all 10 agent workflows, they finally answered queries correctly. The bug had been live for 8 days.

The other gotcha: credentials in REST PUT

When a workflow uses an HTTP node bound to a credential (OpenWeatherMap key, etc), the GET response includes a credential reference. If you PUT it back verbatim, you get SQLITE_CONSTRAINT: FOREIGN KEY constraint failed — n8n is strict about credential ownership. The fix is to strip credential bindings from the body before PUTing, then re-add them via the/credentials endpoint. We haven't fully solved this — pulse-weather still has issues, which is why Vex bypasses it and goes to wttr.in directly.

Auditing reality, not LLM claims

When asked "are all your workflows working?", the LLM will confidently say yes. We learned to ignore that and curl every webhook directly:

curl -X POST http://127.0.0.1:5678/webhook/pulse-pdf \
  -H 'content-type: application/json' \
  -d '{"user_id":"...","title":"audit","body":"..."}'

# HTTP 200, size=549, ok=true, file_id=8518d750-... ✓

Of our 17 non-ComfyUI workflows, 10 returned real responses on the first audit. The remaining 7 either needed user_id (the Prepare node throws on missing UUID) or specific args. Once given proper inputs, 16 worked. Only one was genuinely broken (pulse-weather, missing Respond node in the failure path).

When n8n is right (and when it isn't)

Right: capabilities that involve I/O — HTTP, email, file system, external APIs, scheduled triggers. The visual builder lets non-coders extend the system. Each workflow is a self-contained unit you can disable without touching the agent.

Wrong: stateful logic that mutates the agent's memory. The agent loop, intent classification, conversation state — that has to live in the agent itself. n8n can call it; it shouldn't BE it.

What we'd do differently

Use the n8n REST API from day one (don't go through the SQLite DB for any mutation). Keep credential bindings out of the workflow JSON entirely — store them in environment variables and reference by name. Write a smoke test that hits every webhook with a known payload, expects a known response, runs nightly.

The thread, again: verify with real bytes, not LLM claims.

Try Vex's tool layer

Pulse comes with the agent + n8n + 50+ workflows pre-wired. Self-hosted on your hardware.

Create your workspace