Netspective Logo
Netspective Content Engineering GuidelinesAI-Native Content Automation Pipeline

The Workflow Artifact

What the build prompts actually produce — the n8n canvas layout and the workflow JSON, showing where the writer's prompt sits and how the agent's sub-nodes attach.

Building It by Prompt says that prompt 2 "comes back with a complete workflow". This page shows what that means concretely: the canvas you would see in n8n, and the JSON behind it.

Read this as illustration, not as something to import.

This artifact is shown so the structure is legible — which nodes exist, how they connect, and where each prompt lives. It is not a validated workflow. typeVersion values and credential IDs are deliberately omitted, because those are exactly the details that must come from the real node schemas rather than from recall.

That is the whole argument of why general-purpose AI cannot build these workflows reliably, and it applies to this page too. Generate your actual workflow with the prompts in Building It by Prompt, then validate it before it goes anywhere near your systems.

The canvas

The workflow as it appears on the n8n canvas

FIGURE 12 — The same workflow, as the n8n canvas shows it. Every node carries the name it has in the JSON below, so the two can be read side by side. For the exact node types, see the node-level reference.

The workflow JSON

{
  "name": "Content Automation Pipeline",
  "nodes": [
    {
      "id": "schedule-trigger",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [0, 300],
      "parameters": {
        "rule": {
          "interval": [{ "field": "days", "triggerAtHour": 6 }]
        }
      }
    },
    {
      "id": "rss-read",
      "name": "RSS Read",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [220, 200],
      "parameters": {
        "url": "https://example.com/feed.xml"
      }
    },
    {
      "id": "http-request",
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [220, 400],
      "parameters": {
        "url": "https://example.org/news",
        "options": {}
      }
    },
    {
      "id": "html-extract",
      "name": "HTML Extract",
      "type": "n8n-nodes-base.html",
      "position": [440, 400],
      "parameters": {
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            { "key": "title", "cssSelector": "h1" },
            { "key": "body", "cssSelector": "article" },
            { "key": "url", "cssSelector": "link[rel=canonical]", "returnValue": "attribute", "attribute": "href" }
          ]
        }
      }
    },
    {
      "id": "merge",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [660, 300],
      "parameters": {
        "mode": "append",
        "numberInputs": 2
      }
    },
    {
      "id": "remove-duplicates",
      "name": "Remove Duplicates",
      "type": "n8n-nodes-base.removeDuplicates",
      "position": [880, 300],
      "parameters": {
        "operation": "removeItemsSeenInPreviousExecutions",
        "logic": "removeItemsWithAlreadySeenKeyValues",
        "dedupeValue": "={{ $json.url }}"
      }
    },
    {
      "id": "ai-agent",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [1100, 300],
      "parameters": {
        "promptType": "define",
        "text": "={{ JSON.stringify($input.all().map(i => i.json)) }}",
        "hasOutputParser": true,
        "options": {
          "systemMessage": "You are a staff writer for [CLIENT NAME]. You will receive several source articles covering a related story.\n\nWrite ONE article that synthesises them. Do not summarise any single source, and do not stitch the sources together in sequence.\n\nVoice and format\n- [House voice, taken from your style guide]\n- 600-800 words\n- Open with why this matters to [your audience], not with what happened\n- A subheading every 200-300 words\n\nRules\n- Attribute every factual claim to the source it came from\n- Where sources disagree, say so rather than silently picking one\n- Where something cannot be confirmed from the sources, say so\n- Never invent quotes, figures, names or dates\n- If the sources do not support an article worth publishing, return status \"skip\" with a one-line reason instead of writing one anyway"
        }
      }
    },
    {
      "id": "chat-model",
      "name": "Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [1040, 480],
      "parameters": {
        "options": {}
      }
    },
    {
      "id": "output-parser",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [1200, 480],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"status\": { \"type\": \"string\", \"enum\": [\"write\", \"skip\"] },\n    \"reason\": { \"type\": \"string\" },\n    \"headline\": { \"type\": \"string\" },\n    \"standfirst\": { \"type\": \"string\" },\n    \"body\": { \"type\": \"string\" },\n    \"tags\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } },\n    \"sourceUrls\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } }\n  },\n  \"required\": [\"status\"]\n}"
      }
    },
    {
      "id": "create-draft-post",
      "name": "Create Draft Post",
      "type": "n8n-nodes-base.wordpress",
      "position": [1320, 300],
      "parameters": {
        "resource": "post",
        "operation": "create",
        "title": "={{ $json.output.headline }}",
        "additionalFields": {
          "content": "={{ $json.output.body }}",
          "status": "draft"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "id": "notify-the-team",
      "name": "Notify the Team",
      "type": "n8n-nodes-base.emailSend",
      "position": [1540, 440],
      "parameters": {
        "subject": "Content pipeline failed",
        "text": "={{ $json.error.message }}"
      }
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [[{ "node": "RSS Read", "type": "main", "index": 0 },
                { "node": "HTTP Request", "type": "main", "index": 0 }]]
    },
    "RSS Read": {
      "main": [[{ "node": "Merge", "type": "main", "index": 0 }]]
    },
    "HTTP Request": {
      "main": [[{ "node": "HTML Extract", "type": "main", "index": 0 }]]
    },
    "HTML Extract": {
      "main": [[{ "node": "Merge", "type": "main", "index": 1 }]]
    },
    "Merge": {
      "main": [[{ "node": "Remove Duplicates", "type": "main", "index": 0 }]]
    },
    "Remove Duplicates": {
      "main": [[{ "node": "AI Agent", "type": "main", "index": 0 }]]
    },
    "Chat Model": {
      "ai_languageModel": [[{ "node": "AI Agent", "type": "ai_languageModel", "index": 0 }]]
    },
    "Structured Output Parser": {
      "ai_outputParser": [[{ "node": "AI Agent", "type": "ai_outputParser", "index": 0 }]]
    },
    "AI Agent": {
      "main": [[{ "node": "Create Draft Post", "type": "main", "index": 0 }]]
    },
    "Create Draft Post": {
      "main": [
        [],
        [{ "node": "Notify the Team", "type": "main", "index": 0 }]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}

Reading it

NodeReceivesEmits
Schedule Triggernothing — fires on the clockone empty item, at the agreed hour
RSS Readthe triggerone item per article in the feed
HTTP Requestthe triggerthe raw page, for sources with no feed
HTML Extractthe raw pagetitle, body and canonical URL as fields
Mergeboth collect branchesone combined stream
Remove Duplicatesthe combined streamonly items not seen in an earlier run
AI Agentall remaining itemsone article as structured fields
Create Draft Postthe articlea WordPress post with status: draft
Notify the Teamthe error outputa message to a nominated person

Three things in that JSON are worth pointing out, because they are where the design decisions from earlier pages become concrete.

The sub-nodes connect upward, not along the flow. Chat Model and Structured Output Parser do not appear in any main connection. They attach to the agent on their own connection types — ai_languageModel and ai_outputParser — which is why they hang beneath it on the canvas rather than sitting in the line. Getting this shape wrong is one of the most common hand-editing mistakes.

De-duplication keys on the canonical URL. dedupeValue reads $json.url, and the node is set to compare against items seen in previous executions — this is the state that persists between runs, described in the operational notes.

The error output is a second branch, not a failure. Create Draft Post sets onError to continue on a separate output, and its connections entry has two arrays — the first empty, the second going to the notifier. That is what "notifies rather than failing silently" looks like in the artifact.

Where the prompts live

The distinction drawn in Building It by Prompt is visible directly in the JSON:

  • The run-time prompt is the systemMessage inside the AI Agent node. It was written once and now runs unattended on every execution. Its full text and the reasoning behind it are in the writer's instructions.
  • The output shape is the inputSchema on the Structured Output Parser. This is what makes the result arrive as separate headline, body and tags fields instead of one block of prose — and it carries the skip status that lets a thin news day produce no article at all.
  • The build-time prompts appear nowhere in this file, because they are not part of the running system. They produced it, and then stepped out — as covered in build time versus run time.

For all nine prompts side by side — the one in this file and the eight that are not — see The Prompt Map.

How is this guide?

Last updated on

On this page