Skip to main content

Documentation Index

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

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

A task is a row in the awaithumans database. It moves through a small state machine driven by three actors: the agent (creates / cancels), the human (completes), and the scheduler (times out).
                 ┌─────────────┐
                 │   CREATED   │  agent called await_human()
                 └──────┬──────┘
                        │ notify channels (slack, email, …)

                 ┌─────────────┐
                 │  NOTIFIED   │  message posted, awaiting human
                 └──────┬──────┘
                        │ human opens in dashboard / slack / email

                 ┌─────────────┐
                 │ IN_PROGRESS │  (optional intermediate)
                 └──────┬──────┘
                        │ human submits

              ┌──────────┴──────────┐
              │                     │
              ▼ verifier passes     ▼ verifier rejects
       ┌──────────────┐      ┌─────────────┐
       │  COMPLETED   │      │  REJECTED   │  ← non-terminal
       └──────────────┘      └──────┬──────┘     (human gets to retry)
        terminal                    │
                                    │ exhausts max_attempts

                         ┌─────────────────────────┐
                         │ VERIFICATION_EXHAUSTED  │
                         └─────────────────────────┘
                                  terminal


Side branches:

  any non-terminal status ──▶  TIMED_OUT   (scheduler hit `timeout_at`)
  any non-terminal status ──▶  CANCELLED   (agent or operator cancelled)

Statuses

StatusTerminal?Meaning
CREATEDnoJust inserted; channels haven’t fired yet.
NOTIFIEDnoAt least one channel delivered the task.
IN_PROGRESSnoHuman opened the task (best-effort tracking).
SUBMITTEDnoHuman clicked submit; verifier (if any) is running.
VERIFIEDnoVerifier passed; commit imminent.
REJECTEDnoVerifier rejected this attempt. Human can resubmit.
COMPLETEDyesFinal response stored; agent unblocked.
TIMED_OUTyesScheduler fired timeout_at before completion.
CANCELLEDyesAgent or operator cancelled.
VERIFICATION_EXHAUSTEDyesOut of max_attempts; agent gets a typed error.
The “terminal” column matters: tasks in non-terminal statuses still answer to long-polling agents and dashboard refreshes; terminal statuses unblock the agent immediately.

What the agent sees

The SDK’s await_human() long-polls until the task is terminal, then maps the status to a typed return / exception:
try:
    decision = await_human_sync(...)        # blocks until terminal
    # status was COMPLETED → decision is the typed response
except TaskTimeoutError:
    # status was TIMED_OUT
except TaskCancelledError:
    # status was CANCELLED
except VerificationExhaustedError:
    # status was VERIFICATION_EXHAUSTED
Same shape in TypeScript with awaitHuman and instanceof checks.

What the dashboard shows

Each status renders with a distinct badge color so an operator scanning the queue sees at a glance which tasks need attention. REJECTED tasks (non-terminal but failed) get a yellow “needs attention” treatment so a reviewer doesn’t think the verifier silently swallowed their submission.

Audit trail

Every status transition writes a row to audit_entries with:
  • from_statusto_status
  • action (created, completed, verified, rejected, timed_out, cancelled)
  • actor_type (agent, human, system)
  • actor_email (when human-driven)
  • channel (where the action happened: dashboard, slack, email)
  • extra_data (response keys, verifier reason, etc.)
The dashboard renders the trail at /task/{id}/audit. Operators use it to debug “what happened to this task” without joining tables.

Why REJECTED is non-terminal

If the verifier rejects an attempt and we marked the task terminal, the human couldn’t fix their answer. Instead, REJECTED keeps the task active, the verifier reason is shown to the reviewer (“notes contradict decision”), and they get another shot. The cycle ends when either:
  • A submission passes the verifier → COMPLETED
  • max_attempts is exhausted → VERIFICATION_EXHAUSTED (terminal)
  • Timeout fires → TIMED_OUT (terminal)
The lifecycle is shaped to settle exactly once. Once a task hits any terminal status, that status is the canonical answer for that idempotency key — re-invoking await_human() with the same key returns the same outcome rather than starting over. See Idempotency.