What it does: an alert arrives, an agent() step classifies its severity,
and critical alerts fan out to parallel notifications and then park on a form until a human
acknowledges — while low-severity alerts auto-resolve.
When to use it: on-call triage, alert routing, anything where a judgement call decides whether to wake a human.
Requires Claude Code channels
This recipe uses an agent() step, which is answered by the driving Claude Code
session. Set up channels and start the run with
—channel <id>, or the run will park at WAITING_AGENT with no one
to answer.
Flow
receive ─▶ [agent: triage → {severity, summary}] ─▶ route
├─ critical/high ─▶ notify (parallel: slack + email) ─▶ await_ack (form) ─▶ resolve
└─ else ──────────▶ auto_resolve ─▶ resolve
The workflow
// Incident escalation — triage an alert, escalate the serious ones to a human.
//
// An alert comes in; an agent() classifies its severity; criticals fan out to
// parallel notifications and then wait for a human to acknowledge, while low-severity
// alerts are auto-resolved. Demonstrates agent(), branches, parallel, and a form.
//
// REQUIRES Claude Code channels: the agent() step is answered by the driving Claude
// session, so start the run from Claude Code with --channel <id>. See docs → channels.
//
// Connections used: slack (generic-http or MCP), composio_gmail.
//
// flowctl def create --file examples/workflows/incident-escalation.js
// flowctl run start --workflow IncidentEscalation --channel <id> \
// --input '{"alert_id":"A-12","text":"DB connections exhausted"}' -q
function receive(ctx) {
return { received: true };
}
function on_triage(ctx, data) {
return { severity: data.result.severity, summary: data.result.summary };
}
function route(ctx) { return {}; }
function notify_slack_req(ctx) {
return { body: { channel: "#incidents", text: "[" + ctx.vars.severity + "] " + ctx.vars.summary } };
}
function notify_slack_resp(ctx, response) { return { slack_ok: response.status_code < 300 }; }
function notify_email_req(ctx) {
return { recipient_email: "oncall@example.com", subject: "Incident " + ctx.vars.alert_id, body: ctx.vars.summary };
}
function notify_email_resp(ctx, response) { return { email_ok: response.successful === true }; }
function fanout(ctx) { return {}; }
function on_ack(ctx, data) {
return { acknowledged: true, ack_by: data.ack_by || "unknown" };
}
function auto_resolve(ctx) { return { resolution: "auto-resolved (low severity)" }; }
function resolve(ctx) { return { status: "resolved" }; }
workflow({
name: "IncidentEscalation",
version: "1",
description: "Triage an alert and escalate criticals to a human.",
initial: { alert_id: null, text: "", severity: null, summary: null, acknowledged: false },
triggers: [apiTrigger("alert"), webhookTrigger("alert_hook")],
steps: [
step("receive", { start: true, next: "triage", label: "Receive alert" }, receive),
agent("triage", {
prompt: "Classify this alert and summarize it in one line. Alert: {{text}}",
resultType: "json",
context: ["text", "alert_id"],
resultSchema: {
type: "object",
required: ["severity", "summary"],
properties: {
severity: { type: "string", description: "one of: critical, high, low" },
summary: { type: "string" },
},
},
next: "route",
}, on_triage),
step("route", {
branches: [
branch("severity == 'critical'", "notify"),
branch("severity == 'high'", "notify"),
branch("true", "auto_resolve"),
],
}, route),
step("notify", {
parallel: { steps: ["notify_slack", "notify_email"], next: "await_ack" },
}, fanout),
connector("notify_slack", {
connection: "slack",
operation: "post_message",
instance: "default",
requestMapper: notify_slack_req,
responseCombiner: notify_slack_resp,
}),
connector("notify_email", {
connection: "composio_gmail",
operation: "send_email",
instance: "my-gmail",
requestMapper: notify_email_req,
responseCombiner: notify_email_resp,
}),
form("await_ack", {
title: "Acknowledge incident {{alert_id}}",
schema: {
type: "object",
properties: { ack_by: { type: "string", description: "Who is taking this?" } },
required: ["ack_by"],
},
next: "resolve",
}, on_ack),
step("auto_resolve", { next: "resolve", label: "Auto-resolve" }, auto_resolve),
step("resolve", { next: "end", label: "Resolve" }, resolve),
],
outcomeTemplates: {
completed: "Incident {{alert_id}} ({{severity}}) — {{status}}.",
},
result: (vars) => ({ alert_id: vars.alert_id, severity: vars.severity, acknowledged: vars.acknowledged }),
});
Run it
flowctl def create --file examples/workflows/incident-escalation.js
flowctl run start --workflow IncidentEscalation --channel <id> \
--input '{"alert_id":"A-12","text":"DB connections exhausted"}' -q
# critical alerts park on the ack form:
flowctl form submit <exec> await_ack --data '{"ack_by":"jane"}'
Variations
- More channels. Add a PagerDuty or SMS connector to the
notifyparallel block. - Escalation timer. Loop the ack form back through a branch to re-notify if no one acknowledges.
- Skip the human. For lower tiers, route straight to
auto_resolveand just record the event.