AutomationFlowsAI & RAG › ₩^^₩ Agent Swarm (llm) — Webhook + History + Handoffs

₩^^₩ Agent Swarm (llm) — Webhook + History + Handoffs

Original n8n title: ₩^♡^₩ Agent Swarm (llm) — Webhook + History + Handoffs

₩^♡^₩ Agent Swarm (LLM) — Webhook + History + Handoffs. Uses dataStore, httpRequest. Webhook trigger; 18 nodes.

Webhook trigger★★★★☆ complexity18 nodesData StoreHTTP Request
AI & RAG Trigger: Webhook Nodes: 18 Complexity: ★★★★☆ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "\u20a9^\u2661^\u20a9 Agent Swarm (LLM) \u2014 Webhook + History + Handoffs",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "agent-swarm",
        "options": {
          "responseData": "auto",
          "responseCode": 200
        }
      },
      "id": "1",
      "name": "Webhook In",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        -40,
        0
      ]
    },
    {
      "parameters": {
        "functionCode": "// Normalize input, attach a sessionId, and basic meta\nconst body = $json.body ?? $json ?? {};\nconst sessionId = body.sessionId || $query.sessionId || $headers['x-session-id'] || Date.now().toString();\nconst userGoal  = body.goal || body.prompt || body.query || 'Summarize and analyze topic';\nconst userData  = body.data  || {};\nconst now = new Date().toISOString();\n\nreturn [{\n  json: {\n    meta: {\n      sessionId,\n      receivedAt: now,\n      ip: $headers['x-forwarded-for'] || $headers['cf-connecting-ip'] || $headers['x-real-ip'] || null,\n      userAgent: $headers['user-agent'] || null\n    },\n    input: {\n      goal: userGoal,\n      data: userData\n    }\n  }\n}];\n"
      },
      "id": "2",
      "name": "Init Context",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        200,
        0
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "dataStoreId": "={{$env.SWARM_DATASTORE_ID}}",
        "key": "={{$json.meta.sessionId}}",
        "returnAll": false
      },
      "id": "3",
      "name": "History: Get",
      "type": "n8n-nodes-base.dataStore",
      "typeVersion": 1,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "functionCode": "// Merge previous history (if any) with the fresh input\nconst prev = $json.value || null;\nconst history = prev && prev.history ? prev.history : [];\n// keep last 10 turns to bound token usage\nconst trimmed = history.slice(-10);\n\nconst merged = {\n  meta: $json.meta || prev?.meta || {},\n  input: $json.input || {},\n  history: trimmed\n};\n\nreturn [{ json: merged }];\n"
      },
      "id": "4",
      "name": "History: Merge",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        680,
        0
      ]
    },
    {
      "parameters": {
        "functionCode": "// Helper: build OpenAI Chat payload with role prompts\nfunction chatPayload(systemPrompt, userContent, temperature=0.3) {\n  const model = $env.OPENAI_MODEL || \"gpt-4o-mini\";\n  return {\n    model,\n    temperature,\n    messages: [\n      { role: \"system\", content: systemPrompt },\n      // pack short history for handoff-awareness\n      ...($json.history || []).map(h => ({ role: h.role || \"user\", content: h.content })),\n      { role: \"user\", content: userContent }\n    ]\n  };\n}\n\nconst goal = $json.input.goal;\n\nreturn [{\n  json: {\n    researcher: chatPayload(\n      \"You are Researcher-\u03b1. Discover facts, sources, constraints. Return JSON: {insights:[],sources:[],risks:[],next_step:\"...\"}. Be concise, cite URLs.\",\n      `Goal: ${goal}\\nData: ${JSON.stringify($json.input.data || {}, null, 2)}`\n    ),\n    strategist: chatPayload(\n      \"You are Strategist-\u03b2. Turn findings into a step-by-step plan. Return JSON: {plan:[{step,why,owner}],dependencies:[],success_criteria:[],next_step:\"...\"}.\",\n      \"Use Researcher-\u03b1 output in your reasoning.\"\n    ),\n    builder: chatPayload(\n      \"You are Builder-\u03b3. Produce the concrete deliverable (code/text). Return JSON: {artifact_type:\"code|copy|plan\", artifact:\"...\", notes:[],next_step:\"...\"}.\",\n      \"Implement Strategist-\u03b2 plan minimally (MVP).\"\n    ),\n    reviewer: chatPayload(\n      \"You are Reviewer-\u03b4. Critique for safety, legality, quality, and goal fit. Return JSON: {approved:true|false, issues:[], fixes:[], final_summary:\"...\"}. Be strict but practical.\",\n      \"Review the Builder-\u03b3 artifact with context.\"\n    )\n  }\n}];\n"
      },
      "id": "5",
      "name": "Assemble Agent Prompts",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        920,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "headerAuth",
        "sendBody": true,
        "jsonParameters": true,
        "options": {},
        "specifyAuthentication": "headerAuth",
        "headerAuth": {
          "name": "Authorization",
          "value": "={{'Bearer ' + $env.OPENAI_API_KEY}}"
        },
        "queryParametersUi": {
          "parameter": []
        },
        "headersUi": {
          "parameter": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "bodyParametersJson": "={{$json.researcher}}"
      },
      "id": "6",
      "name": "Researcher-\u03b1 (OpenAI)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        1160,
        -140
      ]
    },
    {
      "parameters": {
        "functionCode": "// Parse OpenAI response into JSON\nconst text = $json.choices?.[0]?.message?.content ?? '{}';\nlet parsed;\ntry { parsed = JSON.parse(text); } catch(e) { parsed = { raw: text }; }\nreturn [{ json: { researcher: parsed } }];\n"
      },
      "id": "7",
      "name": "Researcher: Parse",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        1400,
        -140
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "headerAuth",
        "sendBody": true,
        "jsonParameters": true,
        "specifyAuthentication": "headerAuth",
        "headerAuth": {
          "name": "Authorization",
          "value": "={{'Bearer ' + $env.OPENAI_API_KEY}}"
        },
        "headersUi": {
          "parameter": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "bodyParametersJson": "={{$json.$prevNode[\"Assemble Agent Prompts\"].strategist}}"
      },
      "id": "8",
      "name": "Strategist-\u03b2 (OpenAI)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        1160,
        0
      ]
    },
    {
      "parameters": {
        "functionCode": "const text = $json.choices?.[0]?.message?.content ?? '{}';\nlet parsed;\ntry { parsed = JSON.parse(text); } catch(e) { parsed = { raw: text }; }\nreturn [{ json: { strategist: parsed } }];\n"
      },
      "id": "9",
      "name": "Strategist: Parse",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        1400,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "headerAuth",
        "sendBody": true,
        "jsonParameters": true,
        "specifyAuthentication": "headerAuth",
        "headerAuth": {
          "name": "Authorization",
          "value": "={{'Bearer ' + $env.OPENAI_API_KEY}}"
        },
        "headersUi": {
          "parameter": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "bodyParametersJson": "={{$json.$prevNode[\"Assemble Agent Prompts\"].builder}}"
      },
      "id": "10",
      "name": "Builder-\u03b3 (OpenAI)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        1160,
        140
      ]
    },
    {
      "parameters": {
        "functionCode": "const text = $json.choices?.[0]?.message?.content ?? '{}';\nlet parsed;\ntry { parsed = JSON.parse(text); } catch(e) { parsed = { raw: text }; }\nreturn [{ json: { builder: parsed } }];\n"
      },
      "id": "11",
      "name": "Builder: Parse",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        1400,
        140
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "headerAuth",
        "sendBody": true,
        "jsonParameters": true,
        "specifyAuthentication": "headerAuth",
        "headerAuth": {
          "name": "Authorization",
          "value": "={{'Bearer ' + $env.OPENAI_API_KEY}}"
        },
        "headersUi": {
          "parameter": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "bodyParametersJson": "={{$json.$prevNode[\"Assemble Agent Prompts\"].reviewer}}"
      },
      "id": "12",
      "name": "Reviewer-\u03b4 (OpenAI)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        1160,
        300
      ]
    },
    {
      "parameters": {
        "functionCode": "const text = $json.choices?.[0]?.message?.content ?? '{}';\nlet parsed;\ntry { parsed = JSON.parse(text); } catch(e) { parsed = { raw: text }; }\nreturn [{ json: { reviewer: parsed } }];\n"
      },
      "id": "13",
      "name": "Reviewer: Parse",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        1400,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.reviewer.approved === true}}"
            }
          ]
        }
      },
      "id": "14",
      "name": "Approved?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1620,
        220
      ]
    },
    {
      "parameters": {
        "functionCode": "// Assemble final output when approved\nreturn [{\n  json: {\n    status: \"approved\",\n    sessionId: $json.meta?.sessionId,\n    insights: $json.researcher?.insights,\n    sources: $json.researcher?.sources,\n    plan: $json.strategist?.plan,\n    artifact: $json.builder?.artifact,\n    artifact_type: $json.builder?.artifact_type,\n    summary: $json.reviewer?.final_summary,\n    timestamp: new Date().toISOString()\n  }\n}];\n"
      },
      "id": "15",
      "name": "Assemble Final (OK)",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        1840,
        160
      ]
    },
    {
      "parameters": {
        "functionCode": "// Return issues & fixes when not approved\nreturn [{\n  json: {\n    status: \"changes_requested\",\n    sessionId: $json.meta?.sessionId,\n    issues: $json.reviewer?.issues || [],\n    fixes: $json.reviewer?.fixes || [],\n    partial: {\n      insights: $json.researcher?.insights,\n      plan: $json.strategist?.plan,\n      artifact: $json.builder?.artifact\n    },\n    timestamp: new Date().toISOString()\n  }\n}];\n"
      },
      "id": "16",
      "name": "Assemble Final (Revise)",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [
        1840,
        300
      ]
    },
    {
      "parameters": {
        "operation": "upsert",
        "dataStoreId": "={{$env.SWARM_DATASTORE_ID}}",
        "key": "={{$json.sessionId || $json.meta?.sessionId}}",
        "value": "={{$json}}"
      },
      "id": "17",
      "name": "History: Upsert",
      "type": "n8n-nodes-base.dataStore",
      "typeVersion": 1,
      "position": [
        2060,
        220
      ]
    },
    {
      "parameters": {
        "responseBody": "={{$json}}",
        "responseCode": 200
      },
      "id": "18",
      "name": "Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        2280,
        220
      ]
    }
  ],
  "connections": {
    "Webhook In": {
      "main": [
        [
          {
            "node": "Init Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Init Context": {
      "main": [
        [
          {
            "node": "History: Get",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "History: Get": {
      "main": [
        [
          {
            "node": "History: Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "History: Merge": {
      "main": [
        [
          {
            "node": "Assemble Agent Prompts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assemble Agent Prompts": {
      "main": [
        [
          {
            "node": "Researcher-\u03b1 (OpenAI)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Strategist-\u03b2 (OpenAI)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Builder-\u03b3 (OpenAI)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Reviewer-\u03b4 (OpenAI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Researcher-\u03b1 (OpenAI)": {
      "main": [
        [
          {
            "node": "Researcher: Parse",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Strategist-\u03b2 (OpenAI)": {
      "main": [
        [
          {
            "node": "Strategist: Parse",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Builder-\u03b3 (OpenAI)": {
      "main": [
        [
          {
            "node": "Builder: Parse",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reviewer-\u03b4 (OpenAI)": {
      "main": [
        [
          {
            "node": "Reviewer: Parse",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Researcher: Parse": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Strategist: Parse": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Builder: Parse": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reviewer: Parse": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approved?": {
      "main": [
        [
          {
            "node": "Assemble Final (OK)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Assemble Final (Revise)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assemble Final (OK)": {
      "main": [
        [
          {
            "node": "History: Upsert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assemble Final (Revise)": {
      "main": [
        [
          {
            "node": "History: Upsert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "History: Upsert": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "saveExecutionProgress": true,
    "executionOrder": "v1"
  },
  "staticData": null
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

₩^♡^₩ Agent Swarm (LLM) — Webhook + History + Handoffs. Uses dataStore, httpRequest. Webhook trigger; 18 nodes.

Source: https://github.com/405naturaldesign-tech/puraestate/blob/fc713a902df1e926a8beebcb3f1f402ab2fbbec0/n8n-workflows/agent_swarm_n8n.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Jigsaw API key for image processing, I use this as a gatekeeper/second pair of eyes. LINK to their website https://jigsawstack.com/ SECOND A postgress DATABASE (I use Supabase) LlamaCloud for the pars

HTTP Request, Postgres, Stop And Error +2
AI & RAG

Onsite Photos to Jobs (SMS Agent). Uses dataTable, twilio, httpRequest, airtable. Webhook trigger; 62 nodes.

Data Table, Twilio, HTTP Request +1
AI & RAG

W1 - IN WhatsApp Adapter (Secure + Fast ACK). Uses postgres, redis, httpRequest. Webhook trigger; 50 nodes.

Postgres, Redis, HTTP Request
AI & RAG

W1 - IN WhatsApp Adapter (Secure + Fast ACK). Uses postgres, redis, httpRequest. Webhook trigger; 48 nodes.

Postgres, Redis, HTTP Request
AI & RAG

WF-OB-2: Post-Call Handler. Uses httpRequest, slack. Webhook trigger; 48 nodes.

HTTP Request, Slack