Skip to content

InterruptNode

Bypass-Fathom HITL pause primitive (FR-82, AC-14.2, design §9.2). On dispatch the node raises the loop's typed control-flow signal carrying an InterruptAction payload; the loop catches the signal, transitions state="awaiting-input", emits a WaitingForInputEvent, persists a checkpoint, and exits cleanly. Resume happens via cold-restart through GraphRun.resume after GraphRun.respond flips state back to "running" (design §9.4).

Interrupt is control-flow, not routing

Per design §17 Decision #1 (locked), interrupt is a control-flow primitive, not a routing decision: the node deliberately does not return an InterruptAction-shaped patch via the field-merge surface. The dispatch path is the typed signal raise — RoutingDecision never carries an interrupt variant.

InterruptNodeConfig

Pydantic config (subclasses IRBase, so extra="forbid"). Fields mirror stargraph.ir._models.InterruptAction verbatim.

Field Type Default Description
prompt str required Analyst-facing prompt surfaced via WS / GET /runs/{id}.
interrupt_payload dict[str, Any] {} Opaque blob exposed via WS / respond endpoint.
requested_capability str \| None None Capability the human responder must hold; gate enforced at respond-time, not here.
timeout timedelta \| None None Wait bound; None = wait indefinitely (FR-87, NFR-22).
on_timeout Literal["halt"] \| str "halt" Loop policy on timeout: "halt" (terminal) or "goto:<node_id>" (resume target).

Constructor

InterruptNode(*, config: InterruptNodeConfig)

Single keyword-only arg; the config is attached at construction time — same convention WriteArtifactNode and BrokerNode established.

State contract

  • Reads — none; the signal is raise-only and the loop owns state.
  • Writes — none from this node. The actual analyst response is asserted as a stargraph.evidence Fathom fact by GraphRun.respond post-resume.

Side effects + replay

  • side_effects = SideEffects.read — no Stargraph-side mutation; only requests human input.
  • Replay re-executes the raise; the loop's _HitInterrupt handler is deterministic.

YAML

nodes:
  - id: human_gate
    kind: interrupt
rules:
  - id: r-gate-to-branch
    when: "?n <- (node-id (id human_gate))"
    then:
      - kind: interrupt
        prompt: "Approve remediation for {record_id}?"
        interrupt_payload:
          requested_capability: "runs:respond"
        requested_capability: "runs:respond"
        timeout: "PT5M"
        on_timeout: "halt"

The kind: interrupt rule action mirrors InterruptNodeConfig field-by-field. See tests/fixtures/triage_stub_broker.yaml for the full HITL pipeline.

Errors

  • _HitInterrupt (private to stargraph.graph.loop) is always raised — this is the dispatch contract, not an error condition.

See also

  • NodeBase — abstract contract.
  • stargraph.graph.run.GraphRun.respond / GraphRun.resume — the resume path.
  • Engine: checkpointer — durability for HITL waits.