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 →
{
"id": "aicp-incident-responder",
"name": "Incident responder (event-driven skills, AG-3)",
"meta": {
"description": "Source-agnostic incident lane: a webhook accepts PagerDuty, Alertmanager, Slack slash-command, or generic payloads; the agent diagnoses using the scope's skills; a human approves before any fix is applied (via the AG-2 worktree fan-out, merge stays human); the learning is encoded back into a skill. Set the approval-gate workflow ID and tokens; see README."
},
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "incident",
"options": {}
},
"id": "adadadad-0000-0000-0000-000000000001",
"name": "Incident webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
200,
300
]
},
{
"parameters": {
"jsCode": "// Normalize ANY supported event source into one shape:\n// { id, title, description, severity, service, scope, category }\n// CONFIG below maps services->scopes and keywords->skill categories.\nconst CFG = {\n serviceScopeMap: { 'default': 'data-team' }, // e.g. 'billing-api': 'engineering'\n categoryRules: [\n { re: /stream|kafka|pipeline/i, cat: 'streaming' },\n { re: /bill|invoice|payment/i, cat: 'billing' },\n { re: /auth|login|token/i, cat: 'auth' }\n ],\n repo: 'repo', // git repo under workspace/ for the apply step\n base: 'main',\n applyVerify: 'npm test', // self-verify command for the fix worktree\n encodeLearning: true\n};\nconst raw = $input.first().json;\nconst b = raw.body || raw;\nlet ev;\nif (b.event && (b.event.event_type || b.event.eventType)) {\n // PagerDuty v3 webhook\n const d = b.event.data || {};\n ev = { title: d.title || b.event.event_type, description: JSON.stringify(d).slice(0, 1500),\n severity: d.urgency === 'high' ? 'error' : 'warn', service: d.service?.summary || 'default' };\n} else if (Array.isArray(b.alerts)) {\n // Prometheus Alertmanager / Grafana\n const firing = b.alerts.filter(a => a.status === 'firing');\n const a0 = firing[0] || b.alerts[0] || {};\n ev = { title: a0.labels?.alertname || 'alert', description: firing.map(a => `${a.labels?.alertname}: ${a.annotations?.summary || a.annotations?.description || ''}`).join('\\n').slice(0, 1500),\n severity: 'error', service: a0.labels?.service || a0.labels?.job || 'default' };\n} else if (b.command || (b.event && b.event.type === 'app_mention')) {\n // Slack slash command (form fields) or Events-API mention\n const text = b.text || b.event?.text || '';\n ev = { title: `chat request: ${text.slice(0, 80)}`, description: text,\n severity: 'warn', service: 'default' };\n} else {\n // generic { title, description, severity?, service? }\n ev = { title: b.title || 'incident', description: b.description || JSON.stringify(b).slice(0, 1500),\n severity: b.severity || 'warn', service: b.service || 'default' };\n}\nconst hay = `${ev.title} ${ev.description}`;\nconst category = (CFG.categoryRules.find(r => r.re.test(hay)) || {}).cat || 'general';\nconst scope = CFG.serviceScopeMap[ev.service] || CFG.serviceScopeMap['default'];\nreturn [{ json: { ...ev, id: `inc-${$execution.id}`, scope, category, cfg: CFG } }];"
},
"id": "adadadad-0000-0000-0000-000000000002",
"name": "Normalize + classify",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
420,
300
]
},
{
"parameters": {
"method": "POST",
"url": "http://router:8080/route",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "x-router-token",
"value": "change-me-to-a-long-random-secret"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { \"prompt\": 'INCIDENT ' + $json.id + ' [' + $json.category + ']: ' + $json.title + '\\n\\n' + $json.description + '\\n\\nConsult the skills/ directories in your working scope (including inherited ancestor scopes) for runbooks matching this category. Diagnose the likely root cause using available context. Write a report to deliverables/' + $json.id + '.md containing: symptom, evidence, root cause hypothesis, PROPOSED fix (do NOT apply anything), and rollback considerations.', \"task_type\": \"debug\", \"scope\": $json.scope, \"maxTurns\": 30 } }}",
"options": {
"timeout": 900000
}
},
"id": "adadadad-0000-0000-0000-000000000003",
"name": "Diagnose (router)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
640,
300
]
},
{
"parameters": {
"jsCode": "// Typed approval contract (G3). The human must be able to judge this without\n// opening another tab \u2014 that is the whole point of the gate.\nconst i = $input.first().json;\nconst d = i.decision || {}; // the router's decision block, computed upstream\nconst n = $('Normalize + classify').first().json;\n\nreturn [{ json: {\n action: `Apply an automated fix to ${n.repo || 'the repo'} on branch ${n.base || 'main'}`,\n scope: d.scope || n.scope || 'unknown',\n riskTier: d.risk?.tier || d.risk || n.severity || 'unknown',\n // WHICH dimensions fired, not just the resulting tier. A tier alone tells the\n // approver how worried to be; the dimensions tell them WHY, which is what they\n // are actually being asked to check.\n dimensions: [\n d.risk?.data && `data=${d.risk.data}`,\n d.risk?.access && `access=${d.risk.access}`,\n d.risk?.autonomy && `autonomy=${d.risk.autonomy}`,\n d.pii && d.pii !== 'none' && `pii=${d.pii}`,\n Array.isArray(d.guardrails) && d.guardrails.length && `guardrails=${d.guardrails.map(g => g.type).join('/')}`,\n ].filter(Boolean).join(', ') || 'none fired',\n requester: `incident-responder (${n.severity || 'unknown'} severity, triggered by webhook)`,\n dataTouched: `${n.repo || 'repo'} working tree; verify=${n.applyVerify ? 'yes' : 'no'}`,\n preview: String(i.result || i.message || '(no diagnosis returned)').slice(0, 4000),\n autoApprove: false,\n} }];\n"
},
"id": "adadadad-0000-0000-0000-000000000004",
"name": "Fix approval request",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
860,
300
]
},
{
"parameters": {
"source": "database",
"workflowId": {
"__rl": true,
"value": "aicp-approval-gate",
"mode": "id"
},
"options": {
"waitForSubWorkflow": true
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"action": "={{ $json.action }}",
"scope": "={{ $json.scope }}",
"riskTier": "={{ $json.riskTier }}",
"requester": "={{ $json.requester }}",
"dataTouched": "={{ $json.dataTouched }}",
"preview": "={{ $json.preview }}",
"dimensions": "={{ $json.dimensions }}",
"autoApprove": "={{ $json.autoApprove }}"
},
"matchingColumns": [],
"schema": [
{
"id": "action",
"displayName": "action",
"type": "string",
"required": true,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
},
{
"id": "scope",
"displayName": "scope",
"type": "string",
"required": true,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
},
{
"id": "riskTier",
"displayName": "riskTier",
"type": "string",
"required": true,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
},
{
"id": "requester",
"displayName": "requester",
"type": "string",
"required": true,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
},
{
"id": "dataTouched",
"displayName": "dataTouched",
"type": "string",
"required": true,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
},
{
"id": "preview",
"displayName": "preview",
"type": "string",
"required": true,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
},
{
"id": "dimensions",
"displayName": "dimensions",
"type": "string",
"required": true,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
},
{
"id": "autoApprove",
"displayName": "autoApprove",
"type": "boolean",
"required": true,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": true
}
},
"id": "adadadad-0000-0000-0000-000000000005",
"name": "Request approval",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
1080,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true
},
"conditions": [
{
"leftValue": "={{ $json.approved }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
]
},
"options": {}
},
"id": "adadadad-0000-0000-0000-000000000006",
"name": "Approved?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1300,
300
]
},
{
"parameters": {
"method": "POST",
"url": "http://claude-runner:8080/fanout",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "x-runner-token",
"value": "change-me-to-a-long-random-secret"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { \"repo\": $('Normalize + classify').first().json.cfg.repo, \"base\": $('Normalize + classify').first().json.cfg.base, \"clean\": true, \"tasks\": [ { \"id\": $('Normalize + classify').first().json.id, \"prompt\": 'Apply the fix proposed in the incident report deliverables/' + $('Normalize + classify').first().json.id + '.md (in the workspace scope directory). Implement it in this repository, keeping the change minimal.', \"verify\": $('Normalize + classify').first().json.cfg.applyVerify } ] } }}",
"options": {
"timeout": 900000
}
},
"id": "adadadad-0000-0000-0000-000000000007",
"name": "Apply fix (worktree)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1520,
200
]
},
{
"parameters": {
"method": "POST",
"url": "http://router:8080/route",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "x-router-token",
"value": "change-me-to-a-long-random-secret"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ { \"prompt\": 'Encode what was learned from incident ' + $('Normalize + classify').first().json.id + ' as a durable skill. Read deliverables/' + $('Normalize + classify').first().json.id + '.md and write/update a short imperative skill file at skills/' + $('Normalize + classify').first().json.category + '-' + $('Normalize + classify').first().json.id + '.md covering: symptom pattern, how to detect it fast, root cause, the fix that worked, prevention. No tenant names, no PII.', \"task_type\": \"generate\", \"scope\": $('Normalize + classify').first().json.scope, \"maxTurns\": 15 } }}",
"options": {
"timeout": 600000
}
},
"id": "adadadad-0000-0000-0000-000000000008",
"name": "Encode learning (skill)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1740,
200
]
},
{
"parameters": {
"jsCode": "const ev = $('Normalize + classify').first().json;\nconst fan = $('Apply fix (worktree)').first().json;\nconst r = (fan.results || [])[0] || {};\nreturn [{ json: {\n severity: r.verify?.passed ? 'info' : 'warn',\n title: `Incident ${ev.id}: fix ${r.verify?.passed ? 'ready for merge' : 'NEEDS ATTENTION'}`,\n message: `${fan.review || ''}\\nBranch: ${r.branch || 'n/a'} | verify: ${r.verify ? (r.verify.passed ? 'PASS' : 'FAIL') : 'not run'}\\nLearning encoded to skills/. Merge remains a human act.`,\n source: 'incident-responder'\n} }];"
},
"id": "adadadad-0000-0000-0000-000000000009",
"name": "Summary",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1960,
200
]
},
{
"parameters": {
"jsCode": "const ev = $('Normalize + classify').first().json;\nconst gate = $('Request approval').first().json;\nreturn [{ json: {\n severity: 'warn',\n title: `Incident ${ev.id}: diagnosis only (fix not applied)`,\n message: `${gate.timedOut ? 'Approval timed out \u2014 default deny.' : 'Fix rejected by approver.'}\\nReport remains at deliverables/${ev.id}.md.`,\n source: 'incident-responder'\n} }];"
},
"id": "adadadad-0000-0000-0000-00000000000a",
"name": "Diagnosis-only summary",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1520,
420
]
}
],
"connections": {
"Incident webhook": {
"main": [
[
{
"node": "Normalize + classify",
"type": "main",
"index": 0
}
]
]
},
"Normalize + classify": {
"main": [
[
{
"node": "Diagnose (router)",
"type": "main",
"index": 0
}
]
]
},
"Diagnose (router)": {
"main": [
[
{
"node": "Fix approval request",
"type": "main",
"index": 0
}
]
]
},
"Fix approval request": {
"main": [
[
{
"node": "Request approval",
"type": "main",
"index": 0
}
]
]
},
"Request approval": {
"main": [
[
{
"node": "Approved?",
"type": "main",
"index": 0
}
]
]
},
"Approved?": {
"main": [
[
{
"node": "Apply fix (worktree)",
"type": "main",
"index": 0
}
],
[
{
"node": "Diagnosis-only summary",
"type": "main",
"index": 0
}
]
]
},
"Apply fix (worktree)": {
"main": [
[
{
"node": "Encode learning (skill)",
"type": "main",
"index": 0
}
]
]
},
"Encode learning (skill)": {
"main": [
[
{
"node": "Summary",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {},
"active": false
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Incident responder (event-driven skills, AG-3). Uses httpRequest. Webhook trigger; 10 nodes.
Source: https://github.com/jgobuilds/ai-control-plane-public/blob/main/n8n-workflows/incident-responder.workflow.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di
This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .
eek-Go v2 (Batch-Then-Review). Uses httpRequest. Webhook trigger; 75 nodes.
This workflow receives webhook requests from a content calendar and uses the X API v2 to publish text posts, threads, image/video posts, and polls, as well as delete existing posts and run a credentia
This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c