UniteLabs
Guides

Typed operator inputs

Collect structured data from operators at runtime using a RunInput model — enums, booleans, numbers, and text fields rendered as a platform form.

When a phase calls pause_flow_run(wait_for_input=...), the platform renders a form for the operator based on the RunInput model's field annotations. A bool becomes a toggle switch, a Literal[...] becomes a dropdown, and so on. This guide shows how each type maps to a form control and how to submit input programmatically via the API.

If the operator only needs to confirm an action — with no data to enter — use operator_confirm() instead. This guide is for collecting structured input.
For unitelabs-sdk< 0.10.0, import flow and task from Prefect instead. Use flow in place of both workflow and phase decorators, and task as step decorators.

Prerequisites

  • Familiarity with phases and HITL (see Human in the loop)
  • unitelabs-sdk[automate] installed

Type → form field mapping

Python typeForm controlNotes
strText input
boolToggle switch
intNumber inputAccepts whole numbers only
floatNumber inputAccepts decimals
Literal["a", "b", ...]Dropdown selectValues become the option list
Enum subclassDropdown selectEnum values become the option list

Fields without a default are required — the operator cannot submit the form until they are filled. Fields with a default are optional and pre-filled with that value.

Using enums for constrained choices

When an operator must pick from a fixed set of options, use Literal or a Python Enum. Both render as a dropdown select.

With Literal

workflows/protocol_select.py
from typing import Literal
from unitelabs.sdk import pause_flow_run, RunInput
from unitelabs.sdk.automate import phase

class ProtocolInput(RunInput):
    sample_id: str
    protocol: Literal["standard", "dilution_2x", "dilution_10x"]
    confirm: bool = False

@phase()
async def select_protocol():
    data = await pause_flow_run(wait_for_input=ProtocolInput)
    run_protocol(data.sample_id, data.protocol)

With a Python Enum

workflows/triage.py
from enum import Enum
from unitelabs.sdk import pause_flow_run, RunInput
from unitelabs.sdk.automate import phase

class Priority(str, Enum):
    low = "low"
    medium = "medium"
    high = "high"

class TriageInput(RunInput):
    sample_id: str
    priority: Priority = Priority.medium

@phase()
async def triage_sample():
    data = await pause_flow_run(wait_for_input=TriageInput)
    register_triage(data.sample_id, data.priority)

Use str, Enum as the base class so the values serialize cleanly to JSON strings when submitted via the API.

Optional fields and defaults

Fields with default values appear in the form pre-filled but not required. Fields without defaults must be filled before the operator can submit.

workflows/qc_gate.py
from unitelabs.sdk import pause_flow_run, RunInput
from unitelabs.sdk.automate import phase

class QCInput(RunInput):
    sample_id: str        # required — text input, no default
    approved: bool        # required — toggle, no default
    notes: str = ""       # optional — text input, empty by default
    repeat_count: int = 1 # optional — number input, pre-filled with 1

@phase()
async def quality_check():
    data = await pause_flow_run(wait_for_input=QCInput)
    if not data.approved:
        raise ValueError(f"Sample {data.sample_id} rejected at QC gate. Notes: {data.notes}")
    log_approval(data.sample_id, data.repeat_count)

The AWAITING_INPUT run state

When execution reaches a pause_flow_run(wait_for_input=...) call, the run transitions to AWAITING_INPUT. You can observe this by polling the run status:

GET /v1/runs/{runId}/status
{
  "status": "AWAITING_INPUT",
  "statusSince": "2025-06-01T14:22:00.000Z",
  "input": {
    "phase-abc-123": {
      "id": "phase-abc-123",
      "description": "Confirm sample before dispensing",
      "schema": {
        "properties": {
          "sample_id": { "type": "string", "title": "Sample ID" },
          "approved": { "type": "boolean", "title": "Approved" },
          "notes": { "type": "string", "title": "Notes", "default": "" }
        },
        "required": ["sample_id", "approved"]
      },
      "values": null
    }
  }
}

The input field is a map from phase ID to an input request object containing the JSON Schema for that phase's parameters. Use the schema to render your own form or validate values before submitting.

AWAITING_INPUT is distinct from PAUSED. PAUSED is produced by pause_flow_run() with no input model and carries no input schema — the operator simply clicks resume. AWAITING_INPUT means the platform is holding a typed form open.

Submitting input via API

To resume a run in AWAITING_INPUT, POST to the run's status endpoint with status: "RUNNING" and a data object keyed by phase ID:

curl -X POST "$BASE_URL/v1/runs/{runId}/status" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "RUNNING",
    "data": {
      "phase-abc-123": {
        "sample_id": "S-001",
        "approved": true,
        "notes": "Checked visually — all good"
      }
    }
  }'

The keys in data are the phase IDs from the input field of the status response. The values must match the types in the schema — submit a JSON boolean for bool fields, a number for int/float fields, and a string for str and Enum fields.

Multiple simultaneous inputs

If two or more phases reach their pause_flow_run(wait_for_input=...) at the same time (e.g., parallel branches), the input field in the status response contains one key per waiting phase. Submit all of them in a single POST by including multiple phase IDs in data:

curl -X POST "$BASE_URL/v1/runs/{runId}/status" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "RUNNING",
    "data": {
      "phase-abc-123": { "approved": true, "sample_id": "S-001" },
      "phase-def-456": { "priority": "high", "operator_id": "op-99" }
    }
  }'

Each phase resumes independently once its entry in data is received.

Next steps

  • Basic HITL: simpler pause/resume without structured input using operator_confirm()
  • Workflows API: full reference for run states, status transitions, and artifacts