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.

By the end of this page you’ll have:
  • A local awaithumans server running at http://localhost:3001
  • An operator account
  • A Python script that creates a task and waits for your decision
  • That decision flowing back into the script as a typed value

1. Install

pip install "awaithumans[server]"
The [server] extra installs the FastAPI server, dashboard, and CLI alongside the SDK. The base awaithumans package is just the lightweight HTTP client.

2. Start the server

awaithumans dev
You’ll see something like:
✓ Server running at http://localhost:3001
✓ Database: ./.awaithumans/dev.db
✓ Open the dashboard at:

    http://localhost:3001/setup?token=8aF...

  (one-shot setup link — paste it into your browser)
Open the printed /setup?token=... URL, paste a password, and create your operator account. You’re now logged into the dashboard at http://localhost:3001.
awaithumans dev auto-generates the encryption key, admin token, and SQLite database in ./.awaithumans/. The CLI also writes ~/.awaithumans-dev.json so the SDK can auto-discover the server URL and bearer token — you don’t have to set env vars.

3. Run your first task

# refund.py
from pydantic import BaseModel
from awaithumans import await_human_sync


class RefundPayload(BaseModel):
    amount_usd: int
    customer_id: str
    reason: str


class RefundDecision(BaseModel):
    approved: bool
    notes: str | None = None


def main():
    decision = await_human_sync(
        task="Approve $250 refund?",
        payload_schema=RefundPayload,
        payload=RefundPayload(
            amount_usd=250,
            customer_id="cus_demo",
            reason="Duplicate charge",
        ),
        response_schema=RefundDecision,
        timeout_seconds=900,
        # In real code use a stable per-event ID — e.g. `f"refund:{order.id}"`.
        # Same key = same task, even if your script restarts mid-wait.
        idempotency_key="refund:order_demo",
    )

    if decision.approved:
        print(f"Refund approved: {decision.notes}")
    else:
        print(f"Refund rejected: {decision.notes}")


if __name__ == "__main__":
    main()
Run it:
python refund.py
The script prints:
✓ Task created: tsk_4f8a2c1e9b...
  Review at: http://localhost:3001/task?id=tsk_4f8a2c1e9b...
  Waiting for human (timeout: 900s). Ctrl-C to abort.
Then it blocks. The agent is now awaiting a human.

4. Approve it

Click the “Review at” URL (or refresh http://localhost:3001). You’ll see a task on your queue. Click in, fill the form, hit Submit. Within a second your script prints:
Refund approved: Duplicate charge confirmed by support team.
That’s the whole loop. The agent’s await_human() call blocked for as long as the human took to decide, then resumed with the typed RefundDecision instance.

What just happened

┌─────────────────┐  POST /api/tasks   ┌──────────────────────┐
│ refund.py       │ ──────────────────► awaithumans server     │
│                 │                    │                      │
│ awaithumans.    │  long-poll status  │  notify human via:   │
│   await_human() │ ◄──────────────────┤  - dashboard         │
│   blocks        │                    │  - slack (optional)  │
│                 │                    │  - email (optional)  │
│                 │  human submits     │                      │
│ ◄── response ───┤                    │  store response      │
│                 │                    │                      │
│ continues       │                    │                      │
└─────────────────┘                    └──────────────────────┘
The SDK long-polls until the task reaches a terminal state. For agents that can park for hours/days (Temporal, LangGraph workflows), the durable adapters replace polling with workflow-engine signals so you spend zero compute while waiting.

Add a notification channel

Right now the only place a human sees the task is the dashboard. Add Slack with one extra kwarg — the rest of the call is identical to the script above:
decision = await_human_sync(
    task="Approve $250 refund?",
    payload_schema=RefundPayload,
    payload=RefundPayload(
        amount_usd=250, customer_id="cus_demo", reason="Duplicate charge"
    ),
    response_schema=RefundDecision,
    timeout_seconds=900,
    notify=["slack:#approvals"],   # ← post to this channel
)
Set up the Slack channel first, otherwise the notify= line is silently dropped and the task only appears in the dashboard — the snippet above won’t post to any Slack workspace until you’ve configured the bot.You need three things on the awaithumans server (the process running awaithumans dev):
  1. A Slack app installed in your workspace with the right scopes (app manifest in the Slack docs).
  2. AWAITHUMANS_SLACK_BOT_TOKEN (xoxb-...) and AWAITHUMANS_SLACK_SIGNING_SECRET exported in the server’s environment.
  3. The bot invited to the channel you’re posting to (/invite @Await Humans in #approvals).
Restart awaithumans dev after setting the env vars. Without this, the SDK call still succeeds — Slack delivery happens in a background task and only logs the failure server-side.
See Channels: Slack for the full setup walkthrough (static-token mode is the fastest path; OAuth multi-workspace mode is also documented). Email works the same way — notify=["email:approvals@acme.com"] — and has the same prerequisite: configure a transport (Resend or SMTP) on the server first. See Channels: Email.

Add AI verification

Have an LLM gut-check the human’s decision before it lands.
Bring your own key. The verifier runs server-side, not in your agent process. That means your LLM provider API key must be exported on the awaithumans server’s environment (the same shell that ran awaithumans dev), not the shell running your agent script. If you skip this step, the task fails with VERIFIER_API_KEY_MISSING at submit time.
In the terminal that’s running awaithumans dev (or in your .env), set the key for whichever provider you’ll use:
export ANTHROPIC_API_KEY=sk-ant-...
# then restart: awaithumans dev
You also need the provider’s SDK installed in the awaithumans server’s Python environment:
pip install "awaithumans[verifier-claude]"   # for Claude — pick the one matching your provider
Then attach the verifier to your await_human call:
from awaithumans.verifiers.claude import claude_verifier

decision = await_human_sync(
    task="Approve $250 refund?",
    payload_schema=RefundPayload,
    payload=RefundPayload(
        amount_usd=250, customer_id="cus_demo", reason="Duplicate charge"
    ),
    response_schema=RefundDecision,
    timeout_seconds=900,
    verifier=claude_verifier(
        instructions=(
            "Reject if the notes contradict the decision, or if "
            "the amount is over $1000 and notes are empty."
        ),
        max_attempts=3,
    ),
)
Your agent code doesn’t carry any API key — it just declares the verifier config; the server does the LLM call. See Verifier for the full provider list, custom env var names, and NL-parsing tricks.

Next steps

Task lifecycle

The state machine your task moves through.

Idempotency

How the same key recovers the same task across crashes.

Temporal

Park a workflow for hours/days while waiting. Zero compute idle.

LangGraph

Interrupt-based HITL in a LangGraph node.