Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.sirenspec.dev/llms.txt

Use this file to discover all available pages before exploring further.

Overview

A human node pauses execution to collect a response from a human operator. It consumes no LLM tokens: it renders an optional prompt, blocks until a response arrives (or a timeout expires), and writes the collected text to the workflow context exactly like an agent’s output. Downstream nodes and when: edges then gate on that response.
nodes:
  approve_draft:
    type: human
    prompt: |
      {{ draft.output }}

      Approve this draft? (yes / edit / reject)
    writes: working.approval
    timeout: 3600
    on_timeout: use_default
    default_output: "approved"

Node fields

FieldRequiredTypeDefaultDescription
typeYes"human"Node type discriminator.
promptNostringTemplate string shown to the operator. Supports all standard {{ expr }} interpolation. When omitted, only the node ID is shown.
writesYesstringDot-notation context path where the collected response is stored.
timeoutNonumbernoneWall-clock seconds before on_timeout fires. Omitted (or null) waits indefinitely.
on_timeoutNo"abort" | "skip" | "use_default""abort"Action when the timeout expires (see below).
default_outputRequired when on_timeout is "use_default"stringFallback response written on timeout.

Timeout behaviour

on_timeoutBehaviour when the timeout expires
abort (default)Raises HumanInputError and stops the workflow.
skipWrites the empty string to writes and continues.
use_defaultWrites default_output to writes and continues.
HumanInputError is exported from the top-level package:
from sirenspec import HumanInputError

Supplying responses programmatically

By default the node reads a single line from stdin. When driving SirenSpec from Python — tests, web backends, chat bridges — pass a human_input_fn coroutine to execute or execute_streaming to supply responses without a terminal:
import asyncio
from sirenspec import load_workflow, execute

async def auto_approve(prompt: str) -> str:
    # In production this might await a webhook, a Slack reply, or a DB poll.
    return "approved"

workflow = load_workflow("workflow.yaml")
trace = asyncio.run(execute(workflow, user_input="Draft the post.", human_input_fn=auto_approve))
The callable receives the rendered prompt string and returns the operator’s response.

Gating downstream nodes

Because the response is written to the context, you can branch on it with when: edges:
nodes:
  approve:
    type: human
    prompt: "Approve publishing? (yes/no)"
    writes: working.approval

  publish:
    agent: publisher
    writes: output.result

edges:
  - from: approve
    to: publish
    when: working.approval == "yes"
If the operator answers anything other than "yes", the publish node is never activated.

When to use a human node

Use a human node when:
  • A draft, plan, or destructive action needs explicit sign-off before proceeding.
  • A human must supply a value the workflow cannot derive (a decision, a correction, a missing fact).
  • You want a reviewable checkpoint inside an otherwise automated pipeline.
Use something else when:
  • The decision can be expressed as a deterministic rule — use a when: edge on an upstream agent’s output instead.

Cookbook recipe

See the YAML Reference for the full field-by-field listing.