arizuko

arizuko › products › Slack team agent — setup

Slack team agent — setup

The setup puts one agent into a Slack channel. It needs an arizuko instance, a Slack app, linked identities through OAuth, and an optional web chat for each teammate.

The agent needs a public host, a Slack workspace and KVM

create records the product for the main group

arizuko create -i acme --product slack-team
$EDITOR /srv/data/arizuko_acme/.env

--product slack-team records the product as ARIZUKO_DEFAULT_PRODUCT and copies no file. At its first boot routd selects the product for the main group. The next arizuko package sync installs its manual, skills and group/ files. arizuko create also writes the default .env. Without --product, the instance starts with the generic persona. The rest of this guide assumes the product.

.env holds the Slack keys and the OAuth providers

# Identity + secrets
ASSISTANT_NAME=acme              # agent display name, also the pane title
AUTH_SECRET=REPLACE_ME           # arizuko create writes it
WEB_HOST=acme.example.com        # public hostname for proxyd and OAuth callbacks
WEB_PORT=8095                    # starts the web layer

# Slack adapter
SLACK_BOT_TOKEN=xoxb-REPLACE_ME  # from the Slack app page "OAuth & Permissions"
SLACK_SIGNING_SECRET=REPLACE_ME  # from the Slack app page "Basic Information"

# OAuth providers for linked identities, any subset
GITHUB_CLIENT_ID=REPLACE_ME
GITHUB_CLIENT_SECRET=REPLACE_ME
GOOGLE_CLIENT_ID=REPLACE_ME
GOOGLE_CLIENT_SECRET=REPLACE_ME
DISCORD_CLIENT_ID=REPLACE_ME
DISCORD_CLIENT_SECRET=REPLACE_ME

arizuko create already wrote AUTH_SECRET and SECRETS_KEY. Do not change them. reference / env lists every variable and the daemons that read it.

The Slack app needs scopes, events and the signing secret

In api.slack.com/apps, make a new app from scratch in your workspace. Then:

OAuth & Permissions
Add the bot scopes channels:history, channels:read, groups:history, groups:read, im:history, im:read, mpim:history, mpim:read, chat:write, chat:write.public, reactions:read, reactions:write, files:read, files:write, users:read and assistant:write. The AI pane needs the last one. Install the app and copy the Bot User OAuth Token into SLACK_BOT_TOKEN.
Event Subscriptions
Enable events and set the request URL to https://<WEB_HOST>/slack/events. Subscribe to the bot events message.channels, message.groups, message.im, message.mpim, reaction_added and member_joined_channel. app_mention is not necessary, because slakd reads the mention from the message text.
Basic Information
Copy the Signing Secret into SLACK_SIGNING_SECRET.

template/services/slakd/arizuko.yml adds the /slack/ path to proxyd, so compose needs no edit. slakd checks the Slack request signature with the signing secret.

The AI pane is optional

The AI pane opens behind the AI icon in Slack. It has suggested-prompt buttons, a title for each pane and the channel context. It needs the assistant:write scope from the list above. In the Slack app, open Agents & AI Apps and turn the agent on. Slack, step 8 has the steps. To check, click the AI icon next to the bot in Slack. A pane must open.

slakd sets the first pane title from ASSISTANT_NAME. During a turn, the agent changes the title and the suggested prompts with the pane_set_title and pane_set_prompts MCP tools.

package add installs the Slack adapter

Start the instance as a systemd service, as getting started shows. Select the package, then restart the service to start the adapter:

arizuko package add -i acme catalog:slakd
sudo systemctl restart arizuko_acme

Channel use needs no sign-in

The route that maps the Slack channel to a group is the fence. Anyone whom the workspace lets into the channel can talk to the bot, and the grants of the channel limit what the agent does. Only three surfaces need an OAuth login, because they need one identity across platforms: the private web chat, per-user secrets, and memory merged across Slack, GitHub and Discord. A teammate who wants them signs in at https://<WEB_HOST>/auth/login. After that, a login with any linked provider reaches the same user.

To admit teammates in advance, make an invite for the folders that it opens. The argument is a folder glob, not a user pattern:

arizuko invite -i acme create 'main/**' --max-uses 20 --expires 336h
arizuko invite -i acme list

Or open access through onboarding: set ONBOARDING_ENABLED=true and ONBOARDING_PLATFORMS=slack in .env. Teammates who send a DM to the bot go into the admission queue, and the operator approves them in /dash/.

A mention in a channel must get a reply in the thread

The usual routes answer DMs and mentions and observe the rest

A workspace bot usually answers every DM and stays quiet in channels and multi-person DMs until someone mentions it. Add these routes with arizuko route -i acme add or on the routes page of the dashboard:

