{
  "id": "zJrO4OPMVWueYpfX",
  "name": "Template 2: Content Pipeline (Subworkflow Orchestrator)",
  "tags": [],
  "nodes": [
    {
      "id": "861a5047-495a-433e-aafe-c8c93acca79d",
      "name": "Webhook - Content Request",
      "type": "n8n-nodes-base.webhook",
      "position": [
        112,
        304
      ],
      "parameters": {
        "path": "content-pipeline",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "bef00411-9f90-4a8d-87fd-ab7208577b37",
      "name": "Normalize Request",
      "type": "n8n-nodes-base.code",
      "position": [
        320,
        304
      ],
      "parameters": {
        "jsCode": "// Normalize incoming content request and initialize pipeline state\nconst raw = $input.first().json;\nconst src = (raw && typeof raw.body === 'object' && raw.body !== null) ? raw.body : raw;\nreturn {\n  json: {\n    requestId: src.requestId || 'REQ-' + Date.now(),\n    topic: String(src.topic || '').trim(),\n    brief: String(src.brief || '').trim(),\n    qualityThreshold: typeof src.qualityThreshold === 'number' ? src.qualityThreshold : 7.5,\n    maxRevisions: typeof src.maxRevisions === 'number' ? src.maxRevisions : 2,\n    revisionCount: 0,\n    currentDraft: null,\n    previousDraft: null,\n    feedbackNotes: null,\n    review: null,\n    passed: false,\n    research: null,\n    timestamp: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "677dbbad-38f1-403b-b2fb-8d580cf4ced8",
      "name": "Call Research Subworkflow",
      "type": "n8n-nodes-base.executeWorkflow",
      "notes": "Point this at the Research subworkflow after you import it. Passthrough is on, so the full pipeline state flows in and the subworkflow returns state plus a `research` object.",
      "position": [
        544,
        304
      ],
      "parameters": {
        "options": {},
        "workflowId": {
          "__rl": true,
          "mode": "list",
          "value": "QrV3NmOnfJYqmw1W",
          "cachedResultName": "Template 2: Content Pipeline - Research Agent"
        }
      },
      "notesInFlow": true,
      "typeVersion": 1.2
    },
    {
      "id": "aceb47c4-6b3d-44b1-a7e8-0e07a0dfda88",
      "name": "Call Writer Subworkflow",
      "type": "n8n-nodes-base.executeWorkflow",
      "notes": "Point this at the Writer subworkflow. On the first pass, feedbackNotes is null so the writer drafts from scratch. On loop iterations, feedbackNotes and previousDraft are set, so the writer revises.",
      "position": [
        768,
        304
      ],
      "parameters": {
        "options": {},
        "workflowId": {
          "__rl": true,
          "mode": "list",
          "value": "zZjdwqm8PwzrLjeZ",
          "cachedResultName": "Template 2: Content Pipeline - Writer Agent"
        }
      },
      "notesInFlow": true,
      "typeVersion": 1.2
    },
    {
      "id": "a7cd8b8f-b8c4-447d-a0c1-c8f198f3ef06",
      "name": "Call Reviewer Subworkflow",
      "type": "n8n-nodes-base.executeWorkflow",
      "notes": "Point this at the Reviewer subworkflow. It returns state plus a `review` object with scores, overall, issues, and revisionNotes.",
      "position": [
        992,
        304
      ],
      "parameters": {
        "options": {},
        "workflowId": {
          "__rl": true,
          "mode": "list",
          "value": "xVqClqbjOwmWYSfQ",
          "cachedResultUrl": "/workflow/xVqClqbjOwmWYSfQ",
          "cachedResultName": "\u00e2\u009a\u0099\u00ef\u00b8\u008f Template 2: Content Pipeline - Reviewer Agent (LLM-as-a-Judge)"
        },
        "workflowInputs": {
          "value": {},
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        }
      },
      "notesInFlow": true,
      "typeVersion": 1.2
    },
    {
      "id": "efae6de3-98d9-44b5-a054-93f9ea29e522",
      "name": "Evaluate Review",
      "type": "n8n-nodes-base.code",
      "position": [
        1200,
        304
      ],
      "parameters": {
        "jsCode": "// Evaluate review result, compute pass/fail against threshold, prep state for loop or exit\nconst state = $input.first().json;\nconst overall = typeof state.review?.overall === 'number' ? state.review.overall : 0;\nconst threshold = typeof state.qualityThreshold === 'number' ? state.qualityThreshold : 7.5;\nconst passed = overall >= threshold;\nconst newRevisionCount = (state.revisionCount || 0) + 1;\n\nreturn {\n  json: {\n    ...state,\n    passed: passed,\n    revisionCount: newRevisionCount,\n    previousDraft: state.currentDraft,\n    feedbackNotes: passed ? null : (state.review?.revisionNotes || 'Improve overall quality, address the issues list.')\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e2ffdc92-f547-4f80-a0c9-7643ce117037",
      "name": "Approved or Max Revisions?",
      "type": "n8n-nodes-base.if",
      "position": [
        1424,
        304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "cond-passed",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.passed }}",
              "rightValue": true
            },
            {
              "id": "cond-max-hit",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.revisionCount }}",
              "rightValue": "={{ $json.maxRevisions }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "8876dff2-048f-4449-a456-9304192a912f",
      "name": "Finalize Response",
      "type": "n8n-nodes-base.code",
      "position": [
        1664,
        208
      ],
      "parameters": {
        "jsCode": "// Build the final response envelope\nconst s = $input.first().json;\nreturn {\n  json: {\n    requestId: s.requestId,\n    topic: s.topic,\n    brief: s.brief,\n    finalDraft: s.currentDraft,\n    wordCount: s.draftWordCount || null,\n    qualityPassed: s.passed === true,\n    finalScore: s.review?.overall ?? null,\n    scores: s.review?.scores || null,\n    revisionCount: s.revisionCount,\n    hitMaxRevisions: s.revisionCount >= s.maxRevisions && !s.passed,\n    issues: s.review?.issues || [],\n    reviewerNotes: s.review?.revisionNotes || null,\n    timestamp: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "ae42d958-d824-4da1-90f9-85be80c10ac1",
      "name": "Respond to Client",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1888,
        208
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "d80bf175-ad41-4e6a-8838-2a545ff258ed",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -544,
        0
      ],
      "parameters": {
        "width": 620,
        "height": 820,
        "content": "## Content Pipeline (Subworkflow Orchestrator)\n\n### How it works\nA parent orchestrator calls three independent subworkflows in sequence and loops the Writer when quality falls short:\n1. **Intake** (Deterministic): Webhook receives topic and brief. Code node normalizes the payload and initializes loop state (`revisionCount`, `maxRevisions`).\n2. **Research** (Subworkflow): Execute Workflow calls the Research subworkflow. Gathers background notes on the topic.\n3. **Writer** (Subworkflow): Produces a ~400-word draft from research + brief. On revisions, uses the Reviewer's feedback to revise rather than rewrite.\n4. **Reviewer** (Subworkflow): Scores the draft on accuracy, tone, completeness, and clarity. Returns revision notes when it falls short.\n5. **Evaluate + Decision**: IF node finalizes the draft when `overallScore >= qualityThreshold` OR `revisionCount >= maxRevisions`. Otherwise loops back to the Writer with the Reviewer's feedback.\n\n### Setup\n- Import the three subworkflows first (Research, Writer, Reviewer), then the parent. Subworkflows must be saved before the parent can reference them.\n- Connect your **LLM credentials** to each subworkflow's Chat Model sub-node (each subworkflow has its own)\n- Copy the Webhook test URL and POST with `topic`, `brief`, `qualityThreshold`, `maxRevisions`\n\n### Customization\n- Mix model capabilities per subworkflow (lighter for Research, stronger for Writer and Reviewer)\n- Edit the Reviewer subworkflow's scoring criteria to match your domain\n- Tune `qualityThreshold` and `maxRevisions` as you calibrate the loop\n\nThis template is a learning companion to the Production AI Playbook, a series that explores strategies, shares best practices, and provides practical examples for building reliable AI systems in n8n."
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "9a2dbbed-1446-4aef-9b8c-7913ada1ab09",
  "connections": {
    "Evaluate Review": {
      "main": [
        [
          {
            "node": "Approved or Max Revisions?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Finalize Response": {
      "main": [
        [
          {
            "node": "Respond to Client",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Request": {
      "main": [
        [
          {
            "node": "Call Research Subworkflow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Writer Subworkflow": {
      "main": [
        [
          {
            "node": "Call Reviewer Subworkflow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Research Subworkflow": {
      "main": [
        [
          {
            "node": "Call Writer Subworkflow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Reviewer Subworkflow": {
      "main": [
        [
          {
            "node": "Evaluate Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Content Request": {
      "main": [
        [
          {
            "node": "Normalize Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approved or Max Revisions?": {
      "main": [
        [
          {
            "node": "Finalize Response",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Call Writer Subworkflow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}