The Slack channel posts tasks to a channel or DM, opens a Block Kit modal for the form, and accepts both structured submissions and natural-language thread replies (parsed by the verifier). Reviewers without an email/password get a signed handoff URL that drops them into the dashboard authenticated.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.
Two install modes
Static-token (single workspace) — fastest setup. One bot token in env, all tasks posted to that workspace. Use this for self-hosted deployments where one team owns the awaithumans server. OAuth (multi-workspace) — for distribution scenarios where multiple Slack workspaces install your app. The dashboard’s Settings → Slack page handles the install flow. For your first run, start with static-token.Static-token setup
1. Create a Slack app
Go to api.slack.com/apps → Create New App → From manifest. Paste:YOUR-PUBLIC-URL with your awaithumans server’s public address. For local dev: https://<your-ngrok-id>.ngrok.io. The users:read.email scope is needed for the slack:alice@acme.com route format (resolves emails to Slack user IDs).
Install to your workspace. Copy the Bot User OAuth Token (xoxb-...) and the Signing Secret from Basic Information.
2. Set env vars
awaithumans dev. The dashboard’s Settings → Slack page now shows the workspace as a read-only “env” entry.
3. Test it
await_human returns.
OAuth setup
SetAWAITHUMANS_SLACK_CLIENT_ID, AWAITHUMANS_SLACK_CLIENT_SECRET, AWAITHUMANS_SLACK_SIGNING_SECRET, and AWAITHUMANS_SLACK_INSTALL_TOKEN (operator-only secret you share with admins who can install).
Open https://YOUR-PUBLIC-URL/api/channels/slack/oauth/start?install_token=YOUR_TOKEN to kick off the install. After consent, the workspace appears in Settings → Slack.
OAuth installs are stored encrypted at rest (AES-GCM keyed off PAYLOAD_KEY).
Adding users to the directory
Tasks DM’d to a Slack user only open the modal if that user exists in the awaithumans directory. Add them via: Dashboard → Users → Add user. The form has a “Slack member” picker that pulls the workspace’susers.list so operators select from a dropdown rather than copy-pasting U… IDs. The display name auto-fills. Email is optional — a Slack-only user (no email, no password) can still complete tasks; they reach the dashboard via the signed handoff URL the bot puts on every DM.
Notify formats
@alice) are resolved via users.list once per workspace per process, then cached. Emails use Slack’s dedicated users.lookupByEmail. Raw user IDs pass through unchanged.
For a multi-workspace OAuth install, suffix the channel/handle with the team:
Broadcast (channel)
Posts a message with a “Claim this task” button. First clicker wins atomically. After claim:- The original message updates to “Claimed by @user” so the button vanishes for everyone else
- The modal pops for the claimer immediately (using Slack’s interactivity trigger)
- The “View in dashboard” button on the updated message is now signed for the claimer (handoff URL — see below)
- Audit log records the claim with channel=
slack
Direct (user)
DMs the Slack user with two buttons: Approve in Slack (opens the modal in place) and Open in Dashboard (signed handoff URL — works even if the user has no email/password). The implicit-assignee derivation pins the recipient asassigned_to_user_id at task-create time, so the Slack view_submission auth check accepts their submission. Without this, a DM to @alice would surface “This task isn’t assigned to you” when she clicks through.
Signed dashboard handoff (Slack-only users)
A Slack-only user (no email, no password in the directory) has no way to clear the dashboard’s login wall — clicking “Open in Dashboard” without help would bounce them to/login. The notifier signs the URL with (user_id, task_id, expiry, hmac) at post time:
/task?id=TASK. Stateless HMAC, HKDF-derived key from PAYLOAD_KEY, no DB roundtrip on the verify path.
TTL is bound to task.timeout_at — a 7-day approval has a working link on day 6. Expired links return 400.
The post-claim “View in dashboard” button on broadcast messages is also signed, so claimers without password credentials get the same handoff.
Modal form
The modal is auto-generated from the response schema’sform_definition. Fields render as:
| Form kind | Slack block |
|---|---|
switch (bool) | radio buttons (Yes / No) |
single_select | static_select |
multi_select | multi_static_select |
short_text | plain_text_input |
long_text | plain_text_input multiline |
form_definition from their native schema language — Python via extract_form() against a Pydantic model, TypeScript via extractForm() against a Zod object schema. A single boolean response in either tongue produces a Switch primitive, which is what the email/Slack renderers use to decide whether to emit inline action shortcuts.
NL thread replies
A reviewer can reply in the message’s thread instead of clicking through the modal. The verifier parses the natural language into the response schema:verifier= is set on the task and the verifier supports NL parsing (all four built-ins do), the thread text becomes the response. See Verifier.
Post-completion message updates
When a task transitions to a terminal state — completed (Slack modal OR dashboard), cancelled by an operator, or timed out by the scheduler — the original Slack message is rewritten viachat.update:
(channel, ts, team_id) in the slack_task_messages table on every post. Tasks created before this feature shipped won’t have refs — their DMs stay interactive forever.
Authorization
awaithumans validates the Slack user submitting against the task’s assignee:- Submitter must be in the directory and active
- Submitter must be either the task’s assignee OR an operator
- Operators stepping in on someone else’s task see an inline banner in the dashboard (”⚠ Assigned to @alice — you’re submitting as operator (ops@acme.com)”)
- Anyone else gets a clear ephemeral reply: “This task isn’t assigned to you.”
Audit identity
completed_by_email is the directory user’s email when set. For Slack-only users (no email) the audit log surfaces completed_by_user_id plus a completed_by_display_name derived from display_name → email → @<slack_user_id> → row id. Operators see consistent attribution across Slack and dashboard completions, even when one of the parties has no email.
Runnable examples
| Example | Language | What it tests |
|---|---|---|
examples/slack-native/ | Python | Refund-approval task DM’d to a Slack user; manual review |
examples/slack-native-ts/ | TypeScript | Same flow, TS SDK |
tests/slack/.
Common gotchas
request_urlmismatch. Slack tries to verify your interactivity URL. If your awaithumans server moves (ngrok ID rotates, etc.), update the manifest.SLACK_SIGNING_SECRETmismatch. Every interaction gets HMAC-checked. Wrong secret → 401s in your logs.- Bot not in channel. The bot needs to be in the channel you’re posting to. Add it manually or via
channels:joinscope. - Workspace member cache. The dashboard’s “Pick Slack member” dropdown caches
users.listresults — restart the server to refresh. - Slack-only user but no
users:read.emailscope. Theslack:alice@acme.comroute format needs that scope. Add it to the app manifest and reinstall. - DM modal doesn’t open. The recipient isn’t in the directory. Add them via Dashboard → Users → Add user.