{
  "name": "Agent Team Watchdog \u2014 Weekly Strategic-Routine Canary",
  "active": true,
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1"
            }
          ]
        }
      },
      "id": "trigger-weekly",
      "name": "Mondays 09:00 UTC",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Build the 4 canary tickets \u2014 one per strategic routine. Known-good prompts\n// designed to produce a short, cheap deliverable. TEAM_ID must match the Agent Team\n// team in Linear (env: LINEAR_TEAM_ID).\nconst now = new Date().toISOString().slice(0, 10);\nconst canaries = [\n  { typeLabel: 'type:architect', routine: 'Technical Architect', title: `[Canary ${now}] Should we adopt Python 3.13 for new services?`, prompt: 'Produce a 2-paragraph ADR draft on whether new services should adopt Python 3.13. Include one risk and one opportunity. Keep it under 400 words.' },\n  { typeLabel: 'type:analyst', routine: 'Analyst', title: `[Canary ${now}] Competitive snapshot \u2014 one prototype`, prompt: 'Produce a 3-bullet competitive snapshot for the Recipe Remix prototype. Focus on whitespace vs. 1-2 named competitors. Keep it under 300 words.' },\n  { typeLabel: 'type:ux', routine: 'User Researcher', title: `[Canary ${now}] Usability heuristic \u2014 richezamor.com homepage`, prompt: 'Do a 3-point Nielsen heuristic scan of the richezamor.com homepage. Flag the single highest-severity finding. Under 300 words.' },\n  { typeLabel: 'type:research', routine: 'AI Researcher', title: `[Canary ${now}] Method eval \u2014 prompt caching for SIA consolidation`, prompt: 'Give a short adopt/trial/hold recommendation on whether SIA\\'s consolidation pipeline should use Anthropic prompt caching. 3 bullets of rationale. Under 300 words.' },\n];\nreturn canaries.map(c => ({ json: { ...c, canaryDate: now } }));"
      },
      "id": "build-canaries",
      "name": "Build canary tickets",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        200,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.linear.app/graphql",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ $env.LINEAR_API_TOKEN }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  query: `mutation CreateCanary($teamId: String!, $title: String!, $desc: String!, $labelIds: [String!]) { issueCreate(input: { teamId: $teamId, title: $title, description: $desc, labelIds: $labelIds, stateId: null }) { success issue { id identifier url } } }`,\n  variables: {\n    teamId: $env.LINEAR_TEAM_ID,\n    title: $json.title,\n    desc: $json.prompt + '\\n\\n_Generated by weekly canary watchdog_',\n    labelIds: [$env[`LINEAR_LABEL_ID_${$json.typeLabel.replace('type:', '').toUpperCase()}`]]\n  }\n}) }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "create-canary",
      "name": "Create canary ticket",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        400,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Extract the created issue id + identifier so we can flip its state next.\nconst in0 = $('Build canary tickets').item.json;\nconst resp = $input.first().json;\nconst issue = resp?.data?.issueCreate?.issue;\nif (!issue) throw new Error(`issueCreate failed: ${JSON.stringify(resp).slice(0, 300)}`);\nreturn [{ json: { ...in0, issueId: issue.id, issueIdentifier: issue.identifier, issueUrl: issue.url } }];"
      },
      "id": "capture-issue",
      "name": "Capture issue id",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        600,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.linear.app/graphql",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ $env.LINEAR_API_TOKEN }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  query: `mutation FlipState($id: String!, $stateId: String!) { issueUpdate(id: $id, input: { stateId: $stateId }) { success } }`,\n  variables: { id: $json.issueId, stateId: $env.LINEAR_STATE_ID_READY_FOR_CLAUDE_ROUTINES }\n}) }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "flip-to-ready",
      "name": "Flip to 'Ready for Claude routines'",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        800,
        0
      ]
    },
    {
      "parameters": {
        "amount": 30,
        "unit": "minutes"
      },
      "id": "wait-30min",
      "name": "Wait 30 min",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1000,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.linear.app/graphql",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ $env.LINEAR_API_TOKEN }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  query: `query CanaryComments($id: String!) { issue(id: $id) { comments(first: 20) { nodes { body } } } }`,\n  variables: { id: $json.issueId }\n}) }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "fetch-comments",
      "name": "Fetch canary comments",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1200,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Evaluate the canary: did n8n fire the routine? Did the routine complete?\nconst in0 = $('Capture issue id').item.json;\nconst resp = $input.first().json;\nconst comments = resp?.data?.issue?.comments?.nodes || [];\nconst bodies = comments.map(c => c.body || '');\nconst fired = bodies.some(b => /routine fired/i.test(b));\nconst complete = bodies.some(b => /\u2713.*complete|\u2713 complete/i.test(b));\nconst notion = bodies.some(b => /notion\\.so|notion\\.site/i.test(b));\n\nconst missing = [];\nif (!fired) missing.push('fire');\nif (!complete) missing.push('complete');\nif (!notion) missing.push('notion-artifact');\n\nreturn [{\n  json: {\n    ...in0,\n    fired,\n    complete,\n    notion,\n    missing,\n    healthy: missing.length === 0,\n  }\n}];"
      },
      "id": "evaluate",
      "name": "Evaluate canary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1400,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "not-healthy",
              "leftValue": "={{ $json.healthy }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "false"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-missed",
      "name": "If canary missed a step",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1600,
        0
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "#agent-team",
          "mode": "name",
          "cachedResultName": "agent-team"
        },
        "text": "=\ud83d\udc24 Canary failed \u2014 *{{ $json.routine }}* ({{ $json.issueIdentifier }}) missing: {{ $json.missing.join(', ') }}\n{{ $json.issueUrl }}",
        "otherOptions": {}
      },
      "id": "slack-canary-failed",
      "name": "Slack \u2014 canary failed",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1800,
        0
      ],
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    }
  ],
  "connections": {
    "Mondays 09:00 UTC": {
      "main": [
        [
          {
            "node": "Build canary tickets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build canary tickets": {
      "main": [
        [
          {
            "node": "Create canary ticket",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create canary ticket": {
      "main": [
        [
          {
            "node": "Capture issue id",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Capture issue id": {
      "main": [
        [
          {
            "node": "Flip to 'Ready for Claude routines'",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flip to 'Ready for Claude routines'": {
      "main": [
        [
          {
            "node": "Wait 30 min",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 30 min": {
      "main": [
        [
          {
            "node": "Fetch canary comments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch canary comments": {
      "main": [
        [
          {
            "node": "Evaluate canary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate canary": {
      "main": [
        [
          {
            "node": "If canary missed a step",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If canary missed a step": {
      "main": [
        [
          {
            "node": "Slack \u2014 canary failed",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "tags": [],
  "meta": {
    "templateCredsSetupCompleted": false
  }
}