{
  "name": "Context-Managed AI Agent – tool loadout, sub-agent quarantine and offloaded notes",
  "nodes": [
    {
      "id": "d37fff23-b4ce-4e00-8579-ba2505c8cae6",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        -240
      ],
      "parameters": {
        "content": "## Context-managed agent\nThree of Drew Breunig's context fixes, built from plain n8n nodes around one AI Agent (DeepSeek `deepseek-flash`):\n1. **Tool loadout**: a cheap call picks the tools; only those reach the agent.\n2. **Context quarantine**: a sub-agent with a blank history, used as a tool.\n3. **Context offloading**: notes in a Data Table.\n\nSource: [The State of Context Engineering](https://www.dbreunig.com/2026/10/24/the-state-of-context-engineering.html)\n\n### Setup\n1. Select your **DeepSeek** credential in the 3 DeepSeek nodes.\n2. Click *Run sample* once: **Create notes table** makes the Data Table `ctx_agent_notes` (string columns `session`, `note`).\n3. **Publish.** `use_tool` calls this same workflow, and n8n runs the *published* copy of a sub-workflow. Re-publish after every change to the bottom half.\n4. Open the chat, or click *Run sample*.\n\n### Cost\nAbout 3-6 small `deepseek-flash` calls per message (well under $0.01). The agent models run with DeepSeek's default thinking on.",
        "width": 460,
        "height": 640,
        "color": 7
      }
    },
    {
      "id": "65042713-e1de-45ec-924a-99b6fa0e6a26",
      "name": "Chat",
      "type": "@n8n/n8n-nodes-langchain.chatTrigger",
      "typeVersion": 1.5,
      "position": [
        0,
        0
      ],
      "parameters": {
        "public": false,
        "options": {}
      },
      "webhookId": "60232a55-e5aa-4834-bb1c-9eb0955d42c0"
    },
    {
      "id": "e90aa56e-19a6-46fa-a357-bcec0b7054ac",
      "name": "Run sample",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        180
      ],
      "parameters": {}
    },
    {
      "id": "773e4bc9-70ce-408f-be5b-72667ac0fffa",
      "name": "Sample request",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        180
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "Sample request-0",
              "name": "chatInput",
              "value": "Ask a researcher who designed the Triple Bridge in Ljubljana and when its two side bridges were added.",
              "type": "string"
            },
            {
              "id": "Sample request-1",
              "name": "sessionId",
              "value": "sample-session",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "a8a35492-690f-452c-b01f-e54dabb5fbd2",
      "name": "Create notes table",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        220,
        340
      ],
      "parameters": {
        "resource": "table",
        "operation": "create",
        "tableName": "ctx_agent_notes",
        "columns": {
          "column": [
            {
              "name": "session",
              "type": "string"
            },
            {
              "name": "note",
              "type": "string"
            }
          ]
        },
        "options": {
          "createIfNotExists": true
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "2de94efd-22e2-4809-be80-ff9b87ae23b1",
      "name": "Request",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        440,
        80
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "Request-0",
              "name": "chatInput",
              "value": "={{ $json.chatInput }}",
              "type": "string"
            },
            {
              "id": "Request-1",
              "name": "sessionId",
              "value": "={{ $json.sessionId }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "8ea9f41a-6488-44f5-b7d8-d8c09439b121",
      "name": "Loadout note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        640,
        -240
      ],
      "parameters": {
        "content": "## 1 · Tool loadout\nEight tools exist; the agent sees only the few this request needs. One cheap `deepseek-flash` call (thinking off, JSON mode) scores all eight; **Loadout** keeps score >= 7 (max 4) and puts only their definitions in the system prompt. If the selector fails, all tools load and the reply says so. Limit: the selector sees only the current message, not the chat history.\nBreunig: *\"How do we do tool loadout? It's just a function.\"* ([post](https://www.dbreunig.com/2026/10/24/the-state-of-context-engineering.html))",
        "width": 700,
        "height": 480,
        "color": 4
      }
    },
    {
      "id": "ebd67c72-d8ac-42b0-b807-017f8d078d90",
      "name": "Tool catalogue",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        700,
        80
      ],
      "parameters": {
        "jsCode": "// The full tool catalogue. `summary` is all the cheap selector sees;\n// `usage` is the full definition that only selected tools put into the agent's context.\nconst TOOLS = [\n  { name: 'calculator', summary: 'Evaluate an arithmetic expression.',\n    usage: 'calculator: evaluates arithmetic. input = an expression with numbers and + - * / ^ ( ) %, e.g. \"(17/100)*2340\". Use it for every non-trivial calculation instead of mental math.' },\n  { name: 'date_time', summary: 'Current date and time, or days until/since a date.',\n    usage: 'date_time: returns the current date and time. input = optional IANA time zone (e.g. \"Europe/Ljubljana\") and/or a date \"YYYY-MM-DD\" to get the number of days from today until that date.' },\n  { name: 'unit_convert', summary: 'Convert lengths, weights and temperatures between units.',\n    usage: 'unit_convert: converts units. input = \"<number> <from unit> to <to unit>\", e.g. \"12 km to mi\". Supports m, km, cm, mm, mi, ft, in, kg, g, lb, oz, C, F, K.' },\n  { name: 'text_stats', summary: 'Count words, characters and sentences in a text.',\n    usage: 'text_stats: counts words, characters and sentences. input = the text to measure.' },\n  { name: 'save_note', summary: 'Save a note to long-term storage outside the conversation.',\n    usage: 'save_note: stores one note in an external notes table so it survives beyond this conversation. input = the note text, self-contained (include dates and names).' },\n  { name: 'read_notes', summary: 'Read the notes saved earlier in this chat.',\n    usage: 'read_notes: returns the 20 most recent notes saved in this chat (oldest first), including ones that have dropped out of the chat history. input = \"all\". Never call it in the same step as save_note; wait for the save to finish.' },\n  { name: 'wikipedia', summary: 'Get the short Wikipedia summary of one article.',\n    usage: 'wikipedia: returns the lead summary of one English Wikipedia article. input = the exact article title, e.g. \"Ljubljana Castle\".' },\n  { name: 'research', summary: 'Delegate a multi-step research question to a sub-agent.',\n    usage: 'research: hands a research question to a sub-agent that starts with an empty context, searches Wikipedia on its own and returns only a short report (max ~80 words). input = one self-contained question with all needed details, because the sub-agent cannot see this conversation.' },\n];\nconst req = $input.first().json;\nconst system = 'You choose tools for an AI assistant. The assistant already knows today\\'s date and has general knowledge. For EACH tool, score 0-10 how likely the assistant needs it to fully answer the user request (10 = certainly needed, 0 = useless). Prefer the smallest set: when two tools overlap, score only the better one high. Return only JSON: {\"scores\":{\"<tool name>\":<score>, ...}}';\nconst user = 'User request:\\n' + req.chatInput + '\\n\\nTools:\\n' + TOOLS.map(t => `- ${t.name}: ${t.summary}`).join('\\n');\nreturn [{ json: {\n  chatInput: req.chatInput, sessionId: req.sessionId, catalogue: TOOLS,\n  body: {\n    model: 'deepseek-flash', thinking: { type: 'disabled' }, temperature: 0, max_tokens: 200,\n    response_format: { type: 'json_object' },\n    messages: [{ role: 'system', content: system }, { role: 'user', content: user }],\n  },\n} }];\n"
      }
    },
    {
      "id": "84d518fb-436a-49c1-af3d-29e2b00ca635",
      "name": "Pick tools",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        920,
        80
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.deepseek.com/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "deepSeekApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "options": {
          "timeout": 30000
        }
      },
      "credentials": {
        "deepSeekApi": {
          "name": "DeepSeek account"
        }
      },
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 1000,
      "onError": "continueRegularOutput"
    },
    {
      "id": "4f9300da-8631-4996-9358-b6adf16be444",
      "name": "Loadout",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1140,
        80
      ],
      "parameters": {
        "jsCode": "// Keep the top-scoring tools; only their definitions go into the agent's system prompt.\nconst THRESHOLD = 7;   // minimum score (0-10)\nconst MAX_TOOLS = 4;   // hard cap on the loadout\nconst prev = $('Tool catalogue').first().json;\nconst res = $input.first().json;\nlet scores = null;\ntry { scores = JSON.parse(res.choices[0].message.content).scores; } catch (e) { scores = null; }\n// Selector failed (HTTP error, timeout, bad JSON)? Load every tool rather than none, and say so in the reply.\nconst selectorFailed = !scores || typeof scores !== 'object';\nconst ranked = prev.catalogue\n  .map(t => ({ name: t.name, score: selectorFailed ? null : Number(scores[t.name] ?? 0) }))\n  .sort((a, b) => b.score - a.score);\nconst selected = selectorFailed ? prev.catalogue.map(t => t.name)\n  : ranked.filter(r => r.score >= THRESHOLD).slice(0, MAX_TOOLS).map(r => r.name);\nconst notLoaded = prev.catalogue.map(t => t.name).filter(n => !selected.includes(n));\nconst defs = prev.catalogue.filter(t => selected.includes(t.name)).map(t => '- ' + t.usage);\nconst systemPrompt = [\n  'You are a concise, helpful assistant. Today is ' + $now.toFormat('yyyy-LL-dd') + '.',\n  defs.length\n    ? 'Tools loaded for this request. Call them through use_tool with tool_name = <tool name> and input = <text>:\\n' + defs.join('\\n')\n    : 'No tools are loaded for this request. Answer directly.',\n  'Use only the tools listed above. Keep answers short.',\n].join('\\n\\n');\nreturn [{ json: {\n  chatInput: prev.chatInput, sessionId: prev.sessionId,\n  selected, notLoaded, scores: ranked, systemPrompt, selectorFailed,\n  selectorTokens: res.usage ? res.usage.total_tokens : null,\n} }];\n"
      }
    },
    {
      "id": "4386cd33-4c6f-4bac-8e55-05a5bcf5c21e",
      "name": "Agent note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1380,
        -240
      ],
      "parameters": {
        "content": "## The agent\nIts only tool, **use_tool**, has a fixed, generic description and runs this workflow's bottom half, so unselected tools never enter its context. Check in Executions → *DeepSeek* → input: only the loaded tools appear.\n**Memory** keeps the last 6 turns; pruning and summarisation are separate templates.",
        "width": 640,
        "height": 680,
        "color": 4
      }
    },
    {
      "id": "173dbe6d-5d63-4546-ad9b-922939a7beb9",
      "name": "Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        1460,
        80
      ],
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.chatInput }}",
        "options": {
          "systemMessage": "={{ $json.systemPrompt }}",
          "maxIterations": 8
        }
      }
    },
    {
      "id": "5892d85d-c708-414d-9a18-b6aadb0a8ba5",
      "name": "DeepSeek",
      "type": "@n8n/n8n-nodes-langchain.lmChatDeepSeek",
      "typeVersion": 1,
      "position": [
        1420,
        320
      ],
      "parameters": {
        "model": "deepseek-flash",
        "options": {
          "maxTokens": 2000,
          "temperature": 0.2
        }
      },
      "credentials": {
        "deepSeekApi": {
          "name": "DeepSeek account"
        }
      }
    },
    {
      "id": "cdad88f2-f7b6-40b9-8352-c5bcff2c0f48",
      "name": "Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.4,
      "position": [
        1560,
        320
      ],
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $json.sessionId }}",
        "contextWindowLength": 6
      }
    },
    {
      "id": "3731ad4a-6c84-427c-9de2-1d73d63ca34b",
      "name": "use_tool",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [
        1700,
        320
      ],
      "parameters": {
        "description": "Run one of the tools listed in the system prompt. Arguments: tool_name = the tool name, input = the plain-text input for that tool. Tools that are not listed are not available.",
        "source": "database",
        "workflowId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $workflow.id }}"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "tool_name": "={{ $fromAI('tool_name', 'Name of one tool listed in the system prompt', 'string') }}",
            "input": "={{ $fromAI('input', 'Plain-text input for that tool', 'string') }}",
            "allowed": "={{ $('Loadout').item.json.selected.join(',') }}",
            "session": "={{ $('Loadout').item.json.sessionId }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "tool_name",
              "displayName": "tool_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "input",
              "displayName": "input",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "allowed",
              "displayName": "allowed",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string",
              "removed": false
            },
            {
              "id": "session",
              "displayName": "session",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string",
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        }
      }
    },
    {
      "id": "53a89865-f04f-4186-9da1-bb41b831cae1",
      "name": "Reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1880,
        80
      ],
      "parameters": {
        "jsCode": "// Final answer + which tools were loaded.\nconst ans = $input.first().json;\nconst l = $('Loadout').first().json;\n// Sanity check of the generated system prompt only. The real evidence of what the model saw\n// is the DeepSeek sub-node's input in Executions (system prompt, history, tool calls).\nconst unselectedInSystemPrompt = l.notLoaded.filter(n => l.systemPrompt.includes(n));\nconst total = l.selected.length + l.notLoaded.length;\nconst footer = l.selectorFailed\n  ? `\\n\\n_Tool selector failed, so all ${total} tools were loaded._`\n  : `\\n\\n_Loaded ${l.selected.length}/${total} tools: ${l.selected.join(', ') || 'none'}_`;\nconst answer = ans.output || 'Sorry, the agent returned no answer.';\nreturn [{ json: {\n  output: answer + footer,\n  answer,\n  loaded: l.selected,\n  notLoaded: l.notLoaded,\n  selectorFailed: l.selectorFailed,\n  unselectedInSystemPrompt,\n  systemPromptChars: l.systemPrompt.length,\n} }];\n"
      }
    },
    {
      "id": "97eb8ed5-e087-421a-8e6e-9fe7d8574da6",
      "name": "Dispatch note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -60,
        800
      ],
      "parameters": {
        "content": "## 1b · Loadout, enforced\nEvery `use_tool` call runs here. **Loaded?** refuses any tool not selected for this request, even if the model guesses its name. Trade-off: the agent gets one generic tool plus prose definitions instead of typed schemas, so arguments are checked here.",
        "width": 640,
        "height": 560,
        "color": 4
      }
    },
    {
      "id": "c592df4c-c721-497b-9180-51b16d82bd51",
      "name": "Tool call",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.3,
      "position": [
        0,
        1100
      ],
      "parameters": {
        "inputSource": "workflowInputs",
        "workflowInputs": {
          "values": [
            {
              "name": "tool_name",
              "type": "string"
            },
            {
              "name": "input",
              "type": "string"
            },
            {
              "name": "allowed",
              "type": "string"
            },
            {
              "name": "session",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "a8ee57c0-59b7-447c-81c9-cf401dea0f90",
      "name": "Loaded?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        200,
        1100
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "loaded",
              "leftValue": "={{ String($json.allowed).split(',').includes($json.tool_name) }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      }
    },
    {
      "id": "fc70b584-2f50-403c-9316-ee19aadc5536",
      "name": "Route",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        420,
        1100
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "r0",
                    "leftValue": "={{ [\"calculator\", \"date_time\", \"unit_convert\", \"text_stats\"].includes($json.tool_name) }}",
                    "rightValue": "",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "local"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "r1",
                    "leftValue": "={{ [\"wikipedia\"].includes($json.tool_name) }}",
                    "rightValue": "",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "wikipedia"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "r2",
                    "leftValue": "={{ [\"save_note\"].includes($json.tool_name) }}",
                    "rightValue": "",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "save_note"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "r3",
                    "leftValue": "={{ [\"read_notes\"].includes($json.tool_name) }}",
                    "rightValue": "",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "read_notes"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "r4",
                    "leftValue": "={{ [\"research\"].includes($json.tool_name) }}",
                    "rightValue": "",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "research"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "951373a3-ab61-4606-900d-e8ac88f441eb",
      "name": "Not loaded",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        200,
        1260
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "Not loaded-0",
              "name": "result",
              "value": "=Tool \"{{ $json.tool_name }}\" is not loaded for this request. Loaded tools: {{ $json.allowed || 'none' }}.",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "98a0020a-d78e-476e-8a4d-3997d9c55c51",
      "name": "Local tools",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        760,
        700
      ],
      "parameters": {
        "jsCode": "// Plain deterministic tools. No LLM, no network.\nconst { tool_name: tool, input } = $input.first().json;\nconst text = String(input ?? '').trim();\n\nfunction calc(expr) {               // tiny recursive-descent parser, no eval()\n  const s = expr.replace(/,/g, '').replace(/\\s+/g, '');\n  let i = 0;\n  const peek = () => s[i];\n  function num() {\n    if (peek() === '(') { i++; const v = add(); if (s[i++] !== ')') throw new Error('missing )'); return v; }\n    if (peek() === '-') { i++; return -num(); }\n    const m = /^\\d+(\\.\\d+)?/.exec(s.slice(i));\n    if (!m) throw new Error('unexpected \"' + (peek() ?? 'end') + '\"');\n    i += m[0].length; let v = parseFloat(m[0]);\n    if (peek() === '%') { i++; v = v / 100; }\n    return v;\n  }\n  function pow() { const b = num(); if (peek() === '^') { i++; return Math.pow(b, pow()); } return b; }\n  function mul() { let v = pow(); while (peek() === '*' || peek() === '/') { const o = s[i++]; const r = pow(); v = o === '*' ? v * r : v / r; } return v; }\n  function add() { let v = mul(); while (peek() === '+' || peek() === '-') { const o = s[i++]; const r = mul(); v = o === '+' ? v + r : v - r; } return v; }\n  const v = add();\n  if (i !== s.length) throw new Error('unexpected \"' + s[i] + '\"');\n  return Math.round(v * 1e10) / 1e10;\n}\n\nconst UNITS = { m: ['len', 1], km: ['len', 1000], cm: ['len', 0.01], mm: ['len', 0.001], mi: ['len', 1609.344],\n  ft: ['len', 0.3048], in: ['len', 0.0254], kg: ['mass', 1], g: ['mass', 0.001], lb: ['mass', 0.45359237], oz: ['mass', 0.028349523125] };\nfunction convert(q) {\n  const m = /^(-?[\\d.]+)\\s*([a-z]+)\\s+(?:to|in)\\s+([a-z]+)$/i.exec(q);\n  if (!m) throw new Error('use \"<number> <unit> to <unit>\"');\n  const v = parseFloat(m[1]); const f = m[2].toLowerCase(); const t = m[3].toLowerCase();\n  const T = { c: x => x, f: x => (x - 32) * 5 / 9, k: x => x - 273.15 };\n  const B = { c: x => x, f: x => x * 9 / 5 + 32, k: x => x + 273.15 };\n  if (T[f] && B[t]) return `${v} ${m[2]} = ${Math.round(B[t](T[f](v)) * 100) / 100} ${m[3]}`;\n  if (!UNITS[f] || !UNITS[t] || UNITS[f][0] !== UNITS[t][0]) throw new Error('unsupported units');\n  return `${v} ${f} = ${Math.round(v * UNITS[f][1] / UNITS[t][1] * 10000) / 10000} ${t}`;\n}\n\nlet result;\ntry {\n  if (tool === 'calculator') result = `${text} = ${calc(text)}`;\n  else if (tool === 'date_time') {\n    const zone = (text.match(/[A-Za-z]+\\/[A-Za-z_]+/) || ['UTC'])[0];\n    const now = DateTime.now().setZone(zone);\n    result = `Now: ${now.toFormat(\"cccc yyyy-LL-dd HH:mm\")} (${zone})`;\n    const d = text.match(/\\d{4}-\\d{2}-\\d{2}/);\n    if (d) result += `; days from today until ${d[0]}: ${Math.round(DateTime.fromISO(d[0], { zone }).diff(now.startOf('day'), 'days').days)}`;\n  }\n  else if (tool === 'unit_convert') result = convert(text);\n  else if (tool === 'text_stats') result = `words: ${text.split(/\\s+/).filter(Boolean).length}, characters: ${text.length}, sentences: ${text.split(/[.!?]+/).filter(x => x.trim()).length}`;\n  else result = 'unknown local tool ' + tool;\n} catch (e) { result = `${tool} error: ${e.message}`; }\nreturn [{ json: { result } }];\n"
      }
    },
    {
      "id": "5bc1e81d-41da-4b88-8e3c-8cce82ceae01",
      "name": "Wikipedia",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        760,
        860
      ],
      "parameters": {
        "url": "=https://en.wikipedia.org/api/rest_v1/page/summary/{{ encodeURIComponent(String($json.input ?? '').trim().replace(/ /g, '_')) }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "n8n-context-engineering-demo/1.0"
            }
          ]
        },
        "options": {
          "timeout": 15000
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "3107f6a1-774f-43b1-810a-0dd6849b50b3",
      "name": "Summary",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        980,
        860
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "Summary-0",
              "name": "result",
              "value": "={{ $json.extract ? $json.title + ': ' + $json.extract : 'No Wikipedia article with that exact title.' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "9d10899f-122c-4c2a-b250-79ecb13ec773",
      "name": "Offload note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        700,
        980
      ],
      "parameters": {
        "content": "## 3 · Context offloading\nNotes live in the Data Table **ctx_agent_notes**, not in the chat history. The agent writes with `save_note` and reads with `read_notes` only when needed, so notes cost no tokens on other turns and survive memory trimming. Per chat: remove the filter in *Read notes* to share notes across chats. ([post](https://www.dbreunig.com/2026/10/24/the-state-of-context-engineering.html))",
        "width": 700,
        "height": 480,
        "color": 6
      }
    },
    {
      "id": "b435d8fe-1995-4fd2-9e1a-615e644c5c07",
      "name": "Save note",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        760,
        1180
      ],
      "parameters": {
        "resource": "row",
        "operation": "insert",
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "ctx_agent_notes"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "session": "={{ $json.session }}",
            "note": "={{ $json.input }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "session",
              "displayName": "session",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "note",
              "displayName": "note",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "cda7dee6-e94c-476e-a28b-75c5fb3d802c",
      "name": "Saved",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        980,
        1180
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "Saved-0",
              "name": "result",
              "value": "={{ $json.error ? 'save failed (does the Data Table ctx_agent_notes exist?): ' + ($json.error.message || $json.error) : 'Saved note #' + $json.id }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "b761a8a5-f0ec-4205-92be-59ebeb590d76",
      "name": "Read notes",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        760,
        1340
      ],
      "parameters": {
        "resource": "row",
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "ctx_agent_notes"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "session",
              "condition": "eq",
              "keyValue": "={{ $json.session }}"
            }
          ]
        },
        "returnAll": false,
        "limit": 20,
        "orderBy": true,
        "orderByColumn": "createdAt",
        "orderByDirection": "DESC"
      },
      "alwaysOutputData": true,
      "onError": "continueRegularOutput"
    },
    {
      "id": "6d702fea-ad89-4bf9-80a6-75087b1ba524",
      "name": "Notes list",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        980,
        1340
      ],
      "parameters": {
        "jsCode": "// Return the notes as one compact list (Data table rows carry extra system columns).\nconst items = $input.all().map(i => i.json);\nconst err = items.find(r => r.error);\nif (err) return [{ json: { result: 'read failed (does the Data Table ctx_agent_notes exist?): ' + (err.error.message || err.error) } }];\nconst notes = items.filter(r => r.note).reverse()   // oldest first\n  .map(r => `${String(r.createdAt).slice(0, 16)}  ${r.note}`);\nreturn [{ json: { result: notes.length ? notes.join('\\n') : 'No notes saved in this chat yet.' } }];\n"
      }
    },
    {
      "id": "0e1468cf-d22c-44e1-8d0e-1a8f081804c1",
      "name": "Quarantine note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        700,
        1500
      ],
      "parameters": {
        "content": "## 2 · Context quarantine\n`research` runs a second agent that starts with a **blank history**: no chat, no memory, only the question. It may read several long Wikipedia pages; only its ~80-word report returns to the main agent. Breunig: *\"You expose an agent as a tool... it starts with a blank history.\"* ([post](https://www.dbreunig.com/2026/10/24/the-state-of-context-engineering.html))",
        "width": 700,
        "height": 560,
        "color": 3
      }
    },
    {
      "id": "54410a11-b01f-471e-9288-46dccb8f2b78",
      "name": "Sub-agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        800,
        1700
      ],
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.input }}",
        "options": {
          "systemMessage": "You are a research sub-agent. Use the Wikipedia tool as often as needed to answer the question. Then reply with a factual report of at most 80 words. No preamble, no follow-up questions.",
          "maxIterations": 6
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "ede5baa8-b18b-47fa-bbc6-f99ad915d4d2",
      "name": "DeepSeek sub",
      "type": "@n8n/n8n-nodes-langchain.lmChatDeepSeek",
      "typeVersion": 1,
      "position": [
        760,
        1920
      ],
      "parameters": {
        "model": "deepseek-flash",
        "options": {
          "maxTokens": 2000,
          "temperature": 0.2
        }
      },
      "credentials": {
        "deepSeekApi": {
          "name": "DeepSeek account"
        }
      }
    },
    {
      "id": "ce67faa2-5d4d-4c70-92b4-298811095df2",
      "name": "Wikipedia search",
      "type": "@n8n/n8n-nodes-langchain.toolWikipedia",
      "typeVersion": 1,
      "position": [
        920,
        1920
      ],
      "parameters": {}
    },
    {
      "id": "562e9029-81d2-4266-87fa-3da9a1b3caa5",
      "name": "Report",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1160,
        1700
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "Report-0",
              "name": "result",
              "value": "={{ ($json.output || 'research failed: ' + ($json.error?.message || $json.error || 'no output')).slice(0, 700) }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Chat": {
      "main": [
        [
          {
            "node": "Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run sample": {
      "main": [
        [
          {
            "node": "Sample request",
            "type": "main",
            "index": 0
          },
          {
            "node": "Create notes table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample request": {
      "main": [
        [
          {
            "node": "Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request": {
      "main": [
        [
          {
            "node": "Tool catalogue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tool catalogue": {
      "main": [
        [
          {
            "node": "Pick tools",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pick tools": {
      "main": [
        [
          {
            "node": "Loadout",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loadout": {
      "main": [
        [
          {
            "node": "Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent": {
      "main": [
        [
          {
            "node": "Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DeepSeek": {
      "ai_languageModel": [
        [
          {
            "node": "Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Memory": {
      "ai_memory": [
        [
          {
            "node": "Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "use_tool": {
      "ai_tool": [
        [
          {
            "node": "Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Tool call": {
      "main": [
        [
          {
            "node": "Loaded?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loaded?": {
      "main": [
        [
          {
            "node": "Route",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Not loaded",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route": {
      "main": [
        [
          {
            "node": "Local tools",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wikipedia",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Save note",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Read notes",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Sub-agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wikipedia": {
      "main": [
        [
          {
            "node": "Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save note": {
      "main": [
        [
          {
            "node": "Saved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read notes": {
      "main": [
        [
          {
            "node": "Notes list",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sub-agent": {
      "main": [
        [
          {
            "node": "Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DeepSeek sub": {
      "ai_languageModel": [
        [
          {
            "node": "Sub-agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Wikipedia search": {
      "ai_tool": [
        [
          {
            "node": "Sub-agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
