{
  "name": "Turn meeting notes into decisions and actions",
  "nodes": [
    {
      "parameters": {
        "content": "## Turn meeting notes into decisions and actions\nTurn rough meeting notes into a clean list of what was decided and who will do what by when, with each item tied to the line in the notes it came from.\n\n**How to use**\n1. Open **Chat model** and pick your own credential. To use another AI service, delete that node, add the service's chat model node, and connect it to the Model plug under every step and AI check.\n2. Open **Inputs** and paste your material into each field. Examples are filled in so you can try it first.\n3. Click **Execute workflow**.\n4. Open **Result** to read the answer, anything flagged, and the checks left for you.\n\n**Checks**\n- Check 1 (a plain rule, run automatically with no AI): Every quote in the Source line column appears word for word in the meeting notes. If it fails: try once more, then flag.\n- Check 2 (a plain rule, run automatically with no AI): Every row of the table has something written in \"Owner\", \"Due date\" and \"Follow up\" (a blank or a dash does not count). If it fails: try once more, then flag.\n- Check 3 (a plain rule, run automatically with no AI): Every row of the table has something written in \"Who answers\" (a blank or a dash does not count). If it fails: try once more, then flag.\n- Check 4 (a plain rule, run automatically with no AI): Every quote in the Source line column appears word for word in the meeting notes. If it fails: try once more, then flag.\n- Check 5 (a second AI prompt answers pass or fail): Compare the three lists below with the original meeting notes. Does every decision, action, owner, date and question come from the notes, with nothing added or changed? If it fails: try once more, then flag.\n\nFrom Logic Lab (logiclabhq.com). Free to use and adapt, with credit.",
        "height": 600,
        "width": 760,
        "color": 5
      },
      "id": "632cb3b9-0dd8-4268-a8a9-01cf05abca96",
      "name": "About this workflow",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -560
      ]
    },
    {
      "parameters": {},
      "id": "f15b4971-de7d-4a54-af61-d3effcbaee9a",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        320
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "6c2acb5f-7b25-45f6-ac14-9251c8088ce8",
              "name": "notes",
              "value": "Sample: Weekly ops call, Tue 14 Oct. Present: Dana, Luis, Priya, Sam.\nDana: budget review moved to Nov 3, everyone ok with that.\nLuis will send the revised vendor list to Priya by Friday.\nAgreed we stop the paper sign-in sheet from next month and use the tablet form.\nPriya asked who is updating the onboarding guide. No one took it.\nSam to book the room for the training, sometime before the 20th.",
              "type": "string"
            },
            {
              "id": "3d8923ef-473e-49a6-a358-4995c29975cc",
              "name": "meeting",
              "value": "Sample: Weekly ops call, 14 Oct",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "a9b8be56-6558-415d-a1a2-73c8c6696e4f",
      "name": "Inputs",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        320
      ],
      "notes": "Paste your own material here: notes = Meeting notes; meeting = Meeting name and date (optional).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are an experienced meeting secretary who writes careful, neutral records.\n\nYour task is to read the meeting notes below and find every decision: something the group agreed, chose, approved, changed or stopped.\n\nMeeting: {{ $(\"Inputs\").first().json[\"meeting\"] }}\n\nMeeting notes:\n{{ $(\"Inputs\").first().json[\"notes\"] }}\n\nRules:\n- Use only what is in the notes. Do not add decisions that are not written there.\n- Copy the source line word for word from the notes into the Source line column. Copy a short part if the line is long, but do not change any words.\n- Do not merge two decisions into one row. Do not include actions or open questions. Do not give advice or comments.\n\nReturn exactly one markdown table and nothing else:\n| # | Decision | Source line |\n\nIf there are no decisions, keep the table and add one row that says \"None found\".{{ $(\"Check 1: Every item quotes its note\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every item quotes its note. What was wrong: \" + $(\"Check 1: Every item quotes its note\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "6b224b58-83f2-4e21-aaa9-7e3abd7d4003",
      "name": "1. Pull out decisions",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        480,
        320
      ],
      "notes": "Find every decision in the notes, and copy the line each one comes from. Returns: A table of decisions, each row with the line it came from.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every item quotes its note\n// Rule: Every quote in the Source line column appears word for word in the meeting notes.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"1. Pull out decisions\").first().json.text ?? '');\nconst sources = {\n  \"notes\": String($(\"Inputs\").first().json[\"notes\"] ?? ''),\n};\nconst result = runRule(\"column_quotes_source\", {\"column\":\"Source line\",\"input\":\"notes\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "e74d235f-5fff-429e-aabf-5ed97ca80fd8",
      "name": "Check 1: Every item quotes its note",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        320
      ],
      "notes": "Every quote in the Source line column appears word for word in the meeting notes.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "40a55628-c5d0-450f-a025-61822b370989",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "70b46f59-6ff6-4764-a771-3707fa7c52e2",
      "name": "Passed check 1?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1000,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "199f3f33-7e56-48fc-addf-009dcdd240a6",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "dcba6f67-b1e6-409c-aad1-a97d35b2b46a",
      "name": "Try again? (check 1)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1000,
        520
      ],
      "notes": "True = first failure: send back to \"1. Pull out decisions\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e99e9f02-655a-429d-a7fd-e2383a3d143b",
              "name": "flag",
              "value": "=Every item quotes its note: did not pass. {{ $(\"Check 1: Every item quotes its note\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "72da71de-2c06-4701-a5d1-0e1c36e3f9bf",
      "name": "Flag check 1",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1130,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a project coordinator who makes sure no action is left without an owner or a date.\n\nYour task is to read the meeting notes below and list every action: something a person or team will do after the meeting. Decisions are already listed, so do not repeat them.\n\nMeeting notes:\n{{ $(\"Inputs\").first().json[\"notes\"] }}\n\nDecisions already listed:\n{{ $(\"1. Pull out decisions\").first().json.text }}\n\nRules:\n- Use only what is in the notes. Do not add actions, owners or dates that are not written there.\n- If an action has no named owner, write NOT STATED in the Owner column. If it has no date, write NOT STATED in the Due date column.\n- If a date is vague (for example \"next week\" or \"sometime before the 20th\"), keep the words from the notes and write FLAGGED: vague date.\n- In the Follow up column write OK for a complete action. For an action with a gap write FLAGGED and say in a few words what is missing (for example \"FLAGGED: no owner\").\n- Copy the source line word for word from the notes into the Source line column. Do not change any words.\n- Do not merge two actions into one row.\n\nReturn exactly one markdown table and nothing else:\n| # | Action | Owner | Due date | Source line | Follow up |\n\nIf there are no actions, keep the table and add one row that says \"None found\".{{ $(\"Check 2: Every action has an owner and a date, or is flagged\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every action has an owner and a date, or is flagged. What was wrong: \" + $(\"Check 2: Every action has an owner and a date, or is flagged\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 4: Every action quotes its note\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every action quotes its note. What was wrong: \" + $(\"Check 4: Every action quotes its note\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "cf6760a9-2d7b-4728-a2e0-e6db4e6e6d12",
      "name": "2. List the actions",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1260,
        320
      ],
      "notes": "Find every action, with its owner and date, and flag any that are missing one. Returns: A table of actions, each with an owner, a due date, the line it came from and an OK or FLAGGED mark.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every action has an owner and a date, or is flagged\n// Rule: Every row of the table has something written in \"Owner\", \"Due date\" and \"Follow up\" (a blank or a dash does not count).\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"2. List the actions\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"table_cells_filled\", [\"Owner\",\"Due date\",\"Follow up\"], work, sources);\nreturn [{ json: result }];"
      },
      "id": "ca1f1365-c213-4c02-a5f4-d72b0e081d60",
      "name": "Check 2: Every action has an owner and a date, or is flagged",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1520,
        320
      ],
      "notes": "Every row of the table has something written in \"Owner\", \"Due date\" and \"Follow up\" (a blank or a dash does not count).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "41a557bb-c4d0-437c-a125-63152a3707f6",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f0bbf49e-eff9-4f7b-a76e-2ef07a74cd9d",
      "name": "Passed check 2?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1780,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "189f3da0-7f56-4a8f-acde-ff0aced24239",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "3cc1c24c-11de-48f7-a2ce-c72e4db518c9",
      "name": "Try again? (check 2)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1780,
        520
      ],
      "notes": "True = first failure: send back to \"2. List the actions\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ea9ea095-645a-410a-a8fd-e3cb393d12a8",
              "name": "flag",
              "value": "=Every action has an owner and a date, or is flagged: did not pass. {{ $(\"Check 2: Every action has an owner and a date, or is flagged\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "71da704b-2906-4248-a8d1-12d537e3fb52",
      "name": "Flag check 2",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1910,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are an experienced meeting secretary who makes sure nothing is lost between meetings.\n\nYour task is to read the meeting notes below and list every open question: something asked or doubted that the meeting did not settle. Decisions and actions are already listed, so do not repeat them.\n\nMeeting notes:\n{{ $(\"Inputs\").first().json[\"notes\"] }}\n\nDecisions already listed:\n{{ $(\"1. Pull out decisions\").first().json.text }}\n\nActions already listed:\n{{ $(\"2. List the actions\").first().json.text }}\n\nRules:\n- Use only what is in the notes. Do not invent questions.\n- In the Who answers column write the person the notes name. If the notes name no one, write NOT STATED.\n- Copy the source line word for word from the notes into the Source line column. Do not change any words.\n- Do not answer the questions.\n\nReturn exactly one markdown table and nothing else:\n| # | Open question | Who answers | Source line |\n\nIf there are no open questions, keep the table and add one row that says \"None found\".{{ $(\"Check 3: Every open question says who answers\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Every open question says who answers. What was wrong: \" + $(\"Check 3: Every open question says who answers\").first().json.reason + \"\\nFix that and give your full answer again.\" : \"\" }}{{ $(\"Check 5: Nothing was added that is not in the notes\").isExecuted ? \"\\n\\nYour last answer did not pass this check: Nothing was added that is not in the notes. What was wrong: \" + $(\"Check 5: Nothing was added that is not in the notes\").first().json.text + \"\\nFix that and give your full answer again.\" : \"\" }}"
      },
      "id": "050e7803-a616-4104-ac85-879d9c41c26e",
      "name": "3. Note open questions",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        2040,
        320
      ],
      "notes": "List the questions the meeting left open, and who should answer each. Returns: A table of open questions, each with who should answer it and the line it came from.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every open question says who answers\n// Rule: Every row of the table has something written in \"Who answers\" (a blank or a dash does not count).\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"3. Note open questions\").first().json.text ?? '');\nconst sources = {\n\n};\nconst result = runRule(\"table_cells_filled\", [\"Who answers\"], work, sources);\nreturn [{ json: result }];"
      },
      "id": "4f3a08f4-f9ce-4a19-aa29-e33eff928b23",
      "name": "Check 3: Every open question says who answers",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2300,
        320
      ],
      "notes": "Every row of the table has something written in \"Who answers\" (a blank or a dash does not count).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "42a5594e-c7d0-4835-ae25-5e5c29370663",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f0b9b607-effb-4e12-a76b-f059fa77d5b4",
      "name": "Passed check 3?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2560,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "1b9f4259-8056-4c22-abde-fd77cbd23d80",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "58bfafc9-35e1-403a-aecc-4feb31b72b4c",
      "name": "Try again? (check 3)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2560,
        520
      ],
      "notes": "True = first failure: send back to \"3. Note open questions\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e79e9bdc-635a-4f77-a9fd-e55e3c3d1761",
              "name": "flag",
              "value": "=Every open question says who answers: did not pass. {{ $(\"Check 3: Every open question says who answers\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "70da6eb8-2a06-43db-a7d1-114238e3fce5",
      "name": "Flag check 3",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2690,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Every action quotes its note\n// Rule: Every quote in the Source line column appears word for word in the meeting notes.\n// Runs with no AI. Returns pass (true or false) and a reason.\nconst runRule = function runRule(rule, value, work, sources) {\n    const norm = (s) => String(s ?? '')\n        .toLowerCase()\n        .replace(/[\\u2018\\u2019\\u201c\\u201d\"'`]/g, '')\n        .replace(/\\s+/g, ' ')\n        .trim();\n    const readTable = (text) => {\n        const lines = String(text ?? '')\n            .split(/\\r?\\n/)\n            .map((l) => l.trim());\n        const tables = [];\n        let cur = null;\n        const cells = (l) => l\n            .replace(/^\\|/, '')\n            .replace(/\\|$/, '')\n            .split('|')\n            .map((c) => c.trim());\n        for (let i = 0; i < lines.length; i++) {\n            const l = lines[i] ?? '';\n            if (!l.startsWith('|')) {\n                cur = null;\n                continue;\n            }\n            if (!cur) {\n                const next = lines[i + 1] ?? '';\n                if (/^\\|?\\s*:?-{2,}/.test(next)) {\n                    cur = { header: cells(l), rows: [] };\n                    tables.push(cur);\n                    i++;\n                }\n                continue;\n            }\n            cur.rows.push(cells(l));\n        }\n        return tables;\n    };\n    // Every table that has this column, with \"None found\" rows left out.\n    // Returns that column's cells from every such table, or null if no table has it.\n    const findColumn = (name) => {\n        let seen = false;\n        const cells = [];\n        for (const t of readTable(work)) {\n            const i = t.header.findIndex((h) => norm(h) === norm(name));\n            if (i < 0)\n                continue;\n            seen = true;\n            for (const r of t.rows) {\n                if (r.some((c) => norm(c) === 'none found'))\n                    continue;\n                cells.push(r[i] ?? '');\n            }\n        }\n        return seen ? cells : null;\n    };\n    const empty = (c) => !c || /^[-\\u2013\\u2014\\s]*$/.test(c);\n    switch (rule) {\n        case 'has_text':\n            return norm(work).includes(norm(value))\n                ? { pass: true, reason: 'Found.' }\n                : { pass: false, reason: `The answer does not include \"${value}\".` };\n        case 'lacks_text':\n            return norm(work).includes(norm(value))\n                ? { pass: false, reason: `The answer includes \"${value}\", which it should not.` }\n                : { pass: true, reason: 'Not found, as it should be.' };\n        case 'max_words': {\n            const n = String(work ?? '')\n                .split(/\\s+/)\n                .filter(Boolean).length;\n            return n <= Number(value)\n                ? { pass: true, reason: `${n} words.` }\n                : { pass: false, reason: `The answer has ${n} words; the limit is ${value}.` };\n        }\n        case 'table_cells_filled': {\n            const problems = [];\n            for (const col of value) {\n                const f = findColumn(col);\n                if (!f) {\n                    problems.push(`No table with a \"${col}\" column was found.`);\n                    continue;\n                }\n                f.forEach((c, i) => {\n                    if (empty(c))\n                        problems.push(`Row ${i + 1} has nothing in \"${col}\".`);\n                });\n            }\n            return problems.length\n                ? { pass: false, reason: problems.join(' ') }\n                : { pass: true, reason: 'Every row is filled in.' };\n        }\n        case 'column_values_in': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const allowed = value.allowed.map(norm);\n            const bad = [];\n            f.forEach((c, i) => {\n                if (!allowed.includes(norm(c)))\n                    bad.push(`Row ${i + 1} says \"${c}\".`);\n            });\n            return bad.length\n                ? {\n                    pass: false,\n                    reason: `The \"${value.column}\" column must say ${value.allowed.join(' or ')}. ${bad.join(' ')}`,\n                }\n                : { pass: true, reason: 'Every row uses an allowed word.' };\n        }\n        case 'column_quotes_source': {\n            const f = findColumn(value.column);\n            if (!f)\n                return { pass: false, reason: `No table with a \"${value.column}\" column was found.` };\n            const src = norm(sources[value.input] ?? '');\n            const bad = [];\n            f.forEach((c, i) => {\n                // A quote may be shortened with \"...\"; each piece must be in the source.\n                const pieces = c\n                    .split(/\\.\\.\\.|\\u2026/)\n                    .map(norm)\n                    .filter(Boolean);\n                if (!pieces.length || !pieces.every((p) => src.includes(p)))\n                    bad.push(`Row ${i + 1}: \"${c}\" is not in the source.`);\n            });\n            return bad.length\n                ? { pass: false, reason: bad.join(' ') }\n                : { pass: true, reason: 'Every quote was found in the source.' };\n        }\n        case 'numbers_in_source': {\n            const nums = (s) => (String(s ?? '').match(/\\d[\\d,]*(?:\\.\\d+)?/g) ?? []).map((n) => n.replace(/,/g, ''));\n            const src = new Set(nums(sources[value.input] ?? ''));\n            const missing = [...new Set(nums(work))].filter((n) => !src.has(n));\n            return missing.length\n                ? {\n                    pass: false,\n                    reason: `These numbers are not in the source: ${missing.join(', ')}.`,\n                }\n                : { pass: true, reason: 'Every number was found in the source.' };\n        }\n        default:\n            return { pass: false, reason: `Unknown rule \"${rule}\".` };\n    }\n};\n\nconst work = String($(\"2. List the actions\").first().json.text ?? '');\nconst sources = {\n  \"notes\": String($(\"Inputs\").first().json[\"notes\"] ?? ''),\n};\nconst result = runRule(\"column_quotes_source\", {\"column\":\"Source line\",\"input\":\"notes\"}, work, sources);\nreturn [{ json: result }];"
      },
      "id": "b67c69b1-9c70-4de0-a04c-19e7c29397b6",
      "name": "Check 4: Every action quotes its note",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2820,
        320
      ],
      "notes": "Every quote in the Source line column appears word for word in the meeting notes.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "43a55ae1-c6d0-46a2-af25-5fef283704d0",
              "leftValue": "={{ $json.pass === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f0acea14-effd-4ca9-a769-b1c27a83d827",
      "name": "Passed check 4?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3080,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "1a9f40c6-8156-4db5-aade-fbe4ccd23f13",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "58b2e3d6-b5d9-4af5-a6dd-a5a429a5d593",
      "name": "Try again? (check 4)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3080,
        520
      ],
      "notes": "True = first failure: send back to \"2. List the actions\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e89e9d6f-625a-4de4-aafd-e6f13b3d15ce",
              "name": "flag",
              "value": "=Every action quotes its note: did not pass. {{ $(\"Check 4: Every action quotes its note\").first().json.reason }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "77da79bd-2f06-4bba-a2d1-096331e3f1e0",
      "name": "Flag check 4",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3210,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "promptType": "define",
        "hasOutputParser": false,
        "text": "=You are a careful reviewer. You do not fix or rewrite the work; you only decide whether it passes one check.\n\nThe check: Nothing was added that is not in the notes\nCompare the three lists below with the original meeting notes. Does every decision, action, owner, date and question come from the notes, with nothing added or changed?\n\nDecisions:\n{{ $(\"1. Pull out decisions\").first().json.text }}\n\nActions:\n{{ $(\"2. List the actions\").first().json.text }}\n\nOpen questions:\n{{ $(\"3. Note open questions\").first().json.text }}\n\nOriginal meeting notes:\n{{ $(\"Inputs\").first().json[\"notes\"] }}\n\nThe work to check (the answer from Step 3 (Note open questions)):\n{{ $(\"3. Note open questions\").first().json.text }}\n\nReply with exactly PASS or FAIL on the first line. On the second line, say in one sentence why. If it fails, name the exact item or line that fails. Do not add anything else."
      },
      "id": "e028a65d-8a0c-4890-a71a-15ff3b6713b2",
      "name": "Check 5: Nothing was added that is not in the notes",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        3340,
        320
      ],
      "notes": "Answers PASS or FAIL. Compare the three lists below with the original meeting notes. Does every decision, action, owner, date and question come from the notes, with nothing added or changed?",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "44a55c74-c1d0-4ec3-ac25-5b362f370fd5",
              "leftValue": "={{ String($json.text ?? '').trim().toUpperCase().startsWith('PASS') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f0aaab7d-f000-4b40-a766-a9ab7a8616be",
      "name": "Passed check 5?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3600,
        320
      ],
      "notes": "True = passed. False = did not pass.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "1d9f457f-7a56-42b0-a9de-fa51d1d246f2",
              "leftValue": "={{ $runIndex }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "34b06c93-29dc-4028-a2db-93214da84cd6",
      "name": "Try again? (check 5)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3600,
        520
      ],
      "notes": "True = first failure: send back to \"3. Note open questions\" once. False = failed twice: flag it.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e59e98b6-615a-4c51-abfd-e8843e3d1a87",
              "name": "flag",
              "value": "=Nothing was added that is not in the notes: did not pass. {{ $(\"Check 5: Nothing was added that is not in the notes\").first().json.text }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "76da782a-3006-4d4d-a1d1-07d032e3f373",
      "name": "Flag check 5",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3730,
        700
      ],
      "notes": "Notes the problem and carries on. The note shows in Result.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "330909e9-9fcc-431c-ab02-fab31ef44b7e",
              "name": "result",
              "value": "={{ $(\"3. Note open questions\").first().json.text }}",
              "type": "string"
            },
            {
              "id": "1fc6a456-0686-43b5-aa52-e1f864861b0f",
              "name": "flagged",
              "value": "={{ [$(\"Flag check 1\").isExecuted ? $(\"Flag check 1\").first().json.flag : \"\", $(\"Flag check 2\").isExecuted ? $(\"Flag check 2\").first().json.flag : \"\", $(\"Flag check 3\").isExecuted ? $(\"Flag check 3\").first().json.flag : \"\", $(\"Flag check 4\").isExecuted ? $(\"Flag check 4\").first().json.flag : \"\", $(\"Flag check 5\").isExecuted ? $(\"Flag check 5\").first().json.flag : \"\"].filter(x => x).join(\"\\n\") || \"None\" }}",
              "type": "string"
            },
            {
              "id": "3c8d99c4-22ae-4b4b-a6d4-c6f6d9a616a5",
              "name": "check_by_hand",
              "value": "None",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "a9473bea-8473-4a51-a4b6-a99485f70e7b",
      "name": "Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3860,
        320
      ],
      "notes": "The final answer, anything flagged, and the checks left for you.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "f0422124-e986-4387-a4a9-e97607fecb21",
      "name": "Chat model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        480,
        840
      ],
      "notes": "Attach your credential here, or swap in any chat model node.",
      "notesInFlow": true
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Inputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Inputs": {
      "main": [
        [
          {
            "node": "1. Pull out decisions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Pull out decisions": {
      "main": [
        [
          {
            "node": "Check 1: Every item quotes its note",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 1: Every item quotes its note": {
      "main": [
        [
          {
            "node": "Passed check 1?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 1?": {
      "main": [
        [
          {
            "node": "2. List the actions",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 1)": {
      "main": [
        [
          {
            "node": "1. Pull out decisions",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 1": {
      "main": [
        [
          {
            "node": "2. List the actions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. List the actions": {
      "main": [
        [
          {
            "node": "Check 2: Every action has an owner and a date, or is flagged",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 2: Every action has an owner and a date, or is flagged": {
      "main": [
        [
          {
            "node": "Passed check 2?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 2?": {
      "main": [
        [
          {
            "node": "3. Note open questions",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 2)": {
      "main": [
        [
          {
            "node": "2. List the actions",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 2": {
      "main": [
        [
          {
            "node": "3. Note open questions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Note open questions": {
      "main": [
        [
          {
            "node": "Check 3: Every open question says who answers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 3: Every open question says who answers": {
      "main": [
        [
          {
            "node": "Passed check 3?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 3?": {
      "main": [
        [
          {
            "node": "Check 4: Every action quotes its note",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 3)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 3)": {
      "main": [
        [
          {
            "node": "3. Note open questions",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 3": {
      "main": [
        [
          {
            "node": "Check 4: Every action quotes its note",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 4: Every action quotes its note": {
      "main": [
        [
          {
            "node": "Passed check 4?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 4?": {
      "main": [
        [
          {
            "node": "Check 5: Nothing was added that is not in the notes",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 4)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 4)": {
      "main": [
        [
          {
            "node": "2. List the actions",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 4": {
      "main": [
        [
          {
            "node": "Check 5: Nothing was added that is not in the notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check 5: Nothing was added that is not in the notes": {
      "main": [
        [
          {
            "node": "Passed check 5?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Passed check 5?": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Try again? (check 5)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try again? (check 5)": {
      "main": [
        [
          {
            "node": "3. Note open questions",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Flag check 5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag check 5": {
      "main": [
        [
          {
            "node": "Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chat model": {
      "ai_languageModel": [
        [
          {
            "node": "1. Pull out decisions",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "2. List the actions",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "3. Note open questions",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Check 5: Nothing was added that is not in the notes",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "template": "meeting-notes-to-actions"
  }
}