seq  match                                          target
-2   chat_jid=slack:*/dm/*                          main
-1   chat_jid=slack:*/channel/* verb=mention        main
-1   chat_jid=slack:*/group/*   verb=mention        main
0    chat_jid=slack:*/channel/*                     main#observe
0    chat_jid=slack:*/group/*                       main#observe

The channel messages still go into main under #observe, so the agent has the context at the next mention. No turn starts until someone calls the agent. Asymmetric channels has the general recipe.

Each channel folder has its own instructions

Each Slack channel maps to a group folder. Edit its PERSONA.md for voice and SYSTEM.md for instructions, unless a selected package ships that file: a package file returns on the next package sync. Select packages for it with arizuko package add -g and set tool grants separately. The files are available over WebDAV under /dav/<folder>/.

For each teammate, the agent writes the memory file into users/ in the channel folder. You do not write it. Change grants on /dash/ or with arizuko group grant.

Secrets for one channel stay in that channel. Put the API keys of tools that one channel needs into the secrets table: arizuko secret -i <instance> set <folder> KEY --value V. The broker on the host uses the key for each tool call. Other channels do not see it.

Each new channel is one more group

arizuko group -i acme add 'slack:T012ABCD/channel/C0HJKL456' eng-support

The JID matches the routes above. Edit the new folder's PERSONA.md for its voice and select packages for it with arizuko package add -g eng-support.

The grants of a channel are rows in the acl table. The CLI writes them: arizuko group -i acme grant <sub> '<pattern>' [action] [--effect allow|deny] [--params <matcher>]. The action defaults to admin. Grants explains the row, and the CLI reference lists the flags.

Autoviv makes the folders for you

To avoid one arizuko group add for each new channel, add a catch-all route to a world agent, for example atlas, and add an autoviv paragraph to its SYSTEM.md. A new channel then reaches atlas at its first message. The agent calls register_group, and the next message of that channel goes to its own folder. Autoviv has the recipe and the limits.

One slakd serves one workspace

slakd reads its bot token once at start, so one process serves one workspace. Two ways cover more workspaces.

Option A: one instance with a second slakd service

A second account is its own compose fragment, slakd-<label>.yml, with its own container_name, env_file, LISTEN_URL and CHANNEL_NAME. The fragment reads env/slakd-<label>.env. A <KEY>_<LABEL> line in .env replaces the key for that account, for example SLACK_BOT_TOKEN_BETA and SLACK_SIGNING_SECRET_BETA. Without these lines, the second service reads the first workspace. The second Slack app needs its own event URL with its own proxyd route to the second service. [compose README] describes the fragment.

The JIDs of each workspace carry its team ID, for example slack:T012/channel/C0HJK. So routes and groups stay separate for each workspace with no extra setup.

Use option A when one operator owns the workspaces and they share state: one model key, one /dash/, one OAuth setup, one store and one set of users. The cost is shared spend and shared failure: a bad skill or an Anthropic outage hits every workspace at once.

Option B: one instance for each workspace

Run arizuko create -i acme and arizuko create -i beta as separate instances on one host. Each gets its own WEB_HOST, store, systemd unit, model key and /dash/. Use option B to isolate customers: a runaway agent or a leaked token of one workspace cannot reach the other.

Hook tokens bring external events into a channel agent

To bring GitHub pull requests, Linear issues or Sentry alerts into the agent, make a hook token for the folder. Events arrive as messages at hook:<folder>/<source>, with the body unchanged. Webhooks has the full recipe.

# make the token with the CLI, or with issue_webhook from the agent
arizuko token -i acme issue webhook main/eng github
# webhook URL: https://<WEB_HOST>/hook/<token>
arizuko route -i acme add 'chat_jid=hook:main/eng/github' main/eng --seq -5

# in github.com/<repo>/settings/hooks:
# Payload URL: https://<WEB_HOST>/hook/<token>
# Content-Type: application/json

The events arrive in the main/eng folder with chat_jid = hook:main/eng/github. The main/eng agent sorts them, and it can call escalate_group to its parent when a person must act. Moves across channels and platforms use the same MCP tools, as talking to agents shows.

The hook forwards the request body, not its signature headers. The token in the URL is the bearer credential; rotate it with a new token. Use gitd when GitHub signature verification is required. The webhooks security model explains this.

Email uses the emaid adapter

For forwarded alerts and mailing-list digests, use the emaid adapter, not a hook token. It receives over IMAP, sends over SMTP and keeps threads by Message-ID. emaid has the connection setup. Three variables decide whether emaid trusts inbound mail:

# In .env: required to mark ANY inbound as trusted
EMAIL_TRUSTED_AUTHSERV=mx.google.com   # your upstream MX authserv-id
EMAIL_TRUSTED_DOMAINS=mycompany.com    # optional; From domains to trust
EMAIL_STRICT_AUTH=false                # true = drop unauthenticated mail

Without EMAIL_TRUSTED_AUTHSERV, every inbound mail arrives with verb=untrusted, and the slack-team CLAUDE.md tells the agent not to act on its contents. Set EMAIL_TRUSTED_AUTHSERV to the authserv-id of your provider, for example mx.google.com for Gmail or mail.fastmail.com for Fastmail, to trust mail that passes DMARC.

A child group can run the support product in one channel

The slack-team product is general: it answers questions, summarises threads and files issues. For one channel that needs a narrower agent, for example #eng-support, add the support product as a child group under the slack-team root. Most channels keep the slack-team persona, and #eng-support gets the support persona: knowledge base first, a citation or a statement that it does not know, and an escalation when it cannot answer.

One Slack app serves both behaviours

The two products differ in PERSONA.md and CLAUDE.md, and they list similar skills. As two groups under one Slack instance, they share one bot identity in Slack, with one app, one token and one onboarding flow, and they act differently in each channel.

Two commands and two routes add the child group

After the steps above, add a support child group:

# 1. make the child group from the support product
arizuko group -i acme add 'slack:T123/channel/C456' main/support -p support
arizuko package sync -i acme

# 2. copy the example facts once, then put your knowledge base into facts/,
#    one Markdown file for each topic
cp -r ant/examples/support/facts /srv/data/arizuko_acme/groups/main/support/
$EDITOR /srv/data/arizuko_acme/groups/main/support/facts/<topic>.md

# 3. optional: start turns on mentions only; a lower seq comes first
arizuko route -i acme add 'chat_jid=slack:T123/channel/C456 verb=mention' main/support --seq -3
arizuko route -i acme add 'chat_jid=slack:T123/channel/C456' 'main/support#observe' --seq -2

Messages from C456 then go to the session of main/support, with its persona, its memory and its facts/, separate from the rest of slack-team. Thread state belongs to each group, so no context crosses channels.

Each part has one job

slack-team, the root main
The general voice. It answers every channel that has no narrower route. Persona at groups/main/PERSONA.md.
support, the child main/support
The voice that cites facts, does not guess and escalates to the parent group when it cannot answer. Persona at groups/main/support/PERSONA.md, instructions at groups/main/support/SYSTEM.md.
Routes
The routes are the fence. A Slack user in #eng-support talks to the support persona, and the same user in #general talks to the slack-team persona. Grants on main/support can limit the support agent for each user.
WebDAV
The facts/, PERSONA.md and SYSTEM.md of each group are at /dav/main/ and /dav/main/support/.

More children use the same pattern

Add main/eng with -p pm for project management in the engineering channel, or main/marketing with -p creator. Each child is one arizuko group add and one route. A child copies nothing from the slack-team parent, because groups are isolated by design.

Each child group runs its own guest for each turn, so the concurrency limit applies: MAX_CONCURRENT_CONTAINERS, default 5. For the agent to make child groups itself, it needs mcp:register_group with a scope that covers the child folder. No other limit on the number of children exists.

A small team needs no child group

For a small team with one busy channel, the slack-team persona is enough. A support child adds a folder, a route and a facts/ directory to maintain. Add it when you can name the channel that needs a different behaviour, for example when you keep changing main/SYSTEM.md for one channel.

Five problems have a known cause

The bot left the channel
Slack removes the bot when someone archives the channel or an admin removes the bot. Invite it again with /invite @acme in the channel. No restart is necessary.
The bot is in the channel and does not answer
Check the route table: a route must match the JID of the channel, and the mention route must not be #observe. arizuko route -i acme list prints the table.
Slack refuses the token
Someone rotated the token or installed the app again. Copy the new Bot User OAuth Token into SLACK_BOT_TOKEN in .env and restart: sudo systemctl restart arizuko_acme.
The AI pane is slow
The Slack assistant.threads.* endpoints have a low rate limit. Run sudo journalctl -u arizuko_acme | grep 429. If you see throttle warnings, the agent calls pane_set_status or pane_set_prompts more often than Slack allows for the workspace.
Memory does not follow the user after an OAuth login
The link did not complete. Ask the user to open https://<WEB_HOST>/auth/login and sign in again with the provider to link.

Six pages give the details

Slack team agent
What teammates see.
Slack
Slack details.
grants
Actions limited for each user and each folder.
auth
OAuth providers and linked accounts.
slack pane
The AI pane, its events and the suggested prompts.
slakd
The Slack adapter daemon.