deploy: Dockerfile, Compose pro Rolle, LiteLLM, Prometheus/Alertmanager, Canary-Prober, n8n-Workflows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 00:51:31 +02:00
co-authored by Claude Opus 5.5
parent 4cbe313c62
commit cb5a413169
28 changed files with 1731 additions and 9 deletions
+18
View File
@@ -0,0 +1,18 @@
# n8n-Workflows
Importierbar über *Workflows → Import from File*. Alle Workflows werden **inaktiv** importiert.
| Datei | Zweck | Trigger |
|---|---|---|
| `knaster-request.json` | Anfrage an den Router (`/v1/run`) und Antwort | Webhook `POST /webhook/knaster/run` `{input, skill?}` |
| `knaster-maintenance-chat.json` | Pflege-Agent per Prompt (HITL) | n8n Chat |
| `knaster-service-routine.json` | wöchentliche Service-Routine, Bericht an Kanal | Zeitplan Mo 08:00 |
| `knaster-notify.json` | Meldungen von Healer/Policy an einen Kanal | Webhook `POST /webhook/knaster/notify` (= `NOTIFY_WEBHOOK_URL`) |
| `knaster-jobs.json` | Lifecycle-/Skill-Jobs an Trainings-Runner bzw. Hermes | Webhook `POST /webhook/knaster/jobs` (= `KNASTER_JOB_WEBHOOK_URL`) |
Nach dem Import:
1. Credential **knaster-router** (Header Auth): Name `Authorization`, Wert `Bearer <KNASTER_API_TOKEN>`.
2. Credential **knaster-webhook** (Header Auth) für eingehende Webhooks. Das gleiche Header-Paar muss im Aufrufer gesetzt werden.
3. Im Knoten **Konfiguration** `router_url` und ggf. `channel_url`, `trainer_url`, `hermes_url` anpassen.
Die Entscheidungslogik liegt bewusst im Router-Service (ADR-0005). n8n kümmert sich um Trigger, Kanäle und Weiterleitung.
+249
View File
@@ -0,0 +1,249 @@
{
"name": "knaster: Jobs an Runner/Hermes",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "knaster/jobs",
"responseMode": "responseNode",
"options": {},
"authentication": "headerAuth"
},
"id": "24f3f3a3-47ae-5b87-8902-bcc3465b593a",
"name": "Job",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
300
],
"webhookId": "03e1babe-7daa-5ab1-ba6e-8cc4de50268c",
"credentials": {
"httpHeaderAuth": {
"id": "",
"name": "knaster-webhook"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "b3b14ae1-2d7f-5ef9-a96f-1d3693c703f6",
"name": "router_url",
"value": "http://router:8080",
"type": "string"
},
{
"id": "a3123e42-afa4-53f7-a266-066cce1ff763",
"name": "trainer_url",
"value": "http://gpu-flex:9000/jobs",
"type": "string"
},
{
"id": "c672b90a-49d5-5e50-9b23-3766353f0487",
"name": "hermes_url",
"value": "http://hermes:8000/jobs",
"type": "string"
}
]
},
"options": {}
},
"id": "acb2e1cb-0776-564b-b909-b5b3f613dc88",
"name": "Konfiguration",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
220,
300
],
"notes": "Nach dem Import anpassen. Credential 'knaster-router' = Header Auth: Authorization / Bearer <KNASTER_API_TOKEN>"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "9dec25ae-5535-5f01-aebb-b9d7e7397686",
"leftValue": "={{ ['graduate_skill','merge_skills'].includes($('Job').item.json.body.job) }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "a2881a83-cbb5-5cdb-96bb-d6a1833ac388",
"name": "Skill-Job?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
440,
300
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Konfiguration').item.json.hermes_url }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($('Job').item.json.body) }}",
"options": {
"timeout": 30000
}
},
"id": "ba36e0ed-7cca-5c04-af60-9644177cac6f",
"name": "An Hermes (Skill-Autor)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
660,
200
],
"credentials": {
"httpHeaderAuth": {
"id": "",
"name": "knaster-router"
}
}
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Konfiguration').item.json.trainer_url }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($('Job').item.json.body) }}",
"options": {
"timeout": 30000
}
},
"id": "98abbd0d-1384-52bb-b095-cb978f2bcc37",
"name": "An Trainings-Runner",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
660,
420
],
"credentials": {
"httpHeaderAuth": {
"id": "",
"name": "knaster-router"
}
}
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ accepted: true }) }}",
"options": {
"responseCode": 202
}
},
"id": "6678356f-6c70-58fe-a9e0-be3d7cf23851",
"name": "Angenommen",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
880,
300
]
}
],
"connections": {
"Job": {
"main": [
[
{
"node": "Konfiguration",
"type": "main",
"index": 0
}
]
]
},
"Konfiguration": {
"main": [
[
{
"node": "Skill-Job?",
"type": "main",
"index": 0
}
]
]
},
"Skill-Job?": {
"main": [
[
{
"node": "An Hermes (Skill-Autor)",
"type": "main",
"index": 0
}
],
[
{
"node": "An Trainings-Runner",
"type": "main",
"index": 0
}
]
]
},
"An Hermes (Skill-Autor)": {
"main": [
[
{
"node": "Angenommen",
"type": "main",
"index": 0
}
]
]
},
"An Trainings-Runner": {
"main": [
[
{
"node": "Angenommen",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "knaster"
},
{
"name": "lifecycle"
}
],
"meta": {
"description": "cluster-knaster – siehe docs/spec. Import inaktiv, Credentials setzen."
}
}
+147
View File
@@ -0,0 +1,147 @@
{
"name": "knaster: Pflege-Agent (Chat)",
"nodes": [
{
"parameters": {
"public": false,
"options": {
"responseMode": "lastNode"
}
},
"id": "ef50a82c-c64a-5a2e-b380-f40ae2a7f738",
"name": "Chat",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"typeVersion": 1.1,
"position": [
0,
300
],
"webhookId": "f819ed24-d095-5120-b2f2-e996135967e1"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "fecd0058-75b7-569f-8970-e4647b315631",
"name": "router_url",
"value": "http://router:8080",
"type": "string"
}
]
},
"options": {}
},
"id": "a08a0680-3d4b-5845-8693-86627a2aaeb4",
"name": "Konfiguration",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
220,
300
],
"notes": "Nach dem Import anpassen. Credential 'knaster-router' = Header Auth: Authorization / Bearer <KNASTER_API_TOKEN>"
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Konfiguration').item.json.router_url }}/v1/maintenance/command",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ text: $('Chat').item.json.chatInput, session: $('Chat').item.json.sessionId, user: 'n8n-chat' }) }}",
"options": {
"timeout": 60000
}
},
"id": "4fc64c5d-b22d-5168-a4bb-d56a3c4e1001",
"name": "Befehl an Pflege-Agent",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
440,
300
],
"credentials": {
"httpHeaderAuth": {
"id": "",
"name": "knaster-router"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "4d75dea4-a1de-51e1-b324-06282f454d47",
"name": "output",
"value": "={{ $json.text }}",
"type": "string"
}
]
},
"options": {}
},
"id": "ca5fef45-e097-52a6-af88-8d9a6c8d1461",
"name": "Antwort",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
660,
300
]
}
],
"connections": {
"Chat": {
"main": [
[
{
"node": "Konfiguration",
"type": "main",
"index": 0
}
]
]
},
"Konfiguration": {
"main": [
[
{
"node": "Befehl an Pflege-Agent",
"type": "main",
"index": 0
}
]
]
},
"Befehl an Pflege-Agent": {
"main": [
[
{
"node": "Antwort",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "knaster"
},
{
"name": "hitl"
}
],
"meta": {
"description": "cluster-knaster – siehe docs/spec. Import inaktiv, Credentials setzen."
}
}
+141
View File
@@ -0,0 +1,141 @@
{
"name": "knaster: Benachrichtigungen",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "knaster/notify",
"responseMode": "responseNode",
"options": {},
"authentication": "headerAuth"
},
"id": "e324dc52-1df2-54f4-916f-540c3033562a",
"name": "Healer-Meldung",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
300
],
"webhookId": "978b850a-ca87-5dca-a16e-b737df5acad2",
"credentials": {
"httpHeaderAuth": {
"id": "",
"name": "knaster-webhook"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "904e09c8-286a-5272-8b3f-4c6c49899e9d",
"name": "router_url",
"value": "http://router:8080",
"type": "string"
},
{
"id": "fe09313b-633c-58b6-8e80-ef4db981118f",
"name": "channel_url",
"value": "https://example.invalid/kanal-webhook",
"type": "string"
}
]
},
"options": {}
},
"id": "7868766e-ff24-5694-9338-f7b0c3aa6603",
"name": "Konfiguration",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
220,
300
],
"notes": "Nach dem Import anpassen. Credential 'knaster-router' = Header Auth: Authorization / Bearer <KNASTER_API_TOKEN>"
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Konfiguration').item.json.channel_url }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ text: '[' + $('Healer-Meldung').item.json.body.severity + '] ' + $('Healer-Meldung').item.json.body.text }) }}",
"options": {}
},
"id": "28c5f6c3-74a0-5b70-8225-2196f1b9164d",
"name": "An Kanal senden",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
440,
300
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ ok: true }) }}",
"options": {
"responseCode": 200
}
},
"id": "3518decf-c8b3-5d29-b88b-b59a0980d173",
"name": "OK",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
660,
300
]
}
],
"connections": {
"Healer-Meldung": {
"main": [
[
{
"node": "Konfiguration",
"type": "main",
"index": 0
}
]
]
},
"Konfiguration": {
"main": [
[
{
"node": "An Kanal senden",
"type": "main",
"index": 0
}
]
]
},
"An Kanal senden": {
"main": [
[
{
"node": "OK",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "knaster"
}
],
"meta": {
"description": "cluster-knaster – siehe docs/spec. Import inaktiv, Credentials setzen."
}
}
+212
View File
@@ -0,0 +1,212 @@
{
"name": "knaster: Anfrage ausführen",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "knaster/run",
"responseMode": "responseNode",
"options": {},
"authentication": "headerAuth"
},
"id": "9463206f-d1cb-52fc-98ba-01370b80290b",
"name": "Anfrage",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
300
],
"webhookId": "bba18b52-dcd1-5231-a132-0434e5f27505",
"credentials": {
"httpHeaderAuth": {
"id": "",
"name": "knaster-webhook"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "b65c35b7-cf8b-5067-8c3c-d90533ad04dd",
"name": "router_url",
"value": "http://router:8080",
"type": "string"
}
]
},
"options": {}
},
"id": "4ccf1d69-7b78-5ad7-9d1a-40ae77e7b3d1",
"name": "Konfiguration",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
220,
300
],
"notes": "Nach dem Import anpassen. Credential 'knaster-router' = Header Auth: Authorization / Bearer <KNASTER_API_TOKEN>"
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Konfiguration').item.json.router_url }}/v1/run",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ input: $('Anfrage').item.json.body.input, skill: $('Anfrage').item.json.body.skill || null }) }}",
"options": {
"timeout": 180000
}
},
"id": "d6f5c10d-2a30-598a-af5e-3f8040098cb4",
"name": "Router /v1/run",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
440,
300
],
"credentials": {
"httpHeaderAuth": {
"id": "",
"name": "knaster-router"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "3fc7e0c8-fd00-5368-819c-dff9572baf12",
"leftValue": "={{ $json.status }}",
"rightValue": "ok",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "01605cd8-f4e0-53c6-acfe-32c389d6cbe6",
"name": "Erfolgreich?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
660,
300
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ output: $json.output, stage: $json.stage, skill: $json.skill, escalations: $json.escalations, run_id: $json.run_id }) }}",
"options": {
"responseCode": 200
}
},
"id": "9ca6279a-dfb6-525e-aa18-8385bed2f448",
"name": "Antwort",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
880,
200
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ status: $json.status, run_id: $json.run_id, reasons: $json.attempts[$json.attempts.length-1].decision.reasons }) }}",
"options": {
"responseCode": 422
}
},
"id": "8728502e-e051-5259-a2bd-57646e3b1c5e",
"name": "Fehler",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
880,
420
]
}
],
"connections": {
"Anfrage": {
"main": [
[
{
"node": "Konfiguration",
"type": "main",
"index": 0
}
]
]
},
"Konfiguration": {
"main": [
[
{
"node": "Router /v1/run",
"type": "main",
"index": 0
}
]
]
},
"Router /v1/run": {
"main": [
[
{
"node": "Erfolgreich?",
"type": "main",
"index": 0
}
]
]
},
"Erfolgreich?": {
"main": [
[
{
"node": "Antwort",
"type": "main",
"index": 0
}
],
[
{
"node": "Fehler",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "knaster"
}
],
"meta": {
"description": "cluster-knaster – siehe docs/spec. Import inaktiv, Credentials setzen."
}
}
+199
View File
@@ -0,0 +1,199 @@
{
"name": "knaster: Service-Routine (wöchentlich)",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"weeksInterval": 1,
"triggerAtDay": [
1
],
"triggerAtHour": 8
}
]
}
},
"id": "c58239bb-f1c9-557c-952c-21645fcb8a57",
"name": "Zeitplan",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
0,
300
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "df17a04d-3369-5ef6-83b2-150c90db08fa",
"name": "router_url",
"value": "http://router:8080",
"type": "string"
},
{
"id": "fb597b99-b6c3-5306-b5fa-68ffc02938d3",
"name": "channel_url",
"value": "https://example.invalid/kanal-webhook",
"type": "string"
}
]
},
"options": {}
},
"id": "dc94a1c9-74b6-5403-942a-59a84e634a59",
"name": "Konfiguration",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
220,
300
],
"notes": "Nach dem Import anpassen. Credential 'knaster-router' = Header Auth: Authorization / Bearer <KNASTER_API_TOKEN>"
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Konfiguration').item.json.router_url }}/v1/maintenance/routine",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({}) }}",
"options": {
"timeout": 180000
}
},
"id": "ba3983aa-c877-5686-8297-60b9be29bdb3",
"name": "Routine starten",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
440,
300
],
"credentials": {
"httpHeaderAuth": {
"id": "",
"name": "knaster-router"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "f74160f5-984f-5078-9fc3-5557cdc71e5f",
"leftValue": "={{ $json.findings.length + $json.proposals.length + $json.auto_executed.length }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "07062ef5-09ab-5043-83d6-7d4806fe945a",
"name": "Etwas zu tun?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
660,
300
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Konfiguration').item.json.channel_url }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ text: $json.text }) }}",
"options": {}
},
"id": "f6ec4fd3-03a4-5226-beec-ac4f11c0fb30",
"name": "An Kanal senden",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
880,
200
],
"notes": "Kanal (Telegram/Matrix/Mail) hier anbinden. Antworten laufen über den Pflege-Chat."
}
],
"connections": {
"Zeitplan": {
"main": [
[
{
"node": "Konfiguration",
"type": "main",
"index": 0
}
]
]
},
"Konfiguration": {
"main": [
[
{
"node": "Routine starten",
"type": "main",
"index": 0
}
]
]
},
"Routine starten": {
"main": [
[
{
"node": "Etwas zu tun?",
"type": "main",
"index": 0
}
]
]
},
"Etwas zu tun?": {
"main": [
[
{
"node": "An Kanal senden",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "knaster"
},
{
"name": "hitl"
}
],
"meta": {
"description": "cluster-knaster – siehe docs/spec. Import inaktiv, Credentials setzen."
}
}