{
  "name": "[Subworkflow.ai] RAG agent with Jev re-ranking",
  "nodes": [
    {
      "parameters": {
        "options": {}
      },
      "id": "7fa43e65-6adc-4096-9de9-e2afb6a4d5c6",
      "name": "Chat",
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "typeVersion": 1.1,
      "position": [
        -688,
        -256
      ],
      "webhookId": "cf56bd53-4738-4930-b10d-8c1def5134b8"
    },
    {
      "parameters": {
        "model": "google/gemini-2.5-flash",
        "options": {
          "temperature": 0.2
        }
      },
      "id": "55b3c3b6-28be-41c6-b449-eb5fd606c8b6",
      "name": "Gemini 2.5 Flash",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "typeVersion": 1,
      "position": [
        -576,
        -32
      ],
      "credentials": {
        "openRouterApi": {
          "id": "O3r1xV6tLDukbaqy",
          "name": "n8n-jimleuk-20260918"
        }
      }
    },
    {
      "parameters": {
        "contextWindowLength": 10
      },
      "id": "e707b324-dceb-4321-bd3d-35c9bddff8e0",
      "name": "Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.3,
      "position": [
        -448,
        -32
      ]
    },
    {
      "parameters": {
        "description": "Search the staff handbook and return the passages that best answer a policy question. Pass the employee's question in full, in natural language - do not reduce it to keywords. Returns ranked passages with a relevance score; prefer higher-ranked ones and cite the source path.",
        "workflowId": {
          "__rl": true,
          "mode": "id",
          "value": "lkgvcwGTk7wucSo4"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "query": "={{ $fromAI('query', 'the user\\'s question, in full natural language', 'string') }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "query",
              "displayName": "query",
              "type": "string",
              "required": true,
              "canBeUsedToMatch": true
            }
          ]
        }
      },
      "id": "54fd192c-d4b4-4d78-9130-0967ac74b1c9",
      "name": "search_knowledge_base",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [
        -320,
        -32
      ]
    },
    {
      "parameters": {
        "workflowInputs": {
          "values": [
            {
              "name": "query"
            }
          ]
        }
      },
      "id": "5c33a95c-2693-4861-b4ab-14551fe3d393",
      "name": "When Called by Agent",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        -96,
        -256
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "d392ff37-ae01-4248-9b5c-5de456b18c6b",
      "name": "Embeddings",
      "type": "@n8n/n8n-nodes-langchain.embeddingsOpenAi",
      "typeVersion": 1.2,
      "position": [
        80,
        -32
      ],
      "credentials": {
        "openAiApi": {
          "id": "5aV1juGxeFi4UESv",
          "name": "OpenAi account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// One Jev request per retrieved chunk.\n//\n// The agent never sees this. Retrieval-plus-rerank is one capability, so the\n// rerank always runs, over everything retrieved, on the real passage text -\n// not on whatever an LLM would have retyped into a tool call.\nconst MODEL = '~typesafe/jev-latest';\nconst MAX_CHARS = 4000;\n\n// Pressing \"Test workflow\" runs this path with no query, because nothing has\n// called it. Fall back to a sample so the canvas demonstrates the rerank instead\n// of erroring - but never in production, where a missing query means the agent\n// failed to pass one and answering a different question would be worse.\nconst SAMPLE_QUERY = \"how much parental leave do I get if I've been here 18 months?\";\n\nconst trigger = $('When Called by Agent').first().json;\nlet query = String(trigger.query ?? trigger.chatInput ?? '').trim();\nlet usedSampleQuery = false;\n\nif (!query) {\n  if ($execution.mode === 'production') {\n    throw new Error('`query` is empty - the tool node should populate it from the agent');\n  }\n  query = SAMPLE_QUERY;\n  usedSampleQuery = true;\n}\n\nconst docs = $input.all().map((i) => i.json);\nif (docs.length === 0) throw new Error('Qdrant returned no documents');\n\nconst out = docs.map((d, idx) => {\n  const text = String(d.pageContent ?? d.text ?? d.document?.pageContent ?? '').trim();\n  const meta = d.metadata ?? d.document?.metadata ?? {};\n  return {\n    json: {\n      query,\n      usedSampleQuery,\n      startedAt: trigger.startedAt ?? Date.now(),\n      chunk: {\n        index: idx,\n        vectorScore: typeof d.score === 'number' ? d.score : null,\n        vectorRank: idx + 1,\n        text: text.slice(0, MAX_CHARS),\n        source: meta.source ?? meta.url ?? null,\n        title: meta.title ?? null,\n      },\n      body_request: {\n        model: MODEL,\n        state: {\n          question: query,\n          passage: text.slice(0, MAX_CHARS),\n          passage_title: meta.title ?? '',\n          passage_source: meta.source ?? '',\n        },\n        questions: {\n          answers_question: {\n            type: 'score',\n            instructions: 'How well does `passage` answer `question`?',\n            criteria: [\n              'unrelated to the question',\n              'same topic, but does not address the question',\n              'partially addresses the question',\n              'answers the question',\n              'answers the question directly and completely',\n            ],\n          },\n          is_self_contained: {\n            type: 'noul',\n            instructions: 'Can this passage be understood on its own, without the surrounding document? A fragment that begins mid-explanation or refers to \"the above\" is not self-contained.',\n          },\n          is_specific: {\n            type: 'noul',\n            instructions: 'Does this passage give concrete specifics - figures, durations, thresholds, conditions, steps - rather than only general statements of intent?',\n          },\n          is_stale: {\n            type: 'noul',\n            instructions: 'Has this passage been superseded? Answer yes if it describes a version, policy or rule that a later one has replaced, even if the passage is otherwise accurate and well written.',\n          },\n        },\n        session_id: String(trigger.requestId ?? $execution.id).slice(0, 256),\n      },\n    },\n  };\n});\n\nreturn out;\n"
      },
      "id": "0cfb4592-c2b8-4d03-be31-ff7a2db38bc0",
      "name": "Build Rerank Requests",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        448,
        -256
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/alpha/decisions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openRouterApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.body_request) }}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 10,
              "batchInterval": 0
            }
          },
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "673e0ff7-639a-4bf3-8bd7-9520e8627aff",
      "name": "Rerank",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        640,
        -256
      ],
      "credentials": {
        "openRouterApi": {
          "id": "O3r1xV6tLDukbaqy",
          "name": "n8n-jimleuk-20260918"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// --- shared decision core (inlined into each Code node at build time) -------\n// Mirrors TypeSafe's published answer contract:\n//   Choice -> { choice, probabilities, confidence }\n//   Score  -> { score (continuous, probability-weighted), probabilities, confidence }\n//   Noul   -> { noul } : probability of yes, and NO confidence value\n// See https://docs.typesafe.ai/primitives.md\nfunction tsDecision(q, probs, extra) {\n  const probabilities = {};\n  q.candidates.forEach((c, j) => { probabilities[c] = Number(probs[j].toFixed(6)); });\n\n  let bestIdx = 0;\n  for (let j = 1; j < probs.length; j++) if (probs[j] > probs[bestIdx]) bestIdx = j;\n\n  if (q.type === 'noul') {\n    const yesIdx = q.candidates.indexOf('yes');\n    const p = probs[yesIdx >= 0 ? yesIdx : probs.length - 1];\n    // Jev returns no confidence for a Noul: the probability IS the answer.\n    // A value near 0.5 means yes and no are similarly likely - NOT medium intensity.\n    return Object.assign({\n      type: 'noul',\n      noul: Number(p.toFixed(6)),\n      value: p >= 0.5, // convenience for code that needs a hard boolean\n      probabilities,\n    }, extra || {});\n  }\n\n  if (q.type === 'score') {\n    // Jev's `score` is the probability-weighted position on the ordered levels,\n    // not the argmax index - their own docs threshold on `score > 1.5`.\n    return Object.assign({\n      type: 'score',\n      score: Number(probs.reduce((acc, p, j) => acc + p * j, 0).toFixed(6)),\n      level: q.candidates[bestIdx],\n      levelIndex: bestIdx,\n      probabilities,\n      confidence: Number(probs[bestIdx].toFixed(6)),\n    }, extra || {});\n  }\n\n  return Object.assign({\n    type: 'choice',\n    choice: q.candidates[bestIdx],\n    probabilities,\n    confidence: Number(probs[bestIdx].toFixed(6)),\n  }, extra || {});\n}\n\n// Vote counting -> renormalised probabilities over declared candidates only.\nfunction tsProbs(q, tokens) {\n  const counts = {};\n  for (const k of q.keys) counts[k] = 0;\n  for (const t of tokens) {\n    const tok = String(t ?? '').trim().toUpperCase().charAt(0);\n    if (Object.prototype.hasOwnProperty.call(counts, tok)) counts[tok] += 1;\n  }\n  const valid = Object.values(counts).reduce((a, b) => a + b, 0);\n  return {\n    valid,\n    degenerate: valid === 0,\n    probs: valid === 0 ? q.keys.map(() => 1 / q.keys.length) : q.keys.map((k) => counts[k] / valid),\n  };\n}\n\n// Aggregate. Jev excludes Noul from confidence, so ambiguous Nouls are tracked\n// on their own axis rather than folded into minConfidence.\nfunction tsSummary(decisions) {\n  let minConfidence = 1;\n  let maxNoulAmbiguity = 0;\n  for (const d of decisions) {\n    if (d.type === 'noul') {\n      const amb = 1 - Math.abs(2 * d.noul - 1); // 1 at p=0.5, 0 at p=0 or 1\n      if (amb > maxNoulAmbiguity) maxNoulAmbiguity = amb;\n    } else if (d.confidence < minConfidence) {\n      minConfidence = d.confidence;\n    }\n  }\n  return {\n    minConfidence: Number(minConfidence.toFixed(6)),\n    maxNoulAmbiguity: Number(maxNoulAmbiguity.toFixed(6)),\n  };\n}\n\n// Rank by judgement, return what the agent should read.\n//\n// Vector similarity finds passages that look like the question. It cannot tell\n// which one answers it. That distinction is the whole point of this step, and\n// `movedUp` in the output makes it visible.\nconst MIN_ANSWER = 1.5;       // below this it does not address the question\nconst MIN_SELF_CONTAINED = 0.35;\nconst MAX_STALE = 0.6;\nconst TOP_N = 5;\n\nconst W_ANSWER = 0.7;\nconst W_SPECIFIC = 0.2;\nconst W_SELF = 0.1;\n\nconst prompts = $('Build Rerank Requests').all();\nconst responses = $input.all();\n\nconst scored = [];\n\nfor (let i = 0; i < responses.length; i++) {\n  const meta = prompts[i].json;\n  const res = responses[i].json;\n  if (!res || typeof res.answers !== 'object') {\n    const msg = res?.error?.message ?? JSON.stringify(res)?.slice(0, 200);\n    throw new Error(`Rerank returned no answers for chunk ${meta.chunk?.index}: ${msg}`);\n  }\n  const a = res.answers;\n  const p = (k) => (typeof a[k]?.noul === 'number' ? a[k].noul : null);\n\n  const answer = typeof a.answers_question?.score === 'number' ? a.answers_question.score : 0;\n  const selfContained = p('is_self_contained') ?? 0;\n  const specific = p('is_specific') ?? 0;\n  const stale = p('is_stale') ?? 0;\n\n  const drops = [];\n  if (answer < MIN_ANSWER) drops.push('does not address the question');\n  if (selfContained < MIN_SELF_CONTAINED) drops.push('fragment, not self-contained');\n  if (stale > MAX_STALE) drops.push('describes superseded behaviour');\n\n  scored.push({\n    ...meta.chunk,\n    answersQuestion: Number(answer.toFixed(4)),\n    answerConfidence: a.answers_question?.confidence ?? null,\n    isSelfContained: selfContained,\n    isSpecific: specific,\n    isStale: stale,\n    score: Number((W_ANSWER * (answer / 4) + W_SPECIFIC * specific + W_SELF * selfContained).toFixed(4)),\n    kept: drops.length === 0,\n    droppedBecause: drops.length ? drops.join('; ') : null,\n  });\n}\n\nconst kept = scored\n  .filter((c) => c.kept)\n  .sort((x, y) => y.score - x.score)\n  .slice(0, TOP_N)\n  .map((c, i) => ({ ...c, rank: i + 1, movedUp: c.vectorRank - (i + 1) }));\n\n// The agent reads `passages`. Everything else is here for inspection.\nreturn [\n  {\n    json: {\n      query: prompts[0]?.json?.query ?? null,\n      // true when nothing supplied a query and the sample was used instead\n      usedSampleQuery: Boolean(prompts[0]?.json?.usedSampleQuery),\n      passages: kept.map((c) => ({\n        rank: c.rank,\n        text: c.text,\n        title: c.title,\n        source: c.source,\n        relevance: c.answersQuestion,\n      })),\n      retrieved: scored.length,\n      returned: kept.length,\n      dropped: scored.filter((c) => !c.kept).map((c) => ({ vectorRank: c.vectorRank, title: c.title, why: c.droppedBecause })),\n      // Positive means the rerank promoted it above where the vector search put it.\n      biggestClimb: kept.length ? Math.max(...kept.map((c) => c.movedUp)) : 0,\n      topWasVectorRank: kept[0]?.vectorRank ?? null,\n      rerankCalls: responses.length,\n      elapsedMs: Date.now() - (prompts[0]?.json?.startedAt ?? Date.now()),\n      detail: kept,\n    },\n  },\n];\n"
      },
      "id": "c95aee5f-2ac3-498c-a5a9-16d6cf2f5369",
      "name": "Rank and Return",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        -256
      ]
    },
    {
      "parameters": {},
      "id": "0aed35d1-c705-472d-9116-6bc24cc16a54",
      "name": "Seed Collection (run once)",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        16,
        272
      ],
      "disabled": true
    },
    {
      "parameters": {
        "jsCode": "// The same passages the retrieval step is pinned with, so a real\n// collection reproduces the demo exactly.\nconst docs = [\n  {\n    \"pageContent\": \"Our commitment to families. We believe people do their best work when life outside work is supported. Every policy in this handbook is written with that in mind, and managers are expected to apply them with compassion and common sense.\",\n    \"metadata\": {\n      \"title\": \"Our commitment to families\",\n      \"source\": \"handbook/intro/values\"\n    },\n    \"score\": 0.92\n  },\n  {\n    \"pageContent\": \"Parental leave (policy v2 — superseded 1 January 2026). Employees with at least 24 months' continuous service are entitled to 12 weeks of parental leave at full pay. Employees below that threshold receive statutory pay only. This policy was replaced by v3; see the current parental leave page.\",\n    \"metadata\": {\n      \"title\": \"Parental leave (v2, superseded)\",\n      \"source\": \"handbook/archive/parental-leave-v2\"\n    },\n    \"score\": 0.91\n  },\n  {\n    \"pageContent\": \"…and as set out above, that entitlement is then pro-rated against the employee's contracted hours for the relevant period. The preceding paragraph explains how the qualifying period is calculated.\",\n    \"metadata\": {\n      \"title\": \"Pro-rating (continued)\",\n      \"source\": \"handbook/leave/pro-rating#p3\"\n    },\n    \"score\": 0.9\n  },\n  {\n    \"pageContent\": \"Annual leave. All employees receive 28 days of paid annual leave per year, inclusive of public holidays, accruing monthly from the start date. Unused days may be carried over up to a maximum of 5.\",\n    \"metadata\": {\n      \"title\": \"Annual leave\",\n      \"source\": \"handbook/leave/annual\"\n    },\n    \"score\": 0.88\n  },\n  {\n    \"pageContent\": \"Parental leave (current policy, effective 1 January 2026). Employees with at least 12 months' continuous service at the expected week of birth or placement are entitled to 18 weeks of parental leave at full pay, followed by up to 21 weeks at statutory pay. Service is measured from the start date, not the probation end date. Employees with less than 12 months' service receive statutory pay from day one.\",\n    \"metadata\": {\n      \"title\": \"Parental leave (current)\",\n      \"source\": \"handbook/leave/parental\"\n    },\n    \"score\": 0.86\n  },\n  {\n    \"pageContent\": \"Requesting parental leave. Give at least 15 weeks' notice before the expected week of birth using the leave request form in Workday. Your manager confirms eligibility and People Ops issues written confirmation within 10 working days. Leave can be taken in up to three separate blocks.\",\n    \"metadata\": {\n      \"title\": \"Requesting parental leave\",\n      \"source\": \"handbook/leave/parental-requests\"\n    },\n    \"score\": 0.84\n  },\n  {\n    \"pageContent\": \"Sickness absence. Notify your manager before 10:00 on the first day of absence. A self-certification form covers up to 7 calendar days; beyond that a fit note is required.\",\n    \"metadata\": {\n      \"title\": \"Sickness absence\",\n      \"source\": \"handbook/leave/sickness\"\n    },\n    \"score\": 0.79\n  },\n  {\n    \"pageContent\": \"Pension. We contribute 6% of pensionable pay when you contribute 3% or more. Contributions begin after the first full month of employment and are managed through the provider portal.\",\n    \"metadata\": {\n      \"title\": \"Pension contributions\",\n      \"source\": \"handbook/benefits/pension\"\n    },\n    \"score\": 0.76\n  }\n];\nreturn docs.map((d) => ({ json: { text: d.pageContent, ...d.metadata } }));"
      },
      "id": "920c5639-bd60-450b-8f3c-25e6f891bd5a",
      "name": "Sample Documents",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        208,
        272
      ],
      "disabled": true
    },
    {
      "parameters": {
        "mode": "insert",
        "qdrantCollection": {
          "__rl": true,
          "mode": "id",
          "value": "employee-handbook"
        },
        "options": {}
      },
      "id": "ceb4ff3d-8618-41ac-bbd2-e1681ef6fe2b",
      "name": "Qdrant: Insert",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant",
      "typeVersion": 1.3,
      "position": [
        384,
        272
      ],
      "disabled": true
    },
    {
      "parameters": {
        "content": "# Staff handbook RAG agent with Jev re-ranking\n\nThis template showcases how to implement a Jev-based reranker for vector store results to improve RAG agent response.\n\n## Why Agent Subworkflow Tool?\nWe found that having Jev as a tool, the agent was retyping every retrieved passage into a tool call which would have meant a hefty token tax. Also the LLM tended to paraphrase the results at random which affected the rerank results.\n\n## What the rerank adds\nVector similarity finds passages that *look like* the question. It cannot tell which one *answers* it. Here's a quick breakdown of the classification used:\n| Question | Catches |\n| --- | --- |\n| answers_question | values statements that are on-topic and say nothing |\n| is_self_contained | fragments starting \"as set out above\" |\n| is_specific | intent without figures, thresholds or conditions |\n| is_stale | a superseded policy version |",
        "height": 672,
        "width": 512
      },
      "id": "a95b484b-dc53-4fea-b979-76c17be816ed",
      "name": "How this works",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1328,
        -464
      ]
    },
    {
      "parameters": {
        "mode": "load",
        "qdrantCollection": {
          "__rl": true,
          "mode": "id",
          "value": "employee-handbook"
        },
        "prompt": "={{ $json.query }}",
        "topK": 20,
        "options": {}
      },
      "id": "7b726c3c-a0d3-43f7-80bc-fef132d51501",
      "name": "Employee Handbook",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant",
      "typeVersion": 1.3,
      "position": [
        80,
        -256
      ],
      "credentials": {
        "qdrantApi": {
          "id": "4UAzN0zxCMMc76rG",
          "name": "clients-dev"
        }
      }
    },
    {
      "parameters": {
        "options": {
          "systemMessage": "You answer employee questions about company policy using the staff handbook.\n\nAlways call search_knowledge_base before answering. Pass the question in full -\nthe search understands natural language and works worse on keywords.\n\nAnswer only from the passages returned, and cite the `source` of each one you\nuse. Quote figures and thresholds exactly as written; do not round or restate\nthem loosely.\n\nIf the passages do not settle the question - particularly anything touching\neligibility, pay or notice periods - say so and point the employee at People Ops\nrather than filling the gap yourself. A confident wrong answer about leave or pay\nis worse than no answer.\n\nThe passages come back ranked by how well they answer the question, not by\nkeyword similarity. Trust the order.",
          "maxIterations": 5,
          "returnIntermediateSteps": true
        }
      },
      "id": "a36504b2-834e-4d77-800e-73033199d7bc",
      "name": "Employee Assistant",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3,
      "position": [
        -512,
        -256
      ]
    },
    {
      "parameters": {
        "content": "## Use this to generate Vector Store data \nDisabled on purpose and totally optional. Run it if you want to fully test out the example above.",
        "height": 336,
        "width": 832,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -112,
        160
      ],
      "id": "40754e5d-f0c3-42e9-9cd4-bd2409783f33",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## Jev as a Reranker",
        "height": 272,
        "width": 432,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        368,
        -336
      ],
      "id": "8ffeb85a-7874-46cb-9649-ae0d4eb8535e",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## ⚠️ OpenRouter Requirement\nWe're using Jev via OpenRouter so you'll need an OpenRouter key and credits. Alternatively, swap this out for another Jev provider.",
        "height": 144,
        "width": 432,
        "color": 6
      },
      "id": "886002e7-62b6-47b4-b01f-87c60c695b2e",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        368,
        -512
      ]
    },
    {
      "parameters": {
        "content": "[![](https://cdn.subworkflow.ai/marketing/banner-300x100.png?v=20260918)](https://subworkflow.ai)",
        "height": 128,
        "width": 336,
        "color": 7
      },
      "id": "2628dbfa-c38a-4bfd-bee0-3179e1d2c88f",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1344,
        224
      ]
    }
  ],
  "pinData": {
    "Employee Handbook": [
      {
        "json": {
          "pageContent": "Our commitment to families. We believe people do their best work when life outside work is supported. Every policy in this handbook is written with that in mind, and managers are expected to apply them with compassion and common sense.",
          "metadata": {
            "title": "Our commitment to families",
            "source": "handbook/intro/values"
          },
          "score": 0.92
        }
      },
      {
        "json": {
          "pageContent": "Parental leave (policy v2 — superseded 1 January 2026). Employees with at least 24 months' continuous service are entitled to 12 weeks of parental leave at full pay. Employees below that threshold receive statutory pay only. This policy was replaced by v3; see the current parental leave page.",
          "metadata": {
            "title": "Parental leave (v2, superseded)",
            "source": "handbook/archive/parental-leave-v2"
          },
          "score": 0.91
        }
      },
      {
        "json": {
          "pageContent": "…and as set out above, that entitlement is then pro-rated against the employee's contracted hours for the relevant period. The preceding paragraph explains how the qualifying period is calculated.",
          "metadata": {
            "title": "Pro-rating (continued)",
            "source": "handbook/leave/pro-rating#p3"
          },
          "score": 0.9
        }
      },
      {
        "json": {
          "pageContent": "Annual leave. All employees receive 28 days of paid annual leave per year, inclusive of public holidays, accruing monthly from the start date. Unused days may be carried over up to a maximum of 5.",
          "metadata": {
            "title": "Annual leave",
            "source": "handbook/leave/annual"
          },
          "score": 0.88
        }
      },
      {
        "json": {
          "pageContent": "Parental leave (current policy, effective 1 January 2026). Employees with at least 12 months' continuous service at the expected week of birth or placement are entitled to 18 weeks of parental leave at full pay, followed by up to 21 weeks at statutory pay. Service is measured from the start date, not the probation end date. Employees with less than 12 months' service receive statutory pay from day one.",
          "metadata": {
            "title": "Parental leave (current)",
            "source": "handbook/leave/parental"
          },
          "score": 0.86
        }
      },
      {
        "json": {
          "pageContent": "Requesting parental leave. Give at least 15 weeks' notice before the expected week of birth using the leave request form in Workday. Your manager confirms eligibility and People Ops issues written confirmation within 10 working days. Leave can be taken in up to three separate blocks.",
          "metadata": {
            "title": "Requesting parental leave",
            "source": "handbook/leave/parental-requests"
          },
          "score": 0.84
        }
      },
      {
        "json": {
          "pageContent": "Sickness absence. Notify your manager before 10:00 on the first day of absence. A self-certification form covers up to 7 calendar days; beyond that a fit note is required.",
          "metadata": {
            "title": "Sickness absence",
            "source": "handbook/leave/sickness"
          },
          "score": 0.79
        }
      },
      {
        "json": {
          "pageContent": "Pension. We contribute 6% of pensionable pay when you contribute 3% or more. Contributions begin after the first full month of employment and are managed through the provider portal.",
          "metadata": {
            "title": "Pension contributions",
            "source": "handbook/benefits/pension"
          },
          "score": 0.76
        }
      }
    ]
  },
  "connections": {
    "Chat": {
      "main": [
        [
          {
            "node": "Employee Assistant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini 2.5 Flash": {
      "ai_languageModel": [
        [
          {
            "node": "Employee Assistant",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Memory": {
      "ai_memory": [
        [
          {
            "node": "Employee Assistant",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "search_knowledge_base": {
      "ai_tool": [
        [
          {
            "node": "Employee Assistant",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "When Called by Agent": {
      "main": [
        [
          {
            "node": "Employee Handbook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings": {
      "ai_embedding": [
        [
          {
            "node": "Employee Handbook",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Build Rerank Requests": {
      "main": [
        [
          {
            "node": "Rerank",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rerank": {
      "main": [
        [
          {
            "node": "Rank and Return",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Seed Collection (run once)": {
      "main": [
        [
          {
            "node": "Sample Documents",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample Documents": {
      "main": [
        [
          {
            "node": "Qdrant: Insert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Employee Handbook": {
      "main": [
        [
          {
            "node": "Build Rerank Requests",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "492bea61-5b7d-4e1d-b856-c7bb812a756a",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "b9f144fdc910a1e14e522063b576e7e28af8b611858295f590957fc8454b2836"
  },
  "nodeGroups": [],
  "id": "lkgvcwGTk7wucSo4",
  "tags": []
}