The code is the structure, the LLM is the glue at the joints.
There’s a widespread misconception about automations “powered by artificial intelligence”: people picture a model you give an order to, which then carries out a complex operation from start to finish all on its own. It’s a seductive image, and almost always wrong. The automations that truly work, the ones you ship to production and that don’t betray you at night, are built on a far more sober principle, and once you see it you never forget it.
An LLM-assisted automation is not an intelligence that “does everything”. It’s a deterministic pipeline, a sequence of predictable, repeatable steps, in which the model is consulted only at the junction points where rigid code can’t cope: interpreting a natural-language request, reading unstructured input, choosing between alternatives, translating from one format to another.
Put as an image: the code is the structure, the LLM is the glue at the joints. The guiding principle that follows is just one: separate what must be done (the deterministic spec) from judgment (the LLM). Everything else is implementation detail.
It’s also why these automations work even with small models running locally on modest hardware: every single question put to the model is tiny and tightly bounded. The intelligence isn’t in the model; it’s in the scaffolding you build around it.
Six components. Once you recognize them, you’ll find them in any serious automation.
The building blocks, on their own, are concepts. They become concrete when you place them on real tools. A very common combination, and fully self-hosted, is OpenWebUI as the interface and n8n as the orchestrator. With this stack the roles split very cleanly:

Here’s the full map:

OpenWebUI offers a Tools mechanism: during the conversation the model can decide to invoke an external tool, in our case an n8n webhook. The boundary is sharp:
As long as the user asks questions, everything lives inside OpenWebUI. The moment they ask for an operation, the model fires the tool → the call to the webhook starts → from there on n8n is in command.
That boundary is a simple HTTP/JSON contract: the intent in, a structured result out. Clean and swappable: tomorrow you change the interface and n8n doesn’t even notice.
Note. There’s also the inverse pattern: hooking directly into n8n from OpenWebUI, configuring the workflow as if it were the “model” you talk to (via an OpenAI-compatible Pipe). In that case every message enters the pipeline right away. It’s more powerful, but it blurs the roles a bit; for clarity, here we describe the “n8n behind a Tool” pattern.
This is the most important thing to grasp. The same model (the same Ollama) works in two distinct roles:
These aren’t two intelligences: it’s the same model consulted for two micro-decisions, each small and bounded. That’s exactly why it works even with a modest model.
At every step, inside the backend, the same cycle repeats:
Fetch, decide, act, verify. The important thing is to read it as a cycle, not a straight line: each turn brings home one piece of information and, if answering needs more, the wheel turns again, until the final result comes together. We’ll see exactly this in the example below, where the cycle makes two turns.

Imagine a request posed by an operator in the OpenWebUI chat:
“Which hostgroups contain hosts with services in error?”
Let’s assume the system is connected to the standard Icinga MCP server, which exposes a catalog of ready-made tools: get_services, get_services_with_problems, get_hosts_with_problems, get_hostgroups. Notice a small catch right away: the operator asks for hostgroups, but the natural starting point is the services in error. Between the two sits the membership of hosts in groups. That’s why the cycle will have to turn twice.
Fetch. The model is shown the catalog of tools with their descriptions, and the fact that “in error”, in monitoring language, means “with problems” (the WARNING/CRITICAL/UNKNOWN states).
Decide. Here, and only here, the model steps in. Its sole task is to pick the right tool. Between get_services (all services) and get_services_with_problems (only the problematic ones), “in error” leads it to the second:
{ "tool": "get_services_with_problems" }
It doesn’t make the call, doesn’t invent hostgroup names, doesn’t reason over the results.
Act. The orchestrator actually invokes get_services_with_problems on the Icinga MCP server. Back comes the list of services in error, each with the host it belongs to. From this we derive the set of hosts in trouble: the hosts that have at least one problematic service.
Verify. We have the hosts, but the question didn’t ask for hosts: it asked for hostgroups. A piece is missing, the host→hostgroup membership map. The cycle isn’t done: it turns again.
Fetch and act. This time there’s no need to bother the model: the goal “which hostgroups” predictably requires the membership map, so the orchestrator calls get_hostgroups directly. For each hostgroup it returns the list of its member hosts. (Often this data is already in the host object from the first turn; when it is, the second turn is skipped.)
Relate them: this is where the two lists are joined. Now the deterministic code does the work that makes sense of everything. For each hostgroup it checks whether at least one of the hosts in trouble appears among its member hosts: if so, that hostgroup enters the answer; if not, it’s discarded. In practice it’s an intersection of two sets, the hosts in error and the members of each group:
problem_hosts = { s.host for s in get_services_with_problems() }
answer = [ g.name
for g in get_hostgroups()
if problem_hosts & set(g.members) ]
return sorted(set(answer)) # deduplicated and sorted
Verify and shape. The resulting hostgroups are deduplicated and sorted: this list, and only this, is the single piece of information the operator asked for. If the intersection is empty, the system answers “no hostgroups affected”; it doesn’t ask the model to “make up” a plausible answer.
The takeaway is in the division of roles: the model only picked the right tool from an ambiguous sentence; all the real work (invoking the tools, gathering the services, fetching the hostgroups and above all joining them to keep only the groups with hosts in error) was done by deterministic code and the Icinga MCP server.
Here’s how that cycle takes shape as an n8n workflow: six nodes, one per role.

Notice what isn’t there: no “do everything with AI” node. The model appears just once, for a single choice. Everything else is scaffolding. And if you change the question, “the unreachable hosts” instead of services in error, only the output of node ② changes, from get_services_with_problems to get_hosts_with_problems; the workflow stays identical.
Building an LLM-assisted automation doesn’t mean finding the most powerful model and hoping. It means designing solid scaffolding (a frontend that converses, a backend that executes, precise tools, facts within reach, a guardian that verifies) and entrusting the model with only what it does best: understand, choose, translate. It’s a simple shift in perspective, but it’s everything: the automation’s intelligence isn’t in the model, it’s in how you put it to work.
What we’ve described is the base, the starting point: scaffolding in which the model chooses and the code executes. It’s the best way to understand how these automations really work, and it’s enough to ship concrete cases like the hostgroup one to production. But it’s only the first step.
The moment you want to start getting serious, there are far more sophisticated and automatic approaches, built around genuine reasoning engines: models that, given the goal and the catalog of available tools, plan the sequence of steps on their own, build the needed commands and «recipes» on the fly, chain multiple calls, evaluate intermediate results, and correct course without the flow having been drawn by hand. There the scaffolding doesn’t disappear, but it becomes more dynamic: part of the logic we wrote here is generated and orchestrated by the model itself. It’s a fascinating, fast-moving territory (agents, planners, automatic tool-use, ReAct and the like), with different trade-offs around predictability, cost, and control.
But that, as they say, is another story.