{
  "id": "eTfM9cjV0i7PQtsZ",
  "meta": {
    "site": "https://github.com/zengfr/n8n-workflow-all-templates",
    "name": "Pyragogy AI-Driven Handbook Generator with Multi-Agent Orchestration",
    "wechat": "youandme10086",
    "id": 4904,
    "update_time": "2025-11-10"
  },
  "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": "human-reviewer@example.com",
        "fromEmail": "your-email@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
          }
        ]
      ]
    }
  }
}