AutomationFlowsAI & RAG › Weekly Team Watchdog Canary

Weekly Team Watchdog Canary

Original n8n title: Agent Team Watchdog — Weekly Strategic-routine Canary

Agent Team Watchdog — Weekly Strategic-Routine Canary. Uses httpRequest, slack. Scheduled trigger; 10 nodes.

Cron / scheduled trigger★★★★☆ complexity10 nodesHTTP RequestSlack
AI & RAG Trigger: Cron / scheduled Nodes: 10 Complexity: ★★★★☆ Added:

This workflow follows the HTTP Request → Slack recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "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
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Agent Team Watchdog — Weekly Strategic-Routine Canary. Uses httpRequest, slack. Scheduled trigger; 10 nodes.

Source: https://github.com/rczamor/rz-agent-team/blob/70dec0b7e42f6173ecfa4c86fcf49f17e9945cfa/n8n/watchdog-canary.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

It identifies SKUs with low inventory per source and sends daily alerts via:

Slack, HTTP Request, Gmail
AI & RAG

Linear Router — Reconciler. Uses httpRequest, slack. Scheduled trigger; 17 nodes.

HTTP Request, Slack
AI & RAG

Optimize Campaigns. Uses httpRequest, slack, supabase. Scheduled trigger; 10 nodes.

HTTP Request, Slack, Supabase
AI & RAG

Created by: Peyton Leveillee Last updated: October 2025

OpenAI Chat, Google Sheets, HTTP Request +5
AI & RAG

This workflow empowers app developers and community management teams by automating the generation and posting of responses to user reviews on the Apple App Store. Designed to streamline the engagement

Jwt, HTTP Request, Slack +5