An alarm pauses the job, not the escalation
A 3D printer on the lineStep 6 of 7Unloaded, or a decision for a person
27% into the boat, the spool runs dry. The printer stops and waits.
← Milestones on the escalationRehearse with a simulated machine →
A filament runout opens a servicing escalation and notes the pause on the print. Resuming at the printer closes the alarm, and the print escalation resolves at the finish.
The situation
A printer that runs out of filament is paused, not failed. Someone has to load a spool and press resume, and the print should carry on as the same job. A printer that refuses a print, or stops for good, needs a decision instead. Built by hand, both end up as a failed job and a retry. Here the two read differently.
The setup
The printer pauses with print_error 134184977. The codec reads it as an alarm:
{ "alarmId": "print-error", "level": "critical", "code": "07FF-8011", "text": "The external spool ran out of filament. Load filament and resume the print.", "active": true, "jobId": "Pmw8druzy-p1s-01", "raisedAt": "2026-11-08T22:05:25.810Z", "clearedAt": null}// gateway: a line-alarm escalation on print-servicing,// paused:print-error:2026-11-08T22:05:25.810Z on the print escalationOn the floor
A line-alarm escalation goes to print-servicing: “The external spool ran out of filament. Load filament and resume the print.” The print escalation gains paused:print-error:<at> and stays open. Someone resumes at the printer, print_error returns to 0, the alarm escalation resolves itself, and the print escalation gains resumed:print-error:<at>.
A refused print, or one that stops unfinished, resolves the hop with an errorCode, and the route asks the line’s operator role:
/** Ask until the operator picks one of the decisions this failure allows. */async function askOperator(input: UnitRouteInput, hop: Hop, attempt: number, senderAttempt: number, outcome: HopOutcome): Promise<OperatorResolution> { const where = stationName(hop.from, 'line entry'); const decisions = handoffDecisions(outcome.errorCode); let refused = ''; for (let round = 1; ; round++) { const answer = await conditional<OperatorResolution>(signalKeys.handoffError(input.routeKey, hop.index, attempt, round), { role: input.operatorRole ?? LINE_ROLE.OPERATOR, type: ROW_TYPE.HANDOFF_ERROR, subtype: outcome.errorCode, description: `${refused}${input.units.map((u) => u.unitId).join(', ')} stopped at ${where}: ${outcome.errorCode}. ${outcome.detail ?? ''} Choose ${decisions.join(', ')}.`.replace(/\s+/g, ' ').trim(), priority: 1, workflowType: UNIT_ROUTE_WORKFLOW, metadata: { ...hopFacets(input, hop, attempt, senderAttempt), errorCode: outcome.errorCode, detail: outcome.detail ?? null, decisions, round, } as Record<string, unknown>, timeout: input.operatorTimeout ?? OPERATOR_TIMEOUT, }); if (!answer) return { decision: DECISION.SCRAP, note: 'no decision before the timeout' }; if (decisions.includes(answer.decision)) return answer; refused = `${answer.decision} is not available here. `; }}{ "title": "Line decision", "description": "A handoff that did not complete: retry it, finish the units by hand, or scrap them. Units a machine finished as not ok are off the line: finish them by hand or scrap them. An alarm or a machine that went dark: acknowledge it once handled.", "x-lt-order": [ "decision", "note" ], "required": [ "decision" ], "properties": { "decision": { "type": "string", "enum": [ "retry", "manual", "scrap", "acknowledge", "cleared", "restored", "approve", "reject", "release", "rework" ], "default": "acknowledge", "description": "One of the row's decisions for a failed handoff; acknowledge for an alarm or a machine" }, "note": { "type": "string", "format": "textarea", "default": "", "description": "What was done" }, "clearedBy": { "type": "string", "default": "", "description": "Who cleared it at the machine, when the machine reports it" } }}Choose retry, manual or scrap for the refused print.
Submit the form to see what the workflow resumes with.
The guarantee
A pause never resolves the print escalation, and the alarm closes when the printer clears it. A failure always reaches a person on the line’s operator role, who chooses from the decisions that fit it. See Machines.
Related patterns
The physical world
Offline, quiet and alarm escalations
Step 12 of 12 · A factory line over MQTT
A stopping alarm, a dropped machine and a silent one each become an escalation for the line operator, resolved when the machine recovers.
The escalation boundary
Errors become escalations
Step 10 of 12 · A factory line over MQTT
When a step fails for good, the failure becomes an escalation an operator can act on. The run waits for the decision instead of disappearing.
The escalation boundary
Born-assigned hand-off
Step 11 of 12 · A factory line over MQTT
The next escalation is created already assigned to a named person, in the same write that creates it. It lands in their list with no claim step.
