Skip to Content

@loopstack/hitl-examples

Human-in-the-Loop workflow examples for the Loopstack  automation framework.

Side-by-side examples of every way to pause a Loopstack workflow for the user. The point of this package is to answer one question: when do I design a custom document with a widget, vs. delegate to a HITL sub-workflow, vs. let an LLM agent ask the user?

Decision matrix

You are building…UseExamples
A predefined workflow with known user-input steps — form fields, structured data, a record the user editsCustom document with a widget — your workflow owns the document and the wait: true transitionInline Form, Prompt Input Chat, Meeting Notes
An LLM agent loop where the LLM dynamically decides to ask the user something or request approvalAgent tools ask_clarification / ask_for_approval from @loopstack/hitlAgent Ask Clarification, Agent Ask For Approval
A predefined workflow that just needs a quick generic ask — one free-text field, yes/no, pick-one — and you don’t want to design a formSub-workflow shortcut — AskUserWorkflow / ConfirmUserWorkflow from @loopstack/hitlAsk User Text, Ask User Options, Ask User Confirm, Confirm Content
npx giget@latest gh:loopstack-ai/loopstack/registry/examples/hitl-examples src/hitl-examples

Then register the module in your app:

import { Module } from '@nestjs/common'; import { LoopstackModule } from '@loopstack/loopstack-module'; import { HitlExamplesModule } from './hitl-examples/hitl-examples.module'; @Module({ imports: [LoopstackModule.forRoot(), HitlExamplesModule], }) export class AppModule {}

Install as a Dependency

npm install @loopstack/hitl-examples
import { HitlExamplesModule } from '@loopstack/hitl-examples';

Required app-module configuration

HitlExamplesModule brings its own HitlModule, AgentModule, and ClaudeModule. The Meeting Notes and Agent examples additionally call LlmGenerateObjectTool / LlmGenerateTextTool from @loopstack/llm-provider-module. That module is @Global and must be configured once in your root module to set the default model:

import { Module } from '@nestjs/common'; import { HitlExamplesModule } from '@loopstack/hitl-examples'; import { LlmProviderModule } from '@loopstack/llm-provider-module'; import { LoopstackModule } from '@loopstack/loopstack-module'; @Module({ imports: [LoopstackModule.forRoot(), LlmProviderModule.forRoot({ model: 'claude-sonnet-4-6' }), HitlExamplesModule], }) export class AppModule {}

ClaudeModule (re-exported from HitlExamplesModule) registers the Claude provider with the LLM registry; LlmProviderModule.forRoot(...) sets the default model the tools dispatch to. The non-agent examples (Inline Form, Prompt Input Chat, Ask User *, Confirm Content) work without it.

Environment

The agent and meeting-notes examples require Claude credentials:

ANTHROPIC_API_KEY=sk-ant-...

The non-agent examples (Inline Form, Prompt Input Chat, Ask User *, Confirm Content) work without any LLM provider.

Examples

ExampleStudio titleDescription
Inline FormHITL - Inline Form ExampleCustom Document with a form widget, wait: true payload typed by the document schema
Prompt Input ChatHITL - Prompt Input Chat ExampleWorkflow-level prompt-input widget; loops on user messages without an LLM
Ask User TextHITL - Ask User Text ExampleAskUserWorkflow for a generic free-text question
Ask User OptionsHITL - Ask User Options ExampleAskUserWorkflow with mode: 'options' and allowCustomAnswer
Ask User ConfirmHITL - Ask User Confirm ExampleAskUserWorkflow with mode: 'confirm' for yes/no decisions
Confirm ContentHITL - Confirm Content ExampleConfirmUserWorkflow to show a pre-rendered markdown blob for explicit approval
Agent Ask ClarificationHITL - Agent Ask Clarification ExampleLLM agent with ask_clarification tool — pauses to request missing info
Agent Ask For ApprovalHITL - Agent Ask For Approval ExampleLLM agent with ask_for_approval tool — drafts content and pauses for confirm/deny
Meeting NotesHITL - Meeting Notes ExampleEnd-to-end document-review flow: user notes → LLM-extracted structure → user approval

Inline Form

The workflow owns a FeedbackFormDocument whose schema doubles as the wait: true transition payload. The form’s Submit button triggers the transition with typed data — no wrapping or unpacking.

Files

  • inline-form-example.workflow.ts — workflow class
  • feedback-form-document.ts — @Document with Zod schema
  • feedback-form-document.yaml — form widget definition

Prompt Input Chat

Workflow-level widget: prompt-input with enabledWhen: [waiting_for_user]. Loops on user messages and echoes them back — no LLM and no sub-workflow involved. Demonstrates the chat-style entry pattern that LLM agent loops are built on.

