Suspendable Nodes
NodeTool has no suspend/resume API. There is no SuspendableState, no
SuspendableNode interface, no suspendWorkflow(), and no
WorkflowSuspendedError. RunResult.status (packages/kernel/src/runner.ts)
is "completed" | "failed" | "cancelled", and JobStatus
(packages/protocol/src/messages.ts) adds only "pending", "running", and
"error" — none of them is "suspended". A node cannot pause a run and be
resumed later.
packages/models/src/run-event.ts does declare RunSuspended, NodeSuspended,
and NodeResumed event types, but nothing in the runtime emits them.
For a run that needs to wait on something external, the shapes that do exist
are trigger nodes (packages/kernel/src/trigger-wakeup.ts delivers an event to
a registered trigger and starts a run), the webhook route
(POST /api/webhooks/:token), and the interactive escalation path — a run
started with interactive: true parks on a failing node and waits for the
caller’s verdict (see Workflow Supervisor).
The Built-in WaitNode (a delay, not a suspension)
Important:
WaitNode(nodetool.triggers.Wait, inpackages/automation-nodes/src/nodes/triggers.ts) does not suspend anything. It is a simple in-process delay:process()sleeps fortimeout_secondsviasetTimeout, then passes its input through. The run stays alive and in memory for the whole delay. Use it for rate-limiting and fixed delays.
import { WaitNode } from "@nodetool-ai/automation-nodes";
// A node that delays for a fixed number of seconds, then forwards `input`.
const waitNode = new WaitNode();
waitNode.timeout_seconds = 5; // Seconds to wait (0 = no wait, pass through immediately)
waitNode.input = { request_id: "REQ-123" };
WaitNode Properties
| Property | Type | Default | Description |
|---|---|---|---|
timeout_seconds |
int (minimum 0) |
0 |
Seconds to wait before continuing. 0 = no wait (pass through immediately). |
input |
any |
"" |
Input data passed through to the output after the delay |
WaitNode Output
After the delay, the WaitNode outputs:
{
data: { /* input data passed through */ },
resumed_at: "2026-03-16T12:00:00.000Z", // ISO timestamp after the delay
waited_seconds: 5.0 // Actual seconds slept
}
The output field names (
resumed_at,waited_seconds) read like suspension semantics, but they only reflect thesetTimeoutdelay — nothing was suspended.
Writing a trigger node
A trigger node is how a node “waits” for an outside event. Set static readonly isTrigger = true on the class. When a run starts because an event arrived (the run request carries a trigger_event that targets the node), the kernel calls emitTriggerEvent(event, outputs) instead of the live-listening genProcess() loop. Without a trigger event, the node keeps its streaming behavior, which is what an in-editor live test uses.
import type { StreamingOutputs, TriggerEvent } from "@nodetool-ai/node-sdk";
async emitTriggerEvent(event: TriggerEvent, outputs: StreamingOutputs): Promise<void> {
await outputs.emit("data", event.payload);
}
TriggerEvent has node_id, payload, and input_id (the idempotency key of the stored trigger input). The default implementation on BaseNode copies each key of an object payload to the declared output of the same name and drops other keys. Override it to shape adapter payloads onto your outputs. nodetool.triggers.ManualTrigger in packages/automation-nodes/src/nodes/triggers.ts is a complete example. To fire a trigger from the CLI, pass --trigger-event to nodetool workflows run (see the CLI reference).
See Also
- Triggers design - How triggers register, fire, and start runs
- Workflow API - API endpoints for workflow control