Skip to content

Slack

Use agents in Slack threads and review tool calls with Approve and Deny buttons. For HTTP delivery, the daemon needs a public HTTPS address. For local testing, use Socket Mode.

  1. At api.slack.com/apps, choose Create New App › From a manifest. Paste the manifest below, with agents.example.com changed to the daemon's address. Slack will verify the URL after you start the configured daemon in step 6.

  2. Choose Install to Workspace, then copy the Bot User OAuth Token (xoxb-...). Under Basic Information › App Credentials, copy the Signing Secret.

  3. Store both:

    echo "xoxb-..." | wardrik vault put SLACK_BOT_TOKEN --for api_key
    echo "..." | wardrik vault put SLACK_SIGNING_SECRET --for signing_secret
  4. Add the channel to config.yaml. Change acme.com to your email domain, and acme to your org:

    ~/.config/wardrik/config.yaml
    channels:
    slack:
    type: slack
    token: ${SLACK_BOT_TOKEN}
    signing_secret: ${SLACK_SIGNING_SECRET}
    domains: {acme.com: acme}
  5. Restart the daemon, and check the channel:

    wardrik stop && wardrik start
    wardrik doctor
  6. In the app's Event Subscriptions, choose Retry next to the request URL.

  7. List the channel in the agent's file:

    channels: [slack]
  8. In a Slack channel, run /invite @Wardrik.

Slack app manifest
display_information:
name: Wardrik
description: Agents that answer in threads, and ask before they act.
features:
app_home:
home_tab_enabled: false
messages_tab_enabled: true
messages_tab_read_only_enabled: true
bot_user:
display_name: Wardrik
always_online: true
oauth_config:
scopes:
bot:
- channels:history
- channels:read
- chat:write
- chat:write.public
- groups:history
- groups:read
- reactions:write
- users:read
- users:read.email
settings:
event_subscriptions:
request_url: https://agents.example.com/v1/channels/slack/events
bot_events:
- app_uninstalled
- channel_left
- group_left
- member_joined_channel
- message.channels
- message.groups
- tokens_revoked
interactivity:
is_enabled: true
request_url: https://agents.example.com/v1/channels/slack/actions
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false
  • slack in each URL is the channel's name in config.yaml: lower-case letters, digits and -.
  • See all settings in config.yaml › channels.

Map Slack members to Wardrik users to control access. Each member uses the mapped user’s permissions. See built-in rules.

  • domains maps an email domain to an org. [email protected] acts as user:acme/@maria.

  • members maps one member ID to a user, and wins over domains. Copy the ID from the member's Slack profile (Copy member ID):

    members: {U024BE7LH: user:admin}
  • Unmapped members receive an access-denied message directing them to an admin.

  • Members of other workspaces, over Slack Connect, are matched by members only.

Socket Mode needs no public address. It's for testing.

  1. In the manifest, set socket_mode_enabled: true and remove both request_url lines.

  2. Under Basic Information › App-Level Tokens, create a token with connections:write (xapp-...). Store it as SLACK_APP_TOKEN.

  3. In config.yaml, set app_token in place of signing_secret:

    channels:
    slack:
    type: slack
    token: ${SLACK_BOT_TOKEN}
    app_token: ${SLACK_APP_TOKEN}
    domains: {acme.com: acme}

Use a separate app per daemon. Slack spreads events across all of an app's connections.

:wave: books answers here now. [Answer every message] [⋯]

  • With one agent available, it replies when mentioned. Answer every message makes it reply to each message, in its own thread.
  • With several, the bot posts a button per agent. With none, it posts how to make one available.
  • ⋯ has Use another agent and Stop answering here. Removing the bot also unbinds the channel.
  • A channel is available to the deployment's org, unless orgs assigns it to another.

maria: @Wardrik how much Barolo is left? :eyes: ↳ Wardrik: Two cases, both 2019.

  • Mention the bot to start a thread. Replies in the thread need no mention.
  • Each thread is a conversation, such as conversation: "slack.T0123.C024BE91L.1696.1234". Threads share the agent's definition and files.
  • Every channel an agent answers in shares its files and digests. To keep a private channel's work out of a public one, give each its own agent.
  • :eyes: means the agent received the message. :white_check_mark: means it replied.
  • The thread's status shows the agent's current activity.
  • To post notices in a channel, set notify: "slack:#updates". See Send notifications.

A pending call shows its tool, the policy message and its arguments, with Approve, Deny and a note field. Once resolved, the buttons are replaced with the result:

Denied deploy by @jon: Deploy to staging first, and show me the diff.

The member who started the turn can always approve its calls. Anyone else needs a binding (see Who can approve):

~/.config/wardrik/config.yaml
roles:
approver: {rules: [{verbs: [approve, deny, decide], kinds: [agent], names: [books]}]}
bindings:
- {org: acme, role: approver, subjects: [user:acme/@maria]}

Scheduled and webhook-triggered turns have no initiating Slack member. Use a CLI admin approval or grant an approver role to the Slack users who should review them.

If the app was invited while the daemon was down, /remove @Wardrik and invite it again. Then check:

  • Event Subscriptions shows the request URL as verified;
  • wardrik doctor verifies the tokens with Slack;
  • wardrik logs.

If a member is denied access, check their Slack email domain or add their member ID to members.