Files

  • prompt-input-chat-example.workflow.ts — workflow class
  • prompt-input-chat-example.workflow.yaml — workflow-level prompt-input widget

Ask User Text

Delegate a generic free-text question to AskUserWorkflow. The parent workflow only owns the callback transition that receives the answer. Use the inline-form pattern instead when you need a structured form.

Files

  • ask-user-text-example.workflow.ts — workflow class

Ask User Options

Delegate a “pick one from a list” question to AskUserWorkflow with mode: 'options'. allowCustomAnswer: true adds a free-text field next to the choices.

Files

  • ask-user-options-example.workflow.ts — workflow class

Ask User Confirm

Delegate a yes/no decision to AskUserWorkflow with mode: 'confirm'. The answer comes back as the literal string 'yes' or 'no'.

Files

  • ask-user-confirm-example.workflow.ts — workflow class

Confirm Content

Hand a pre-rendered markdown blob to ConfirmUserWorkflow for a user-facing review. The callback receives { confirmed: boolean, markdown: string }. Useful for showing a release plan, summary, or diff for explicit approval.

Files

  • confirm-content-example.workflow.ts — workflow class

Agent Ask Clarification

Launches AgentWorkflow with tools: ['ask_clarification']. When the LLM lacks information, it issues an ask_clarification tool call — the loop pauses and prompts the user, then the answer flows back into the agent context.

Files

  • agent-ask-clarification-example.workflow.ts — workflow class

Agent Ask For Approval

Launches AgentWorkflow with tools: ['ask_for_approval']. The LLM drafts content (release notes, in this example), then calls ask_for_approval with the markdown — the loop pauses until the user explicitly confirms or denies.

Files

  • agent-ask-for-approval-example.workflow.ts — workflow class

Meeting Notes

A complete HITL document-review flow:

  1. Save a MeetingNotesDocument with the user’s raw notes (form widget for edits)
  2. User reviews/edits and submits via wait: true transition
  3. LlmGenerateObjectTool extracts structured data via Zod schema
  4. Save OptimizedNotesDocument for the user to review/approve
  5. Final approval via second wait: true transition

Combines custom documents, LLM-driven structured output, and two HITL review steps.

Files

  • meeting-notes-example.workflow.ts — workflow class
  • documents/meeting-notes-document.{ts,yaml} — input form document
  • documents/optimized-notes-document.{ts,yaml} — structured output document
  • templates/extract-notes.md — LLM prompt template

Testing

Every workflow ships with an inner-loop spec in its __tests__/ folder — run them with npm run test (in-process, no backend, no API key). The specs double as the reference for how to test each HITL flavor, extending the decision matrix above:

HITL flavorHow its tests workSpec
Custom document + own wait: true transitionScript the submission with answers, keyed by the wait-transition method nameinline-form
Sub-workflow shortcut (AskUserWorkflow / ConfirmUserWorkflow)Script the child’s wait transition (userAnswered, userConfirmed / userDenied) — answers reaches inline sub-workflows at any depthask-user-*, confirm-content
Chat loop (same wait transition parks repeatedly)queue('first', 'second') submits one message per park, then the run parks again; scripted LLM replies via replayprompt-input-chat
LLM step inside a predefined workflowreplay scripts the LLM response; answers drives the surrounding HITL stepsmeeting-notes
Agent HITL (ask_clarification / ask_for_approval)replayTools: [LlmGenerateTextTool] scripts only the LLM turns; the agent loop, delegation, and the real HITL child run live; answers resumes the nested childagent-ask-clarification, agent-ask-for-approval

The record/refresh loop. The replay specs here use hand-written LLM turns for readability; for your own workflows, record fixtures from a real run instead: run the test once with fixture: '<path>' and live providers (records on the first run, replays afterwards — delete the file to re-record), or derive one from a backend run started with loopstack run --trace via loopstack runs <run-id> --record <file> --tools llm_generate_text. When a replayed test fails with a drift error after a prompt change, that is the signal: run the live check-ups, and re-record when green.

Live check-ups (*.live.spec.ts) run the same flows against the real model with structural assertions — npm run test:live, needs ANTHROPIC_API_KEY, on demand after prompt or model changes, never in the PR gate.

Outer loop. Before relying on a flow, run the real thing once from the terminal and watch it: loopstack run <workflow_name> follows the run live and prompts you at every HITL step; loopstack runs shows the inbox of runs waiting for input.

See the testing guide for the full story.

About

Author: Jakob Klippel 

License: MIT

Last updated on