opsadvanced15 min setupby Mantle
workflow_health (system)
Acts on Workflow Health findings Auto Fix cannot answer: plans, branches, fixes, validates, commits and opens a pull request for a code defect through the host runner; files the rest for a person; reports back to the health check. Tag the installed workflow system:health and point Settings → Workflow Health at it.
Drag to pan, pinch to zoom. This is the real graph the template installs.
How it works
What it does
- Started by the Workflow Health role (never by hand) with the findings of one health check that Auto Fix could not answer.
- For each finding: a
codefinding goes to the host runner — Plan (Claude Code, read-only: root cause + an immediate fix and an isolate/mitigate alternative), then if the plan is safe now Branch (ahealth/…worktree offorigin/main) → Fix → the repo's own checks → Commit and push the branch → a draft pull request againstmain; a plan that is not safe now becomes a GitHub issue listing the options. Every other kind (connection, external, design, unknown) is handed to a person. - Collect outcomes gathers one result per finding and Report back posts them to
POST /workflows/{id}/health/{hid}/complete; the check then readscode_fix_open,fixedorneeds_user_input. - Nothing here merges, publishes, or pushes to
main.
Setup
- Tenant variables:
HEALTH_RUNNER_URL(the host runner, e.g.http://host.docker.internal:8787),HEALTH_RUNNER_TOKEN(secret, its bearer token),MANTLE_API_URL(this API as reached from inside the deployment) andMANTLE_API_KEY(secret; an API key with workflows:read/write/run and runs:read). - Tag the installed workflow
system:health(Settings → Tags) so health never checks it, publish it, and point Settings → Workflow Health at it. workflows/build-workflow-health.pyin the workspace does all of the above for the local tenant.
Nodes 17
- 01Health findings receivedtrigger.workflowTrigger
- 02For each findingcontrol.for_eachLogic
- 03Which kind of problem?control.noopLogic
- 04Plan the fix (Claude Code, read-only)http.requestHTTP
- 05Branch off main (worktree)http.requestHTTP
- 06Fix on the branch (Claude Code, 5–13 min)http.requestHTTP
- 07Run the repo's checkshttp.requestHTTP
- 08Commit and push the branchhttp.requestHTTP
- 09Open the pull requesthttp.requestHTTP
- 10Outcome: pull request opendata.setData
- 11Outcome: fix failed validationdata.setData
- 12No immediate fix: file the plans as an issuehttp.requestHTTP
- 13Outcome: issue open with the optionsdata.setData
- 14Outcome: needs a persondata.setData
- 15Collect outcomescontrol.gatherLogic
- 16Report back to Workflow Healthhttp.requestHTTP
- 17Donecontrol.stopLogic
View template JSON
{
"format": "mantle-workflow/v1",
"id": "system-workflow-health",
"name": "workflow_health (system)",
"graph": {
"nodes": [
{
"id": "receive",
"type": "trigger.workflow",
"label": "Health findings received",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 60,
"y": 300
},
"config": {}
},
{
"id": "each",
"type": "control.for_each",
"label": "For each finding",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 340,
"y": 300
},
"config": {
"items": "findings",
"item_as": "finding",
"parallel": false,
"on_error": "continue",
"empty_collection": "skip",
"max_items": 20
}
},
{
"id": "route",
"type": "control.noop",
"label": "Which kind of problem?",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 620,
"y": 300
},
"config": {}
},
{
"id": "plan",
"type": "http.request",
"label": "Plan the fix (Claude Code, read-only)",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 900,
"y": 120
},
"config": {
"method": "POST",
"url": "{{ $vars.HEALTH_RUNNER_URL }}/remediate/plan",
"headers": {
"Authorization": "Bearer {{ $vars.HEALTH_RUNNER_TOKEN }}",
"Accept": "application/json"
},
"timeout_seconds": 600,
"body_from_input": true
},
"retry": {
"max_attempts": 4,
"backoff": "exponential",
"initial_ms": 15000,
"max_ms": 120000
}
},
{
"id": "branch_repo",
"type": "http.request",
"label": "Branch off main (worktree)",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 1180,
"y": 60
},
"config": {
"method": "POST",
"url": "{{ $vars.HEALTH_RUNNER_URL }}/remediate/branch",
"headers": {
"Authorization": "Bearer {{ $vars.HEALTH_RUNNER_TOKEN }}",
"Accept": "application/json"
},
"timeout_seconds": 120,
"json": {
"repo": "steps.plan.body.repo",
"health_id": "steps.plan.body.health_id",
"finding_id": "steps.plan.body.finding_id",
"plan": "steps.plan.body"
}
},
"retry": {
"max_attempts": 4,
"backoff": "exponential",
"initial_ms": 15000,
"max_ms": 120000
}
},
{
"id": "fix",
"type": "http.request",
"label": "Fix on the branch (Claude Code, 5–13 min)",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 1460,
"y": 60
},
"config": {
"method": "POST",
"url": "{{ $vars.HEALTH_RUNNER_URL }}/remediate/fix",
"headers": {
"Authorization": "Bearer {{ $vars.HEALTH_RUNNER_TOKEN }}",
"Accept": "application/json"
},
"timeout_seconds": 840,
"json": {
"repo": "steps.plan.body.repo",
"health_id": "steps.plan.body.health_id",
"finding_id": "steps.plan.body.finding_id",
"branch": "steps.branch_repo.body.branch",
"plan": "steps.plan.body"
}
},
"retry": {
"max_attempts": 4,
"backoff": "exponential",
"initial_ms": 15000,
"max_ms": 120000
}
},
{
"id": "validate",
"type": "http.request",
"label": "Run the repo's checks",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 1740,
"y": 60
},
"config": {
"method": "POST",
"url": "{{ $vars.HEALTH_RUNNER_URL }}/remediate/validate",
"headers": {
"Authorization": "Bearer {{ $vars.HEALTH_RUNNER_TOKEN }}",
"Accept": "application/json"
},
"timeout_seconds": 900,
"json": {
"repo": "steps.plan.body.repo",
"health_id": "steps.plan.body.health_id",
"finding_id": "steps.plan.body.finding_id",
"branch": "steps.branch_repo.body.branch"
}
},
"retry": {
"max_attempts": 4,
"backoff": "exponential",
"initial_ms": 15000,
"max_ms": 120000
}
},
{
"id": "commit",
"type": "http.request",
"label": "Commit and push the branch",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 2020,
"y": 20
},
"config": {
"method": "POST",
"url": "{{ $vars.HEALTH_RUNNER_URL }}/remediate/commit",
"headers": {
"Authorization": "Bearer {{ $vars.HEALTH_RUNNER_TOKEN }}",
"Accept": "application/json"
},
"timeout_seconds": 180,
"json": {
"repo": "steps.plan.body.repo",
"health_id": "steps.plan.body.health_id",
"finding_id": "steps.plan.body.finding_id",
"branch": "steps.branch_repo.body.branch",
"plan": "steps.plan.body"
}
},
"retry": {
"max_attempts": 4,
"backoff": "exponential",
"initial_ms": 15000,
"max_ms": 120000
}
},
{
"id": "pr",
"type": "http.request",
"label": "Open the pull request",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 2300,
"y": 20
},
"config": {
"method": "POST",
"url": "{{ $vars.HEALTH_RUNNER_URL }}/remediate/pr",
"headers": {
"Authorization": "Bearer {{ $vars.HEALTH_RUNNER_TOKEN }}",
"Accept": "application/json"
},
"timeout_seconds": 120,
"json": {
"repo": "steps.plan.body.repo",
"health_id": "steps.plan.body.health_id",
"finding_id": "steps.plan.body.finding_id",
"branch": "steps.branch_repo.body.branch",
"plan": "steps.plan.body"
}
},
"retry": {
"max_attempts": 4,
"backoff": "exponential",
"initial_ms": 15000,
"max_ms": 120000
}
},
{
"id": "code_done",
"type": "data.set",
"label": "Outcome: pull request open",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 2580,
"y": 20
},
"config": {
"values": {
"finding_id": "{{ $steps.plan.body.finding_id }}",
"status": "pr_open",
"kind": "code_fix",
"repo": "{{ $steps.plan.body.repo }}",
"branch": "{{ $steps.branch_repo.body.branch }}",
"commit": "{{ $steps.commit.body.commit }}",
"pr_url": "{{ $steps.pr.body.url }}",
"pr_number": "{{ $steps.pr.body.number }}",
"chosen_plan_id": "{{ $steps.plan.body.recommended }}",
"detail": "{{ $steps.pr.body.summary }}"
},
"include_input": false,
"types": {
"pr_number": "number"
}
}
},
{
"id": "code_blocked",
"type": "data.set",
"label": "Outcome: fix failed validation",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 2020,
"y": 200
},
"config": {
"values": {
"finding_id": "{{ $steps.plan.body.finding_id }}",
"status": "failed",
"kind": "code_fix",
"repo": "{{ $steps.plan.body.repo }}",
"branch": "{{ $steps.branch_repo.body.branch }}",
"detail": "{{ $steps.validate.body.summary }}"
},
"include_input": false
}
},
{
"id": "code_issue",
"type": "http.request",
"label": "No immediate fix: file the plans as an issue",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 1180,
"y": 240
},
"config": {
"method": "POST",
"url": "{{ $vars.HEALTH_RUNNER_URL }}/remediate/issue",
"headers": {
"Authorization": "Bearer {{ $vars.HEALTH_RUNNER_TOKEN }}",
"Accept": "application/json"
},
"timeout_seconds": 120,
"json": {
"repo": "steps.plan.body.repo",
"health_id": "steps.plan.body.health_id",
"finding_id": "steps.plan.body.finding_id",
"plan": "steps.plan.body"
}
},
"retry": {
"max_attempts": 4,
"backoff": "exponential",
"initial_ms": 15000,
"max_ms": 120000
}
},
{
"id": "code_issue_done",
"type": "data.set",
"label": "Outcome: issue open with the options",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 1460,
"y": 240
},
"config": {
"values": {
"finding_id": "{{ $steps.plan.body.finding_id }}",
"status": "issue_open",
"kind": "code_fix",
"repo": "{{ $steps.plan.body.repo }}",
"issue_url": "{{ $steps.code_issue.body.url }}",
"detail": "{{ $steps.code_issue.body.summary }}"
},
"include_input": false
}
},
{
"id": "other",
"type": "data.set",
"label": "Outcome: needs a person",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 900,
"y": 460
},
"config": {
"values": {
"finding_id": "{{ $json.id }}",
"status": "needs_user_input",
"kind": "notified",
"detail": "{{ $json.classification.reason }}"
},
"include_input": false
}
},
{
"id": "gather",
"type": "control.gather",
"label": "Collect outcomes",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 2860,
"y": 300
},
"config": {
"mode": "loop"
}
},
{
"id": "complete",
"type": "http.request",
"label": "Report back to Workflow Health",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 3140,
"y": 300
},
"config": {
"method": "POST",
"url": "{{ $vars.MANTLE_API_URL }}/workflows/{{ $steps.receive.workflow.id }}/health/{{ $steps.receive.health_id }}/complete",
"headers": {
"Authorization": "Bearer {{ $vars.MANTLE_API_KEY }}",
"Accept": "application/json"
},
"json": {
"outcomes": "input.items",
"delegate_run_id": "{{ $run.id }}"
},
"timeout_seconds": 30
},
"retry": {
"max_attempts": 3,
"backoff": "fixed",
"initial_ms": 5000,
"max_ms": 5000
}
},
{
"id": "done",
"type": "control.stop",
"label": "Done",
"worker_type": "generic-worker",
"connection_id": null,
"position": {
"x": 3420,
"y": 300
},
"config": {
"status": "succeed",
"message": "Health findings handled"
}
}
],
"edges": [
{
"from": "receive",
"to": "each"
},
{
"from": "each",
"to": "route"
},
{
"from": "route",
"to": "plan",
"condition": "kind == 'code'"
},
{
"from": "route",
"to": "other",
"condition": "kind != 'code'"
},
{
"from": "plan",
"to": "branch_repo",
"condition": "body.immediate == true"
},
{
"from": "plan",
"to": "code_issue",
"condition": "body.immediate != true"
},
{
"from": "branch_repo",
"to": "fix"
},
{
"from": "fix",
"to": "validate"
},
{
"from": "validate",
"to": "commit",
"condition": "body.ok == true"
},
{
"from": "validate",
"to": "code_blocked",
"condition": "body.ok != true"
},
{
"from": "commit",
"to": "pr"
},
{
"from": "pr",
"to": "code_done"
},
{
"from": "code_issue",
"to": "code_issue_done"
},
{
"from": "code_done",
"to": "gather"
},
{
"from": "code_blocked",
"to": "gather"
},
{
"from": "code_issue_done",
"to": "gather"
},
{
"from": "other",
"to": "gather"
},
{
"from": "gather",
"to": "complete"
},
{
"from": "complete",
"to": "done"
}
]
}
}Use it, then make it yours.
Templates install as drafts. Change anything, and Workflow Health re-checks the graph on every save.