Agentic AI Orchestration Workflows Implementation Process¶
Three implementations were built for the same task, each progressively introducing AI into different parts of the pipeline. They are documented here in the order they were developed.
Implementation 1 — No Agents (MCP Workflow)¶
File: 📥 Download workflow
The first implementation removes AI entirely. Every step is handled by native n8n nodes: MCP client calls, Set nodes to extract fields, JavaScript code nodes for array manipulation, and IF nodes for routing.
Trigger: HTTP POST to /webhook/netbox-vlan-test with body { "device": "...", "vlan": <vid>, "apply": true/false }
Use this when: the caller is another system and the input is already structured.
Flow¶
Webhook
→ Get Device (MCP) → Get ID
→ Get Interface (MCP) → Edit Fields → Normalise (JS code)
→ IF tagged mode?
├─ no → 400: not in tagged mode
└─ yes → IF VLAN already on interface?
├─ yes → 200: already present, no-op
└─ no → Capture Before State
→ Check VLAN exists (MCP)
→ IF VLAN in IPAM?
├─ no → 400: VLAN not found
└─ yes → Build PATCH payload (JS code)
→ IF apply=true?
├─ no → 200: dry-run result
└─ yes → HTTP PATCH
→ Verify (JS code)
→ IF verify OK?
├─ yes → 200: success
└─ no → Build Rollback PATCH
→ Rollback HTTP PATCH
→ 500: rolled back
Key design decisions¶
Each decision point is a native n8n IF node. The PATCH payload is computed by a JavaScript code node that reads the current tagged_vlans array from the interface lookup, adds the requested VLAN ID, deduplicates, and sorts. Verification reads the PATCH response body directly and checks whether the requested VID appears in the returned tagged_vlans list.
- Dry-run mode: if
apply=falsein the request body, the workflow stops after computing the payload and returns it without sending it to NetBox. This is useful for testing logic before enabling live writes. - Before-state capture: a dedicated code node captures the original
tagged_vlansID array before the PATCH is sent. If verification fails, this snapshot is used to restore the interface to exactly its previous state.
Scope and limitations¶
This implementation only supports adding VLANs. Removal would require a parallel branch with different computation logic, which would significantly increase the node count. The workflow is intended as a proof of concept rather than a general-purpose tool.
Implementation 2 — Multiple Specialised Agents with Guards¶
File: 📥 Download workflow
The second implementation replaces the webhook with a chat interface and introduces AI agents at the points where natural-language understanding is genuinely useful: parsing the user's request, looking up inventory, and verifying the result. Everything else — normalisation, validation, guard evaluation, patch computation, and routing — remains deterministic.
The word guarded describes the entire pipeline design, not just the moment before the PATCH. At every stage where an AI agent produces output, a deterministic layer immediately follows to normalise the data, validate it against a contract, and check whether the next step is safe to take. No raw agent output ever reaches a write operation unchecked.
⚠️ Stability note: this workflow is not 100% stable. Under identical conditions with the same input, agents may occasionally produce unexpected output — returning values in the wrong format, omitting fields, or making more tool calls than instructed. The guarded architecture is specifically designed to contain these failures: normalisation and validation nodes catch bad output before it propagates, and guard nodes stop the workflow before any unsafe action is taken. The workflow fails visibly and safely rather than silently producing a wrong result.
Trigger: n8n chat UI.
Use this when: the interface is conversational but the write path must remain auditable, inspectable, and failure-safe.
Agentic contracts¶
Each agent in this pipeline is bound by an agentic contract — a JSON schema that defines exactly what it must produce. Rather than letting agents emit free-form text that downstream nodes must parse loosely, every agent output is validated against a declared schema before the workflow continues. If the output does not match, a structured output parser retries with a correction prompt.
The Planner agent, for example, must produce a document of this form:
{
"type": "plan",
"version": "1.0",
"request_id": "req-20260303-0007",
"ok": true,
"action": "add_tagged_vlan",
"params": {
"device": "rtr-lab-02",
"interface": "ge-0/0/0",
"vlan_vid": 40
},
"approval_required": true,
"explanation": "Plan to add VLAN 40 as a tagged VLAN on rtr-lab-02 interface ge-0/0/0."
}
If ok is false, the workflow routes back to the user to ask for the missing information rather than proceeding.
Agents and their roles¶
| Agent | 🌡️ Temp | What it does | Followed by |
|---|---|---|---|
| Planner | default | Parses the user's free-text request into a structured JSON plan: action, device, interface, vlan_vid |
Structured output parser → Validate + Normalise Plan code node |
| Lookup | 0 | Makes exactly one MCP call to find the interface by device and interface name | Normalise Lookup Results code node → Lookup OK? gate |
| Lookup VLAN ID | 0 | Makes exactly one MCP call to ipam.vlan to find the VLAN by VID and returns vlan_id and status |
Normalise Lookup Results code node → guard evaluation |
| Verify | 0 | Re-queries the interface via MCP after the PATCH to confirm the change was applied | Structured output parser → IF verify OK gate → rollback if failed |
🌡️ Temperature note: temperature is set to 0 for every agent except the Planner. The Planner's job is to understand user intent from natural language, which benefits from the model's full range of expression. Every other agent has a precisely defined, deterministic task. For those agents, any temperature above 0 introduces unnecessary variance — different output formatting between runs, inconsistent field types, or slightly different instruction interpretation — all of which increases the chance of the downstream normalisation node having to recover from unexpected output.
Every agent is immediately followed by a deterministic layer. The agents are trusted to reason and query, but never trusted to produce output that goes directly into the next stage unchecked.
The Lookup agents are the most operationally sensitive part of the workflow. Each must make exactly one MCP call and return structured JSON. Any deviation — multiple calls, free-text output, or missing fields — causes the normalisation code node to produce incomplete data that can silently corrupt the downstream PATCH.
⚠️ The
Lookup VLAN IDagent is kept separate from the interfaceLookupagent deliberately. Without very precise prompting, agents confuse the VLAN VID, the user-visible number such as40, with the NetBox database ID, the internal primary key required for PATCH calls. When a single agent makes multiple MCP calls, it also tends to extract the VLAN ID from the interface's owntagged_vlanslist rather than from a freshipam.vlanquery, meaning it can only "find" VLANs already on the interface. Splitting into two single-purpose agents, each querying a distinct object type, eliminates both failure modes.
Flow¶
Chat trigger
→ Planner agent → If plan.ok?
├─ no → ask user for missing info
└─ yes → Validate + Normalise Plan (code)
→ Lookup agent → Normalise Lookup Results (code)
→ Lookup VLAN ID agent
→ Normalise Lookup Results (code)
→ Lookup OK?
├─ no → Lookup Error message
└─ yes → Edit Fields
→ guards (code — compute boolean flags)
→ Guard Check (code — evaluate + format errors)
→ Guard Gate (IF)
├─ fail → Guard Abort message
└─ pass → Compute Patch (code)
→ Changed?
├─ no → no-op message
└─ yes → Approval request (chat)
→ [wait for user]
→ Parse Approval (code)
→ If approved?
├─ no → cancelled
└─ yes → HTTP PATCH
→ PATCH OK?
→ Verify agent
→ IF verify OK?
├─ yes → success
└─ no → Rollback
Stage 1 — Plan normalisation and validation¶
After the Planner agent produces its output, two nodes run before anything else happens.
- Structured output parser — n8n's AI agent node includes a structured output option designed to enforce a JSON schema and retry if the output does not conform. This is the first line of defence against malformed output. In practice, however, this mechanism does not work reliably out of the box. Agents sometimes produce output that technically satisfies the schema structure but contains wrong types, missing fields, or values that pass the parser while being semantically incorrect. For this reason, the structured output parser is treated as a best-effort first pass, not a guarantee.
Validate + Normalise Plancode node — performs a deterministic second pass. It checks thattype=plan,ok=true,actionis one of the allowed values, and thatvlan_vidis an integer. It also setsapproval_required=true, enforcing the human-in-the-loop requirement unconditionally regardless of what the agent produced. If any check fails, the workflow stops here with a clear error.
Stage 2 — Lookup normalisation and validation¶
The Lookup agents are configured without structured output parsers. During development, attaching a structured output parser to a Lookup agent caused the agent to loop: it would call the format tool repeatedly instead of making the MCP call and terminating. Removing the parser and instead instructing the agent to emit plain JSON in the prompt produced more stable behaviour.
The consequence is that the Lookup agent's raw output arrives as an unvalidated string. A Normalise Lookup Results code node takes full responsibility for making sense of it — accepting both plain-text JSON and already-parsed objects, stripping markdown code fences, resolving the NetBox mode field in all its possible forms, and coercing all IDs to integers.
After normalisation, a Lookup OK? IF node checks whether both device_id and interface_id are non-null. If either is missing, the workflow routes to a Lookup Error message and stops cleanly.
Stage 3 — Guard evaluation¶
Once lookup data is normalised and confirmed valid, dedicated guard nodes evaluate whether the requested action is safe and applicable.
guards code node computes boolean flags:
| Flag | Condition |
|---|---|
interface_tagged_mode |
Interface mode is "tagged" |
vlan_exists |
VLAN was found in NetBox |
vlan_not_already_present |
VLAN not yet on interface for add, or already on it for remove |
vlan_active |
VLAN status is active or null |
Guard Check code node evaluates the flags and produces human-readable error messages:
- interface in
tagged-allmode: selective VLAN management does not apply - interface not in
taggedmode: reports the current mode - VLAN does not exist: asks the user to create it first
- VLAN is not active: reports the actual status such as
reservedordeprecated
Guard Gate IF node routes to Guard Abort on any failure, or forwards to patch computation if all guards pass.
All guard logic is deterministic JavaScript. It cannot be confused, hallucinated past, or misled by an unusual phrasing of the request.
Stage 4 — Deterministic patch computation¶
The PATCH payload is computed by Compute Patch, a JavaScript code node. It reads the normalised tagged_vlan_ids, adds or removes the target VLAN ID, deduplicates, and sorts. It also computes the rollback array and human-readable VID lists for the approval message, and detects no-ops. The LLM is never asked to compute array operations.
A Changed? IF node checks whether the patch actually differs from the current state. If not, the user is informed and the workflow stops without presenting an approval request.
Stage 5 — Human approval¶
The approval message presents the full before-and-after state. A Parse Approval code node normalises the user's typed response before the If2 node checks it, handling case variations and whitespace so the check is reliable regardless of how the user typed the response.
Stage 6 — Apply, verify, and rollback¶
After the PATCH is sent:
PATCH OK?checks the HTTP response. A successful NetBox PATCH returns an interface object with a numericid; a failed PATCH returns an error body. Failure routes toPATCH Failedwith a clear message.- Verify agent re-queries the interface via MCP and checks for the expected VLAN. Its output is schema-enforced as
{ "verified": true, "reason": "..." }. IF verify OKroutes on the booleanverifiedfield. On failure, a rollback PATCH is sent immediately, retrying up to 3× with a 1-second wait.
Demo¶
The following video demonstrates the full guarded multi-agent workflow — from a natural-language chat request through planning, lookup, guard evaluation, human approval, PATCH, and verification.
▶️ Video:
Implementation 3 — Single Orchestrator Agent¶
File: 📥 Download workflow
The third implementation collapses the entire pipeline into a single AI agent with 8 nodes total.
Trigger: n8n chat UI.
Use this when: you want the simplest canvas and fastest iteration cycle, and can accept that critical validation logic lives inside the prompt rather than in visible deterministic nodes.
Node structure¶
Chat trigger
└─ VLAN Orchestrator agent
├─ 🤖 LLM: qwen2.5:72b (temperature 0)
├─ 🧠 Memory Buffer (20-message window)
├─ 🔍 MCP Client (read-only lookups)
├─ ✍️ netbox_patch_interface (HTTP PATCH tool)
└─ ↩️ netbox_rollback_interface (HTTP PATCH tool)
└─ 💬 Chat Response
How it works¶
The agent manages the full workflow across two conversational turns, governed by a detailed system prompt.
Turn 1 — the agent parses the user's request, makes three sequential MCP calls for interface lookup, VLAN lookup, and resolving existing tagged VLAN IDs to display names, validates the pre-conditions, computes the proposed change, and replies with a formatted approval table. The response embeds an APPROVAL_TOKEN JSON blob, a structured snapshot of everything needed to execute and roll back the change in Turn 2.
Turn 2 — the agent scans its own conversation history for the APPROVAL_TOKEN line, parses it, checks whether the user typed approve or reject, calls netbox_patch_interface, reads the PATCH response to verify the change, and calls netbox_rollback_interface if verification fails.
Turn detection¶
Turn detection works by searching conversation history for a line beginning with APPROVAL_TOKEN:. If found, it is Turn 2. If not, it is Turn 1. This is a simple but reliable pattern for maintaining multi-turn state using only the model's context window, with no external storage, database, or session variable required.
System prompt structure¶
The system prompt encodes the full workflow as explicit, enumerated rules:
- each MCP call is specified exactly: object type, filters, fields, and limit
- validation checks are ordered with explicit stop conditions such as STOP on first failure, do NOT proceed
- the PATCH tool is explicitly forbidden in Turn 1
- the response format for the approval table is defined exactly, including the
APPROVAL_TOKENline
Guards in this implementation¶
The validation logic that lives in dedicated guard code nodes in Implementation 2 is expressed here as prompt rules only. Rules such as NEVER call the PATCH tool in Turn 1, STOP on first validation failure, and never invent IDs — they must come from the tool response rely entirely on the model choosing to follow them.
In practice, prompt rules are not reliable guards. The most common observed failure is the agent inventing values — producing an interface ID or VLAN ID that was not returned by any MCP call — despite an explicit instruction not to do so. This happens silently: the invented ID is sent to the NetBox API, and the PATCH either hits the wrong object or fails with a 404.
⚠️ Stability note: Implementation 3 is less stable than Implementation 2. Under identical conditions with the same input, the orchestrator agent more frequently produces unexpected behaviour — inventing values, skipping steps, or mishandling the two-turn flow. This is a direct consequence of having no deterministic guard or normalisation layer between the agent's reasoning and the write operation. For exploratory or low-stakes use it is acceptable; for anything requiring consistent behaviour it is not.
Demo¶
The following video demonstrates the non-guarded single-agent orchestrator — a complete add-VLAN conversation from request to verified result.
▶️ Video: