AutomationFlowsAI & RAG › AI-Driven Handbook Generator with OpenAI

AI-Driven Handbook Generator with OpenAI

Original n8n title: Ai-driven Handbook Generator with Multi-agent Orchestration (pyragogy AI Village)

AI-Driven Handbook Generator with Multi-Agent Orchestration (Pyragogy AI Village). Uses start, postgres, openAi, emailSend. Webhook trigger; 36 nodes.

Webhook trigger★★★★★ complexityAI-powered36 nodesStartPostgresOpenAIEmail SendGitHubSlack
AI & RAG Trigger: Webhook Nodes: 36 Complexity: ★★★★★ AI nodes: yes Added:

This workflow follows the Emailsend → OpenAI recipe pattern — see all workflows that pair these two integrations.

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
{
  "_templateId": 4904,
  "_templateName": "Pyragogy AI-Driven Handbook Generator with Multi-Agent Orchestration",
  "_templateDescription": "AI-Driven Handbook Generator with Multi-Agent Orchestration (Pyragogy AI Village)\n\nThis n8n workflow is a modular, multi-agent AI orchestration system designed for the collaborative generation of Markdown-based handbooks. Inspired by peer learning and open publishing workflows, it simulates a content pipeline where specialized AI agents act in defined roles, enabling true AI\u2013human co-creation and iterative refinement.\n\nThis project is a core component of Pyragogy, an open framework dedicated to ethical cognitive co-creation, peer AI\u2013human learning, and human-in-the-loop automation for open knowledge systems. It implements the master orchestration architecture for the Pyragogy AI Village, managing a complex sequence of AI agents to process input, perform review, synthesis, and archiving, with a crucial human oversight step for final approval.\n\nHow It Works: A Deep Dive into the Workflow's Architecture\n\nThe workflow orchestrates a sophisticated content generation and review process, ideal for creating AI-driven knowledge bases or handbooks with human oversight.\n\nWebhook Trigger & Input:* The process begins when the workflow receives a JSON input via a *Webhook** (specifically at /webhook/pyragogy/process). This input typically includes details like the handbook's title, initial text, and relevant tags.\n\nDatabase Verification:* It first verifies the connection to a *PostgreSQL database** to ensure data persistence.\n\nMeta-Orchestrator:* A powerful *Meta-Orchestrator** (powered by gpt-4o from OpenAI) analyzes the initial request. Its role is to dynamically determine and activate the optimal sequence of specialized AI agents required to fulfill the input, ensuring tasks are dynamically routed and assigned based on each agent\u2019s responsibility.\n\nAgent Execution & Iteration:** Each activated agent executes its step using OpenAI or custom endpoints. This involves:\n\n    Content Generation: Agents like the Summarizer and the Synthesizer generate new content or refine existing text.\n\n    Peer Review Board: A crucial aspect is the Peer Review Board, comprised of AI agents like the Peer Reviewer, the Sensemaking Agent, and the Prompt Engineer. This board evaluates the output for quality, coherence, and accuracy.\n\n    Reprocessing & Redrafting: If the review agents flag a major_issue, they trigger redrafting loops by generating specific feedback for the Synthesizer. This mechanism ensures iterative refinement until the content meets the required standards.\n\nHuman-in-the-Loop (HITL) Review:* For final approval, particularly for the Archivist agent's output, a *human review process* is initiated. An email is sent to a human reviewer, prompting them to approve, reject, or comment via a \"Wait for Webhook\" node. This ensures *human oversight** and quality control.\n\nContent Persistence & Versioning:** If the content is approved by the human reviewer:\n\n    It's saved to a PostgreSQL database (specifically to the handbook_entries and agent_contributions tables).\n\n    Optionally, the content can be committed to a GitHub repository for version control, provided the necessary environment variables are configured.\n\nNotifications:* The final output and the sequence of executed agents can be sent as a notification to *Slack**, if configured.\n\nObserve the dynamic loop: orchestrate \u2192 assign \u2192 generate \u2192 review (AI/human) \u2192 store\n\nIncluded AI Agents\n\nThis workflow leverages a suite of specialized AI agents, each with a distinct role in the content pipeline:\n\nMeta-Orchestrator:** Determines the optimal sequence of agents to execute based on the input.\n\nSummarizer Agent:** Summarizes text into key points (e.g., 3 key points).\n\nSynthesizer Agent:** Synthesizes new text and effectively incorporates reprocessing feedback from review agents.\n\nPeer Reviewer Agent:** Reviews generated text, highlighting strengths, weaknesses, and suggestions, and indicates major_issue flags.\n\nSensemaking Agent:** Analyzes input within existing context, identifying patterns, gaps, and areas for improvement.\n\nPrompt Engineer Agent:** Refines or generates prompts for subsequent agents, optimizing their output.\n\nOnboarding/Explainer Agent:** Provides explanations of the process or offers guidance to users.\n\nArchivist Agent:** Prepares content for the handbook, manages the human review process, and handles archiving to the database and GitHub.\n\nSetup Steps & Prerequisites\n\nTo get this powerful workflow up and running, follow these steps:\n\nImport the Workflow: Import the pyragogy_master_workflow.json (or generate-collaborative-handbooks-with-gpt4o-multi-agent-orchestration-human-review.json) into your n8n instance.\n\nConnect Credentials:\n\n    Postgres: Set up a Postgres Pyragogy DB credential (ID: pyragogy-postgres).\n\n    OpenAI: Configure an OpenAI Pyragogy credential (ID: pyragogy-openai) for all OpenAI agents. GPT-4o is highly suggested for optimal performance.\n\n    Email Send: Set up a configured email credential (e.g., for sending human review requests).\n\nDefine Environment Variables: Define essential environment variables (an .env.template is included in the repository). These include:\n\n    API base for OpenAI.\n\n    Database connection details.\n\n    (Optional) GitHub: For content persistence and versioning, configure GITHUB_ACCESS_TOKEN, GITHUB_REPOSITORY_OWNER, and GITHUB_REPOSITORY_NAME.\n\n    (Optional) Slack: For notifications, configure SLACK_WEBHOOK_URL.\n\nSend a sample payload to your webhook URL (/webhook/pyragogy/process):\n\n{\n\"title\": \"History of Peer Learning\",\n\"text\": \"Peer learning is an educational approach where students learn from and with each other...\",\n\"tags\": [\"education\", \"pedagogy\"],\n\"requireHitl\": true\n}\n\nIdeal For\n\nThis workflow is perfectly suited for:\n\nEducators and researchers exploring AI-assisted publishing and co-authoring with AI.\n\nKnowledge teams looking to automate content pipelines for internal or external documentation.\n\nAnyone building collaborative Markdown-driven tools or AI-powered knowledge bases.\n\nDocumentation & Contributions: An Open Source and Collaborative Project\n\nThis workflow is an open-source project and community-driven. Its development is transparent and open to everyone.\n\nWe warmly invite you to:\n\nReview it:** Contribute your analysis, identify potential improvements, or report issues.\n\nRemix it:** Adapt it to your specific needs, integrate new features, or modify it for a different use case.\n\nImprove it:** Propose and implement changes that enhance its efficiency, robustness, or capabilities.\n\nShare it back:** Return your contributions to the community, either through pull requests or by sharing your implementations.\n\nEvery contribution is welcome and valued! All relevant information for verification, improvement, and collaboration can be found in the official repository:\n\n\ud83d\udd17 GitHub \u2013 pyragogy-handbook-n8n-workflow\n",
  "_templateCreatedAt": "2025-06-12T15:46:54.341Z",
  "_templateTotalViews": 1059,
  "_templateCategory": [],
  "_fetchedAt": "2026-03-21T08:17:45.682Z",
  "id": "eTfM9cjV0i7PQtsZ",
  "name": "AI-Driven Handbook Generator with Multi-Agent Orchestration (Pyragogy AI Village)",
  "tags": [
    {
      "id": "cPTJEblSALLWTPH5",
      "name": "Pyragogy",
      "createdAt": "2025-06-12T12:35:25.060Z",
      "updatedAt": "2025-06-12T12:35:25.060Z"
    },
    {
      "id": "6QE2GMT2wg7PfNjo",
      "name": "Multi-Agent",
      "createdAt": "2025-06-12T12:35:25.071Z",
      "updatedAt": "2025-06-12T12:35:25.071Z"
    },
    {
      "id": "NRBaVI2WatxRn6jb",
      "name": "Orchestration",
      "createdAt": "2025-06-12T12:35:25.064Z",
      "updatedAt": "2025-06-12T12:35:25.064Z"
    },
    {
      "id": "Fz7qIHTqqiYux3E1",
      "name": "Human-in-Loop",
      "createdAt": "2025-06-12T12:35:25.065Z",
      "updatedAt": "2025-06-12T12:35:25.065Z"
    }
  ],
  "nodes": [
    {
      "id": "907e30c8-87e4-46f8-8863-9e3abf429415",
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "position": [
        -1600,
        300
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "ae6910fd-9244-4bab-a0f7-c91a4ad84f28",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1400,
        300
      ],
      "parameters": {
        "path": "pyragogy/process",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 1
    },
    {
      "id": "a782c52f-b296-4477-8cf4-454a7df0c8a5",
      "name": "Check DB Connection",
      "type": "n8n-nodes-base.postgres",
      "position": [
        -1200,
        300
      ],
      "parameters": {
        "query": "SELECT 1; -- Verifica connessione DB",
        "operation": "executeQuery",
        "additionalFields": {}
      },
      "typeVersion": 1
    },
    {
      "id": "3df503dd-db5e-4bd1-8925-e715b8bbe03c",
      "name": "Meta-Orchestrator",
      "type": "n8n-nodes-base.openAi",
      "position": [
        -1000,
        300
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {},
        "resource": "chat",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "a4c7eb13-bd36-4c9d-8927-2e6fe54c32a3",
      "name": "Parse Orchestration Plan",
      "type": "n8n-nodes-base.function",
      "position": [
        -800,
        300
      ],
      "parameters": {
        "functionCode": "// Analizza il piano di orchestrazione e imposta per il looping\nlet rawPlan = $json.choices[0].message.content;\nlet plan;\n\ntry {\n  plan = JSON.parse(rawPlan);\n} catch (e) {\n  // Se il parsing fallisce, assumi che sia una stringa di array grezza\n  plan = rawPlan;\n}\n\n// Estrai in modo sicuro la sequenza degli agenti:\n// Se 'plan' \u00e8 un oggetto e ha una chiave 'agents', usala.\n// Altrimenti, se 'plan' \u00e8 un array, usalo direttamente.\n// Altrimenti, predefinisci un array vuoto.\nconst agentSequence = Array.isArray(plan) ? plan : (plan && plan.agents && Array.isArray(plan.agents) ? plan.agents : []);\n\n// Memorizza la sequenza e l'indice corrente per il loop\n$workflow.agentSequence = agentSequence;\n$workflow.currentAgentIndex = 0;\n$workflow.redraftLoopCount = 0; // Inizializza il contatore dei cicli di rielaborazione\n\n// Passa i dati di input al primo agente, assicurandosi che $items[0].json.body esista\nconst initialInput = $items[0] && $items[0].json && $items[0].json.body ? $items[0].json.body : {};\n\nreturn [{ json: { ...initialInput, agentToRun: agentSequence[0] || null } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "915e18ea-1540-4059-883f-739f77f41cc2",
      "name": "More Agents to Run?",
      "type": "n8n-nodes-base.if",
      "position": [
        -600,
        300
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $workflow.currentAgentIndex < $workflow.agentSequence.length }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "6f478d71-791e-4f6b-b5e3-5b7b842181b0",
      "name": "Prepare Agent Input",
      "type": "n8n-nodes-base.function",
      "position": [
        -400,
        200
      ],
      "parameters": {
        "functionCode": "// Ottieni il nome dell'agente corrente\nconst agentName = $workflow.agentSequence[$workflow.currentAgentIndex];\n\n// Prepara i dati per l'esecuzione dell'agente\n// Passa l'output del passo precedente (o l'input iniziale).\n// Se stiamo rielaborando, l'input dell'agente dovrebbe includere il feedback di rielaborazione.\nconst previousOutput = $json.output || $json.body.input; // Assumendo che l'output dell'agente sia memorizzato nella chiave 'output'\nconst agentInput = $json.redraftInput || previousOutput; // Usa redraftInput se presente, altrimenti previousOutput\n\nreturn [{ json: { ...$json, agentToRun: agentName, agentInput: agentInput } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "d575e9c9-2d58-4061-944b-61b32d15eefb",
      "name": "Route Agents with Switch",
      "type": "n8n-nodes-base.switch",
      "position": [
        -200,
        200
      ],
      "parameters": {
        "mode": "json"
      },
      "typeVersion": 1
    },
    {
      "id": "3711824c-5a58-4e3d-95dd-3ca16cc551ca",
      "name": "Summarizer Agent",
      "type": "n8n-nodes-base.openAi",
      "position": [
        0,
        0
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {},
        "resource": "chat",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "67b23742-e3bd-4437-99eb-957e6bc0912e",
      "name": "Synthesizer Agent",
      "type": "n8n-nodes-base.openAi",
      "position": [
        0,
        100
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {},
        "resource": "chat",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "8bfe179e-ed05-43df-b9c6-862f86c1189c",
      "name": "Peer Reviewer Agent",
      "type": "n8n-nodes-base.openAi",
      "position": [
        0,
        200
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {},
        "resource": "chat",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "2dc73ca6-f17f-4214-acbb-3ea3ed470f13",
      "name": "Sensemaking Agent",
      "type": "n8n-nodes-base.openAi",
      "position": [
        0,
        300
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {},
        "resource": "chat",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "ec3b9cc0-a039-4cac-90ca-7de54f41f5d4",
      "name": "Prompt Engineer Agent",
      "type": "n8n-nodes-base.openAi",
      "position": [
        0,
        400
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {},
        "resource": "chat",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "bc382e04-9c5d-4ab9-b874-2da213499614",
      "name": "Onboarding/Explainer Agent",
      "type": "n8n-nodes-base.openAi",
      "position": [
        0,
        500
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {},
        "resource": "chat",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "03017738-b11e-4016-8c05-65204ef7e8c3",
      "name": "Add Handbook Metadata",
      "type": "n8n-nodes-base.function",
      "position": [
        0,
        600
      ],
      "parameters": {
        "functionCode": "// Prepara i metadati per il contenuto dell'Handbook.\n// Assicurati che l'input originale contenga 'title' e 'tags' o imposta dei valori predefiniti.\nconst title = $json.body.title || 'Untitled Handbook Entry';\nconst tags = $json.body.tags || [];\nconst phase = $json.body.phase || 'draft'; // Fase iniziale, pu\u00f2 essere 'final' dopo l'approvazione\nconst rhythm = $json.body.rhythm || 'on-demand'; // Ritmo cognitivo\n\nreturn [{ json: { ...$json, handbookTitle: title, handbookTags: tags, handbookPhase: phase, handbookRhythm: rhythm } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "d183d513-00a3-4a38-9ef8-37a646d2ad15",
      "name": "Generate Content for Review",
      "type": "n8n-nodes-base.function",
      "position": [
        200,
        600
      ],
      "parameters": {
        "functionCode": "// Prepara il contenuto proposto dall'Archivista per la revisione umana, inclusa la formattazione YAML.\n// Assicurati che l'input dell'agente (il contenuto generato) sia disponibile.\nconst proposedContent = $json.agentInput;\nconst title = $json.handbookTitle;\nconst tags = $json.handbookTags;\nconst phase = $json.handbookPhase;\nconst rhythm = $json.handbookRhythm;\n\n// Costruisci il front-matter YAML\nconst yamlFrontMatter = `---\ntitle: \"${title.replace(/\"/g, '\\\"')}\"\ntags: [${tags.map(t => `\"${t.replace(/\"/g, '\\\"')}\"`).join(', ')}]\nphase: \"${phase}\"\nrhythm: \"${rhythm}\"\n---\n\n`;\n\nconst finalMarkdownContent = yamlFrontMatter + proposedContent;\n\nreturn [{ json: { ...$json, proposedContent: proposedContent, reviewTitle: title, reviewTags: tags, finalMarkdownContent: finalMarkdownContent } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "41919337-0762-47d0-9c7c-d51fa966caae",
      "name": "Generate Review ID",
      "type": "n8n-nodes-base.function",
      "position": [
        400,
        600
      ],
      "parameters": {
        "functionCode": "// Genera un ID univoco per questa richiesta di revisione.\n// Questo ID verr\u00e0 usato per correlare la risposta del revisore con questa istanza del workflow.\nconst reviewId = crypto.randomUUID();\n\nreturn [{ json: { ...$json, reviewId: reviewId } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "6fb5b6d5-4ea8-49c9-9ebc-3c37eb2897b8",
      "name": "Send Review Request Email",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        600,
        600
      ],
      "parameters": {
        "html": "<h3>Revisione Contenuto Pyragogy Handbook: {{ $json.reviewTitle }}</h3>\n<p>Ciao revisore,</p>\n<p>\u00c8 stato proposto un nuovo contenuto per l'Handbook:</p>\n<hr>\n<pre>{{ $json.proposedContent }}</pre>\n<hr>\n<p><strong>Titolo:</strong> {{ $json.reviewTitle }}</p>\n<p><strong>Tags:</strong> {{ $json.reviewTags.join(', ') }}</p>\n<p>Per favore, clicca su uno dei seguenti link per approvare o rifiutare:</p>\n<p><a href=\"your_n8n_url/webhook/pyragogy/review-feedback?reviewId={{ $json.reviewId }}&status=approved\">Approva</a></p>\n<p><a href=\"your_n8n_url/webhook/pyragogy/review-feedback?reviewId={{ $json.reviewId }}&status=rejected\">Rifiuta</a></p>\n<p>Grazie!</p>",
        "text": "Ciao revisore,\n\n\u00c8 stato proposto un nuovo contenuto per l'Handbook:\n\n---\n{{ $json.proposedContent }}\n---\n\nTitolo: {{ $json.reviewTitle }}\nTags: {{ $json.reviewTags.join(', ') }}\n\nPer favore, clicca su uno dei seguenti link per approvare o rifiutare:\n\nApprova: your_n8n_url/webhook/pyragogy/review-feedback?reviewId={{ $json.reviewId }}&status=approved\nRifiuta: your_n8n_url/webhook/pyragogy/review-feedback?reviewId={{ $json.reviewId }}&status=rejected\n\nGrazie!",
        "options": {},
        "subject": "Revisione Contenuto Pyragogy Handbook: {{ $json.reviewTitle }}",
        "toEmail": "user@example.com",
        "fromEmail": "user@example.com"
      },
      "typeVersion": 1
    },
    {
      "id": "e69480df-aab3-4b05-8c2b-5ec369966084",
      "name": "Wait for Human Approval",
      "type": "n8n-nodes-base.wait",
      "position": [
        800,
        600
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "80fca66e-fdc6-49b1-9873-b10c03409926",
      "name": "Human Decision Split",
      "type": "n8n-nodes-base.if",
      "position": [
        1000,
        600
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.query.status === 'approved' }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ed575c60-32e2-411e-9619-ac40ac647e3b",
      "name": "Save to handbook_entries",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1200,
        500
      ],
      "parameters": {
        "table": "handbook_entries",
        "columns": "title, content, version, created_by, tags, phase, rhythm",
        "additionalFields": {}
      },
      "typeVersion": 1
    },
    {
      "id": "a43a61ae-0062-4705-8ef2-a65d8bd65fb7",
      "name": "Prepare Approved Contribution Data",
      "type": "n8n-nodes-base.function",
      "position": [
        1400,
        500
      ],
      "parameters": {
        "functionCode": "// Registra il contributo dell'agente dopo l'approvazione umana\nconst entryId = $json.id; // ID dall'inserimento in handbook_entries\nconst agentName = 'Archivist';\nconst contributionType = 'Archiving (Approved)';\nconst details = { input: $json.proposedContent, metadata: { title: $json.reviewTitle, version: $json.version, tags: $json.reviewTags, phase: $json.handbookPhase, rhythm: $json.handbookRhythm }, reviewStatus: 'approved' };\n\n$items[0].json.contribution = { entryId, agentName, contributionType, details };\nreturn $items;"
      },
      "typeVersion": 1
    },
    {
      "id": "4a03b029-e65f-4844-a605-f668c39f05f7",
      "name": "Save Agent Contribution (Approved)",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1600,
        500
      ],
      "parameters": {
        "table": "agent_contributions",
        "columns": "entry_id, agent_name, contribution_type, details",
        "additionalFields": {}
      },
      "typeVersion": 1
    },
    {
      "id": "9a3b2bdc-b894-4b5d-a783-003c1d0cbac5",
      "name": "Generate GitHub File Path",
      "type": "n8n-nodes-base.function",
      "position": [
        1800,
        500
      ],
      "parameters": {
        "functionCode": "// Genera il percorso del file GitHub con slug e timestamp per il versioning.\nconst chapterSlug = ($json.reviewTitle || 'untitled').replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();\nconst timestamp = new Date().toISOString().replace(/[:.-]/g, ''); // Rimuove caratteri non validi per i nomi di file\nconst filePathWithVersion = `content/${chapterSlug}_v${timestamp}.md`;\n\nreturn [{ json: { ...$json, githubFilePath: filePathWithVersion } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "42831df9-f694-43fa-8612-b9aee95bef6c",
      "name": "GitHub Enabled?",
      "type": "n8n-nodes-base.if",
      "position": [
        2000,
        500
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $env.GITHUB_ACCESS_TOKEN }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2049ac7d-bf01-4d9b-a222-ca988892d1e6",
      "name": "Commit to GitHub (Approved)",
      "type": "n8n-nodes-base.github",
      "position": [
        2200,
        500
      ],
      "parameters": {
        "owner": "={{ $env.GITHUB_REPOSITORY_OWNER }}",
        "filePath": "={{ $json.githubFilePath }}",
        "resource": "file",
        "operation": "createUpdate",
        "repository": "={{ $env.GITHUB_REPOSITORY_NAME }}"
      },
      "typeVersion": 1
    },
    {
      "id": "352599e9-8b68-412c-bd28-f92844e9c712",
      "name": "Log Human Rejection",
      "type": "n8n-nodes-base.function",
      "position": [
        1200,
        700
      ],
      "parameters": {
        "functionCode": "// Registra il rifiuto umano del contenuto\nconst agentName = 'Archivist';\nconst reviewId = $json.reviewId;\nconst reviewStatus = 'rejected';\nconst reviewComments = $json.query.comments || 'Nessun commento fornito.';\nconst proposedContent = $json.proposedContent;\nconst title = $json.reviewTitle;\n\nconsole.log(`Contenuto proposto dall'Archivista (ID: ${reviewId}, Titolo: ${title}) rifiutato dall'umano. Commenti: ${reviewComments}`);\n\n// Prepara l'output per indicare il rifiuto e consentire al flusso di continuare.\nreturn [{ json: { ...$json, reviewStatus: reviewStatus, reviewComments: reviewComments, output: { message: `Archivista: Contenuto rifiutato dall'umano.`, status: 'rejected', comments: reviewComments } } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "6f0ea479-d689-40fc-a72f-d1ad161f8743",
      "name": "Merge Archivist Paths",
      "type": "n8n-nodes-base.merge",
      "position": [
        2400,
        600
      ],
      "parameters": {
        "mode": "mergeByPropertyName"
      },
      "typeVersion": 1
    },
    {
      "id": "ffd0ab23-cb9d-4ac1-ad3d-6fe856ca1fad",
      "name": "Evaluate Board Consensus",
      "type": "n8n-nodes-base.function",
      "position": [
        200,
        300
      ],
      "parameters": {
        "functionCode": "// Valuta i flag 'major_issue' dagli agenti di revisione per determinare se \u00e8 necessaria una rielaborazione.\nlet majorIssueCount = 0;\nlet redraftFeedback = '';\n\n// Assumi che gli output degli agenti di revisione siano accessibili tramite $node\n// (Ad esempio, se Peer Reviewer -> Sensemaking -> Prompt Engineer sono sequenziali prima di questo nodo)\n\nif ($node[\"Peer Reviewer Agent\"] && $node[\"Peer Reviewer Agent\"].json && $node[\"Peer Reviewer Agent\"].json.choices && $node[\"Peer Reviewer Agent\"].json.choices[0] && $node[\"Peer Reviewer Agent\"].json.choices[0].message && $node[\"Peer Reviewer Agent\"].json.choices[0].message.content) {\n    const peerReviewOutput = JSON.parse($node[\"Peer Reviewer Agent\"].json.choices[0].message.content);\n    if (peerReviewOutput.major_issue) majorIssueCount++;\n    redraftFeedback += `Peer Reviewer: ${peerReviewOutput.suggestions || ''}\\n`;\n}\n\nif ($node[\"Sensemaking Agent\"] && $node[\"Sensemaking Agent\"].json && $node[\"Sensemaking Agent\"].json.choices && $node[\"Sensemaking Agent\"].json.choices[0] && $node[\"Sensemaking Agent\"].json.choices[0].message && $node[\"Sensemaking Agent\"].json.choices[0].message.content) {\n    const sensemakingOutput = JSON.parse($node[\"Sensemaking Agent\"].json.choices[0].message.content);\n    if (sensemakingOutput.major_issue) majorIssueCount++;\n    redraftFeedback += `Sensemaking Agent: ${sensemakingOutput.suggestions || ''}\\n`;\n}\n\nif ($node[\"Prompt Engineer Agent\"] && $node[\"Prompt Engineer Agent\"].json && $node[\"Prompt Engineer Agent\"].json.choices && $node[\"Prompt Engineer Agent\"].json.choices[0] && $node[\"Prompt Engineer Agent\"].json.choices[0].message && $node[\"Prompt Engineer Agent\"].json.choices[0].message.content) {\n    const promptEngineerOutput = JSON.parse($node[\"Prompt Engineer Agent\"].json.choices[0].message.content);\n    if (promptEngineerOutput.major_issue) majorIssueCount++;\n    redraftFeedback += `Prompt Engineer: ${promptEngineerOutput.suggestions || ''}\\n`;\n}\n\nconst redraftNeeded = majorIssueCount >= 2; // Voto a maggioranza\n\nreturn [{ json: { ...$json, redraftNeeded: redraftNeeded, redraftFeedback: redraftFeedback } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "f2ec87e5-0a06-4d8f-896b-a3d203fcea41",
      "name": "Check Redraft Needed",
      "type": "n8n-nodes-base.if",
      "position": [
        400,
        300
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.redraftNeeded && $workflow.redraftLoopCount < 2 }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "413074bb-38ad-42d6-8d63-402e33528a5b",
      "name": "Handle Redraft",
      "type": "n8n-nodes-base.function",
      "position": [
        600,
        200
      ],
      "parameters": {
        "functionCode": "// Gestisce la logica di rielaborazione: incrementa il contatore e reindirizza al Synthesizer.\n\n$workflow.redraftLoopCount += 1; // Incrementa il contatore del ciclo di rielaborazione\n\n// Trova l'indice del Synthesizer nella sequenza degli agenti\nconst synthesizerIndex = $workflow.agentSequence.indexOf(\"Synthesizer\");\n\n// Se il Synthesizer non \u00e8 il prossimo agente, imposta l'indice corrente per farlo ripartire dal Synthesizer.\n// Questo garantisce che il Synthesizer venga eseguito successivamente per la rielaborazione.\nif ($workflow.currentAgentIndex !== synthesizerIndex) {\n    $workflow.currentAgentIndex = synthesizerIndex;\n} else {\n    // Se siamo gi\u00e0 sul Synthesizer (es. dopo il primo passaggio del loop), assicurati che l'indice vada avanti normalmente nel prossimo ciclo.\n    // Questo \u00e8 un caso limite, di solito il Prepare Agent Input lo gestir\u00e0.\n}\n\n// Passa il feedback di rielaborazione come input per il Synthesizer.\n// Il nodo 'Prepare Agent Input' utilizzer\u00e0 questo campo per aggiornare 'agentInput'.\nreturn [{ json: { ...$json, redraftInput: $json.output + \"\\n\\nFeedback per la rielaborazione dal Peer Review Board:\\n\" + $json.redraftFeedback } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "6508f0f9-09c2-4fe6-8cae-eb90ea58d875",
      "name": "Process Agent Output",
      "type": "n8n-nodes-base.function",
      "position": [
        400,
        200
      ],
      "parameters": {
        "functionCode": "// Ottieni il nome dell'agente appena eseguito\nconst agentName = $json.agentToRun;\nlet agentOutput = '';\n\n// Costruisci dinamicamente il nome del nodo per il recupero dell'output\nlet actualNodeName;\n\n// Gestione speciale per l'Archivista dopo la revisione umana\nif (agentName === 'Archivist') {\n    if ($json.query && $json.query.status === 'approved') {\n        agentOutput = { message: 'Contenuto archiviato con successo dopo l'approvazione umana.', entryId: $json.id || 'N/A', handbookMetadata: { title: $json.reviewTitle, tags: $json.reviewTags, phase: $json.handbookPhase, rhythm: $json.handbookRhythm } };\n    } else if ($json.query && $json.query.status === 'rejected') {\n        agentOutput = { message: 'Archiviazione rifiutata dall'umano.', reviewComments: $json.query.comments || 'Nessun commento' };\n    } else if ($json.reviewStatus === 'rejected') {\n        // Caso in cui la revisione umana \u00e8 stata rifiutata e si proviene dal ramo di rifiuto\n        agentOutput = { message: 'Archiviazione rifiutata dall'umano (tramite ramo di rifiuto).', reviewComments: $json.reviewComments || 'Nessun commento' };\n    } else {\n        agentOutput = { message: 'Processo Archivista: Attesa revisione umana o stato inatteso.', status: 'pending_review' };\n    }\n} else {\n    // Logica originale per gli altri agenti OpenAI\n    if (agentName === 'Onboarding/Explainer') {\n        actualNodeName = 'Onboarding/Explainer Agent';\n    } else {\n        actualNodeName = agentName + ' Agent';\n    }\n\n    // Tenta in modo sicuro di recuperare l'output dal nodo determinato dinamicamente\n    // Tenta anche di analizzare il JSON se l'output dell'agente \u00e8 un oggetto JSON (come per gli agenti di revisione)\n    let rawContent = '';\n    if ($node[actualNodeName] && $node[actualNodeName].json && $node[actualNodeName].json.choices && $node[actualNodeName].json.choices[0] && $node[actualNodeName].json.choices[0].message && $node[actualNodeName].json.choices[0].message.content) {\n        rawContent = $node[actualNodeName].json.choices[0].message.content;\n    } else {\n        console.warn(`Impossibile trovare l'output chat OpenAI standard per il nodo: ${actualNodeName}. Ritorno al JSON grezzo.`);\n        agentOutput = $node[actualNodeName] ? $node[actualNodeName].json : `Nessun output specifico trovato per ${agentName}`;\n    }\n\n    // Tenta di analizzare il contenuto come JSON se l'agente \u00e8 un agente di revisione\n    if (agentName === 'Peer Reviewer' || agentName === 'Sensemaking Agent' || agentName === 'Prompt Engineer') {\n        try {\n            agentOutput = JSON.parse(rawContent);\n        } catch (e) {\n            console.warn(`Impossibile analizzare l'output di ${agentName} come JSON. Trattato come stringa.`);\n            agentOutput = rawContent;\n        }\n    } else {\n        agentOutput = rawContent;\n    }\n}\n\n// Registra il contributo\nconst contribution = {\n    agent: agentName,\n    output: agentOutput,\n    timestamp: new Date().toISOString()\n};\n\n// Assicurati che l'array dei contributi esista e aggiungi il nuovo contributo\nconst existingContributions = Array.isArray($json.contributions) ? $json.contributions : [];\n\n// Incrementa l'indice dell'agente per il loop (solo se non siamo in rielaborazione e questo non \u00e8 un agente di revisione che porta a rielaborazione)\n// Questo \u00e8 gestito dalla logica di 'Handle Redraft' che forza l'indice per il riavvio.\nif (!($json.redraftNeeded && $workflow.redraftLoopCount < 2 && agentName !== 'Synthesizer')) { // Evita doppio incremento se riavvia il Synthesizer\n    $workflow.currentAgentIndex += 1;\n}\n\n// Restituisce l'elemento aggiornato, preservando l'input originale, aggiungendo l'output corrente e tutti i contributi\nreturn [{ json: { ...$items[0].json, output: agentOutput, contributions: [...existingContributions, contribution] } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "2fd3c340-a2d0-489c-a098-42530fdb4c61",
      "name": "Slack Enabled?",
      "type": "n8n-nodes-base.if",
      "position": [
        -400,
        500
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $env.SLACK_WEBHOOK_URL }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "314c7a11-493a-4550-9cd0-f1fc5e5b2994",
      "name": "Notify Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        -200,
        500
      ],
      "parameters": {
        "text": "Pyragogy AI Village Workflow Completato!\nInput: {{ $json.body.input }}\nOutput Finale: {{ JSON.stringify($json.output) }}\nAgenti Eseguiti: {{ $workflow.agentSequence.join(', ') }}",
        "attachments": [],
        "otherOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "564a52df-8f4f-4be6-a219-b8c965eccd3b",
      "name": "Final Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -400,
        400
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ { finalOutput: $json.output, contributions: $json.contributions, agentSequence: $workflow.agentSequence } }}"
      },
      "typeVersion": 1
    },
    {
      "id": "generated-ce444b1c-b361-43c8-a0b6-29a1324993c5",
      "name": "Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1600,
        -140
      ],
      "parameters": {
        "width": 1100,
        "height": 400,
        "content": "## Pyragogy AI Village Workflow\n\nThis workflow orchestrates an AI Village process for generating, reviewing, and publishing content to a handbook.\n\n**Key Stages:**\n1.  **Trigger & DB Check:** Initiates the workflow via webhook and verifies database connection.\n2.  **Orchestration:** Meta-Orchestrator plans agent execution.\n3.  **Agent Execution Loop:** Dynamically runs various AI agents (Summarizer, Synthesizer, Peer Reviewer, Sensemaking, Prompt Engineer, Onboarding/Explainer) based on the orchestration plan. Includes a redraft mechanism for iterative improvements.\n4.  **Human Review:** Content generated by agents (specifically Archivist) undergoes human approval.\n5.  **Publishing:** Approved content is saved to a PostgreSQL database and committed to GitHub.\n6.  **Notifications:** Sends final response and Slack notifications.\n\n**Note:** Several nodes have credential issues (Postgres, OpenAI, SMTP, GitHub, Slack) and require configuration to function correctly."
      }
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "a46219cb-1681-4236-bc4b-9c0c4f58389f",
  "connections": {
    "Notify Slack": {
      "main": [
        [
          {
            "node": "Final Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Handle Redraft": {
      "main": [
        [
          {
            "node": "More Agents to Run?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Enabled?": {
      "main": [
        [
          {
            "node": "Notify Slack",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Final Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub Enabled?": {
      "main": [
        [
          {
            "node": "Commit to GitHub (Approved)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge Archivist Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Check DB Connection",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Summarizer Agent": {
      "main": [
        [
          {
            "node": "Process Agent Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Meta-Orchestrator": {
      "main": [
        [
          {
            "node": "Parse Orchestration Plan",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sensemaking Agent": {
      "main": [
        [
          {
            "node": "Prompt Engineer Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Synthesizer Agent": {
      "main": [
        [
          {
            "node": "Process Agent Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Review ID": {
      "main": [
        [
          {
            "node": "Send Review Request Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check DB Connection": {
      "main": [
        [
          {
            "node": "Meta-Orchestrator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Human Rejection": {
      "main": [
        [
          {
            "node": "Merge Archivist Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "More Agents to Run?": {
      "main": [
        [
          {
            "node": "Prepare Agent Input",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack Enabled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Peer Reviewer Agent": {
      "main": [
        [
          {
            "node": "Sensemaking Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Agent Input": {
      "main": [
        [
          {
            "node": "Route Agents with Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Redraft Needed": {
      "main": [
        [
          {
            "node": "Handle Redraft",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Process Agent Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Human Decision Split": {
      "main": [
        [
          {
            "node": "Save to handbook_entries",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Human Rejection",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Agent Output": {
      "main": [
        [
          {
            "node": "More Agents to Run?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Handbook Metadata": {
      "main": [
        [
          {
            "node": "Generate Content for Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Archivist Paths": {
      "main": [
        [
          {
            "node": "Process Agent Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prompt Engineer Agent": {
      "main": [
        [
          {
            "node": "Evaluate Board Consensus",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Human Approval": {
      "main": [
        [
          {
            "node": "Human Decision Split",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate Board Consensus": {
      "main": [
        [
          {
            "node": "Check Redraft Needed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Orchestration Plan": {
      "main": [
        [
          {
            "node": "More Agents to Run?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route Agents with Switch": {
      "main": [
        [
          {
            "node": "Summarizer Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to handbook_entries": {
      "main": [
        [
          {
            "node": "Prepare Approved Contribution Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate GitHub File Path": {
      "main": [
        [
          {
            "node": "GitHub Enabled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Review Request Email": {
      "main": [
        [
          {
            "node": "Wait for Human Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Onboarding/Explainer Agent": {
      "main": [
        [
          {
            "node": "Process Agent Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Commit to GitHub (Approved)": {
      "main": [
        [
          {
            "node": "Merge Archivist Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Content for Review": {
      "main": [
        [
          {
            "node": "Generate Review ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Approved Contribution Data": {
      "main": [
        [
          {
            "node": "Save Agent Contribution (Approved)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Agent Contribution (Approved)": {
      "main": [
        [
          {
            "node": "Generate GitHub File Path",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

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

About this workflow

AI-Driven Handbook Generator with Multi-Agent Orchestration (Pyragogy AI Village). Uses start, postgres, openAi, emailSend. Webhook trigger; 36 nodes.

Source: https://github.com/NEXUS-OMEGA-Autoresearch-Karpathy/omega-sports/blob/8c0c3f79b91e1be816bd3cffd6ded2cff2f5a13a/workflows/pyragogy_handbook_4904.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

Pyragogy AI Village - Orchestrazione Master (Architettura Profonda V2). Uses start, postgres, openAi, emailSend. Webhook trigger; 37 nodes.

Start, Postgres, OpenAI +4
AI & RAG

AI-Driven Handbook Generator with Multi-Agent Orchestration (Pyragogy AI Village). Uses start, postgres, openAi, emailSend. Webhook trigger; 36 nodes.

Start, Postgres, OpenAI +3
AI & RAG

AI-Driven Handbook Generator with Multi-Agent Orchestration (Pyragogy AI Village). Uses start, postgres, openAi, emailSend. Webhook trigger; 36 nodes.

Start, Postgres, OpenAI +3
AI & RAG

Pyragogy AI Village - Orchestrazione Master (Architettura Profonda V2). Uses start, postgres, openAi, emailSend. Webhook trigger; 36 nodes.

Start, Postgres, OpenAI +4
AI & RAG

AI-Driven Handbook Generator with Multi-Agent Orchestration (Pyragogy AI Village). Uses start, postgres, openAi, emailSend. Webhook trigger; 36 nodes.

Start, Postgres, OpenAI +3