{
  "id": "ghiath-vault-watch",
  "name": "Ghiath Vault Watch",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "seconds",
              "secondsInterval": 30
            }
          ]
        }
      },
      "id": "vw-schedule",
      "name": "Every 30s",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        260,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Poll the vault every interval and trigger the agent that owns any folder where\n// a new \"open\" note appeared. Replaces the old localFileTrigger approach, which\n// depended on the Local File Trigger node (unavailable in this n8n image, shows\n// as \"?\") and on inotify seeing subfolders. This uses only fs + a schedule, the\n// same proven pattern as the reminder scheduler.\n//\n// Dedup: workflow static data remembers, per note, the content hash we last\n// fired on. We fire once per distinct content. When the agent flips status to\n// \"doing\"/\"done\" the content changes AND it is no longer \"open\", so it is never\n// re-triggered; and an unchanged still-\"open\" note is not spammed every poll.\nconst fs = require('fs');\nconst path = require('path');\nconst crypto = require('crypto');\n\nconst ROOT = '/data/vault';\nconst DEFAULT_ROUTES = {\n  assistant:  { port: 8642, model: 'deepseek/deepseek-v2-flash', folder: 'scratchpads' },\n  engineer:   { port: 8643, model: 'anthropic/claude-opus-4-8', folder: 'projects' },\n  researcher: { port: 8644, model: 'z-ai/glm-4.6', folder: 'memory' },\n};\nlet routes = DEFAULT_ROUTES;\ntry { if ($env.GHIATH_ROUTES) routes = JSON.parse($env.GHIATH_ROUTES); } catch (e) {}\n\nconst agentByFolder = {};\nfor (const [name, cfg] of Object.entries(routes)) { if (cfg.folder) agentByFolder[cfg.folder] = name; }\nconst folders = Object.keys(agentByFolder);\nif (!folders.length) return [];\n\nconst store = $getWorkflowStaticData('global');\nconst fired = store.fired || (store.fired = {});\n\nconst out = [];\nconst seen = new Set();\n\nfor (const folder of folders) {\n  const dir = path.join(ROOT, folder);\n  let files = [];\n  try { files = fs.readdirSync(dir).filter((f) => f.endsWith('.md')); } catch (e) { continue; }\n\n  for (const f of files) {\n    const key = folder + '/' + f;\n    seen.add(key);\n    const fp = path.join(dir, f);\n    let raw;\n    try { raw = fs.readFileSync(fp, 'utf8'); } catch (e) { continue; }\n\n    let status = 'open'; // notes without front-matter are human-created open tasks\n    const fm = raw.match(/^---\\n([\\s\\S]*?)\\n---/);\n    if (fm) {\n      const s = fm[1].match(/^status:\\s*(\\S+)/m);\n      if (s) status = s[1].toLowerCase();\n    }\n    if (status !== 'open') continue;\n\n    const hash = crypto.createHash('sha256').update(raw).digest('hex');\n    if (fired[key] === hash) continue; // already fired for this exact content\n    fired[key] = hash;\n\n    const agent = agentByFolder[folder];\n    const r = routes[agent];\n    const url = 'http://host.docker.internal:' + r.port + '/v1/chat/completions';\n    const hostPath = key;\n    const prompt =\n      'An open note was placed in your folder: ' + hostPath +\n      '\\nRead it, set its status to \"doing\", act on it, then set its status to \"done\". ' +\n      'Record your result as a note with type \"result\", status \"done\", owner yourself. ' +\n      'If the task should move to another agent, drop an \"open\" note in that agent\\'s folder with owner set to them.';\n\n    out.push({ json: { agent, folder, url, model: r.model, prompt, note: hostPath } });\n  }\n}\n\n// Forget notes that no longer exist so the map cannot grow forever.\nfor (const k of Object.keys(fired)) { if (!seen.has(k)) delete fired[k]; }\n\nreturn out;\n"
      },
      "id": "vw-scan",
      "name": "Scan Open Handoffs",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        520,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.url }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: $json.model, messages: [ { role: 'user', content: $json.prompt } ] }) }}",
        "options": {
          "timeout": 300000
        }
      },
      "id": "call-hermes",
      "name": "Trigger Owning Agent",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        780,
        300
      ],
      "onError": "continueRegularOutput"
    }
  ],
  "connections": {
    "Every 30s": {
      "main": [
        [
          {
            "node": "Scan Open Handoffs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scan Open Handoffs": {
      "main": [
        [
          {
            "node": "Trigger Owning Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false
}