Skip to content

humanNode & resumeFlow

Insert a human-in-the-loop pause point into a flow. When the step executes it throws an InterruptError carrying a deep snapshot of the current state. A resumeFlow helper makes resuming ergonomic.

Setup

typescript
import { FlowBuilder } from "flowneer";
import { withHumanNode } from "flowneer/plugins/agent";

const AppFlow = FlowBuilder.extend([withHumanNode]);

Usage

typescript
import { InterruptError } from "flowneer";
import { withHumanNode, resumeFlow } from "flowneer/plugins/agent";

interface State {
  draft: string;
  approved: boolean;
  feedback: string;
  __humanPrompt?: string;
}

const flow = new AppFlow<State>()
  .startWith(async (s) => {
    s.draft = await generateDraft(s.topic);
  })
  .humanNode({
    prompt: "Please review the draft and provide feedback.",
  })
  .then(async (s) => {
    if (!s.approved) {
      s.draft = await reviseDraft(s.draft, s.feedback);
    }
  });

// --- Runner ---
const shared: State = { draft: "", approved: false, feedback: "" };

try {
  await flow.run(shared);
} catch (e) {
  if (e instanceof InterruptError) {
    const saved = e.savedShared as State;
    console.log("Prompt:", saved.__humanPrompt);
    console.log("Draft:", saved.draft);

    // Get human input...
    const feedback = await getHumanInput();
    const approved = await getApproval();

    // Resume from step 2 (skip steps 0 and 1 which already ran)
    await resumeFlow(flow, saved, { feedback, approved }, 2);
  }
}

.humanNode(options?)

OptionTypeDefaultDescription
promptKeystring"__humanPrompt"Key on shared where the prompt is stored
promptstring | (s, p) => string | Promise<string>Prompt message to store before interrupting
condition(s, p) => boolean | Promise<boolean>Always interruptOnly interrupt when condition returns true

resumeFlow(flow, saved, edits?, fromStep?)

Helper that merges edits into savedShared, optionally applies withReplay(fromStep) to skip already-completed steps, and calls flow.run(merged).

typescript
import { resumeFlow } from "flowneer/plugins/agent";

await resumeFlow(
  flow,
  e.savedShared as State,
  { feedback: "Looks good", approved: true },
  resumeFromStep, // skip steps 0..resumeFromStep-1
);
ParameterTypeDescription
flowFlowBuilderThe same instance that was interrupted
savedSe.savedShared from the InterruptError
editsPartial<S>Human input / corrections to merge
fromStepnumberIf provided, skips steps 0..fromStep-1 via withReplay

Conditional Interrupts

Only pause when the draft needs review:

typescript
.humanNode({
  prompt: (s) => `Review this draft:\n\n${s.draft}`,
  condition: (s) => s.draft.includes("TODO") || s.confidence < 0.8,
})

See Also