arizukoSlack team agent › setup

Slack team agent — setup

Deploy one agent into a Slack channel: arizuko instance, Slack App, OAuth-linked identities, optional per-teammate web chat.

prerequisites

1. seed the instance

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

arizuko create --product slack-team seeds the data dir at /srv/data/arizuko_acme/ with the slack-team product applied: main group gets the slack-team PERSONA.md, CLAUDE.md overlay, and a starter facts/ dir. Plus the default .env and the web/ tree copied from template/web/. Without --product the instance comes up with the generic agent persona; the rest of this guide assumes you used the product flag.

2. fill in .env

The Slack-team product needs at least these keys set:

# Identity + secrets
ASSISTANT_NAME=acme              # agent display name
CHANNEL_SECRET=<random hex>      # signing between adapters and gated
AUTH_SECRET=<random hex>         # JWT signer for /auth/, /dash/, /chat/
WEB_HOST=acme.example.com        # public hostname for proxyd / OAuth callbacks

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

# OAuth providers for linked identities (any subset)
GITHUB_CLIENT_ID=…
GITHUB_CLIENT_SECRET=…
GOOGLE_CLIENT_ID=…
GOOGLE_CLIENT_SECRET=…
DISCORD_CLIENT_ID=…
DISCORD_CLIENT_SECRET=…

Generate the two secrets with openssl rand -hex 32. See reference / env for the complete list and the daemons that read each one.

3. create the Slack App

In api.slack.com/apps, create a new app (from scratch) in your workspace, then:

The /slack/ path is wired into proxyd by template/services/slakd.toml — nothing to edit in compose generation. Slack's request signature is verified by slakd using the signing secret.

enable the AI sidebar (optional)

The AI sidebar is the per-user pane behind Slack’s AI icon — suggested-prompt buttons, a per-pane title, and full channel context. It requires the assistant:write scope (already in the list above). In the Slack App → Agents & AI Apps, toggle “Agent or Assistant” on. Step-by-step with screenshots: Slack adapter how-to, step 8. Verify by clicking the AI icon next to the bot in Slack — a pane should open.

The agent steers the pane at runtime via the pane_set_title and pane_set_prompts MCP tools — title and suggested prompts can change per turn, per channel, per context. Branding is the env var ARIZUKO_ASSISTANT_NAME (instance-wide). No static per-folder frontmatter; runtime steering + instance env covers both axes.

4. run the instance

arizuko run acme

This generates a Docker Compose file in /srv/data/arizuko_acme/compose.yml and runs it. For long-running production, install a systemd unit; see getting started for the template.

5. invite teammates

Plain channel use needs no sign-in. The route rule that maps the Slack channel to an arizuko group is the auth fence: anyone the workspace lets into the channel can talk to the bot, and per-channel grants gate what the agent will do. OAuth login is only needed for surfaces that key off a cross-platform user identity — the private web chat, per-user secrets (spec 7/Y), and merging memory across Slack + GitHub + Discord. To opt in, a teammate signs in at https://<WEB_HOST>/auth/login; subsequent logins on any linked provider resolve to the same auth_users row.

Invite their primary Slack/web identity into the channel group with the invite CLI:

arizuko invite acme create 'slack:user/U*'  --max-uses 20 --expires 14d
arizuko invite acme list

Or open access via onboarding: set ONBOARDING_ENABLED=true and ONBOARDING_PLATFORMS=slack in .env. Teammates who DM the bot land in the admission queue; the operator approves from /dash/.

6. verify

7. recommended routing default

For a workspace bot, the usual shape is “answer every DM, stay quiet in channels and mpims unless @-mentioned.” In the root agent (or via /dash/ → Routing):

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

Channel chatter still lands in main under #observe so the agent has context on the next mention, but no turn fires until someone calls on it. Full recipe and Discord variant: asymmetric channels.

8. tune per channel

Each Slack channel maps to an arizuko group folder. To customise voice, allowed tools, or skills for one channel, edit its ~/CLAUDE.md — either via WebDAV at https://<WEB_HOST>/dav/<channel>/CLAUDE.md or directly on disk under /srv/data/arizuko_acme/groups/<channel>/.

For per-teammate scope (memory file, grants), the agent writes to the channel's users/ subfolder automatically — you do not seed it. Skill toggles and grant rules are managed from /dash/ or via the arizuko group grant CLI.

Channel-scoped secrets. Tool API keys an agent needs in one channel but not another go in the secrets table — set with arizuko secret <instance> set <folder> KEY --value V. They merge into the agent container's env at spawn time; nothing leaks to other channels.

9. adding more channels

Each Slack channel becomes its own arizuko group folder. Map a new channel ID to a folder:

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

The JID matches the route table (see step 7). For a per-channel persona or skill set, edit ~/CLAUDE.md in the new folder — over WebDAV at https://<WEB_HOST>/dav/eng-support/CLAUDE.md or directly under /srv/data/arizuko_acme/groups/eng-support/.

Per-channel grants — who can run which actions in this folder — live in the acl table. Operator grants are admin-scoped via arizuko group acme grant <sub> '<pattern>'; finer per-role and per-user rules (interact, mcp:<tool>, deny effects) are managed from /dash/ → Grants today. Full rule shape: grants. CLI reference: cli.

autoviv (optional)

If you don’t want to run arizuko group add for every new channel, set a catch-all route to a tier-1 agent (e.g. atlas) and add an autoviv paragraph to that agent’s CLAUDE.md. New channels the bot is invited to land in atlas on first message; the agent calls register_group itself and the next message in that channel routes to its own folder. Recipe and limits: autoviv.

10. multi-workspace

One slakd process talks to one workspace — the bot token is read once at startup (slakd/main.go calls chanlib.MustEnv("SLACK_BOT_TOKEN")). Two ways to cover more than one workspace.

option A — one arizuko instance, multiple slakd

Drop a second service TOML beside the default slakd.toml. The compose generator picks up every services/*.toml file (compose/compose.go reads services/ in lexical order) and names each container arizuko_<file>_<flavor>, so slakd-acme.toml and slakd-beta.toml run side by side. This is the shipped pattern in specs/5/R-multi-account.md. Files named <adapter>-<label>.toml share the base adapter's env_file (see envFileFor) — per-workspace tokens go in the TOML's [environment] block, not in .env.

# /srv/data/arizuko_acme/services/slakd-beta.toml
image = "arizuko:latest"
entrypoint = ["slakd"]
volumes = ["${HOST_DATA_DIR}:${CONTAINER_DATA}"]

[environment]
ROUTER_URL = "http://gated:8080"
SLACK_BOT_TOKEN = "xoxb-beta-workspace-token"
SLACK_SIGNING_SECRET = "beta-signing-secret"
CHANNEL_SECRET = "${CHANNEL_SECRET}"
ASSISTANT_NAME = "${ASSISTANT_NAME}"
LISTEN_ADDR = ":8080"
LISTEN_URL = "http://slakd-beta:8080"

[[proxyd_route]]
path = "/slack-beta/"
backend = "http://slakd-beta:8080"
auth = "public"
gated_by = "SLACK_BOT_TOKEN"
preserve_headers = ["X-Slack-Signature", "X-Slack-Request-Timestamp"]

Set the second Slack App's Event Subscription URL to https://<WEB_HOST>/slack-beta/events — distinct path keeps the two webhooks separate. Each workspace's JIDs already carry its team ID (slack:T012/channel/C0HJK, see slakd/jid.go), so route rules and group mappings stay per-workspace without extra plumbing. arizuko run acme then brings both containers up; restart the instance after dropping the file.

Pick this when the workspaces belong to the same operator and should share state — one Anthropic key, one /dash/, one /auth/ OAuth setup, one messages.db, one set of users that can sign in across workspaces. The cost is shared spend and shared blast radius: a bad skill or an Anthropic outage hits every workspace at once.

option B — one arizuko instance per workspace

Run arizuko create acme and arizuko create beta as separate instances on the same host. Each gets its own WEB_HOST, its own database, its own systemd unit, its own Anthropic key, its own /dash/. Pick this for per-customer isolation — one workspace's runaway agent or compromised token can't reach the other.

11. webhook ingest (optional)

Bring external events — GitHub PRs, Linear issues, Sentry alerts — into a Slack-team agent by minting a hook token for the folder. Events arrive as messages at hook:<folder>/<source>; the body is delivered verbatim. Full recipe: webhooks.

Quick GitHub example. Mint a hook token for the engineering folder and paste the URL into the repo's webhook config:

# mint via CLI (operator) or issue_webhook (agent)
arizuko token issue atlas/eng hook github
# https://<WEB_HOST>/hook/<token>

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

Events arrive in the atlas/eng folder with chat_jid = hook:atlas/eng/github and the full request body and headers preserved. Route from there to your main Slack channel with a normal route rule, or let the atlas/eng agent triage and escalate_group to its parent when something needs human attention. Cross-channel and cross-platform moves use the same MCP tools the agents already share (talking to agents).

webd delivers GitHub's JSON verbatim with the X-GitHub-Event and signature headers attached. Parsing and (optional) X-Hub-Signature-256 verification are skill concerns on the agent side — the platform is intentionally hands-off. The URL token is the bearer credential; rotate by reissue. See the webhooks security model.

email ingest

For email-as-webhook (forwarded alerts, mailing-list digests), use the emaid adapter instead of a hook token — it owns IMAP receive, SMTP send, and threading by Message-ID. See emaid for the connection setup. Auth-related env vars (spec 10/17) gate whether inbound is treated as trusted:

# 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,partner.com  # optional; empty = DMARC alone gates
EMAIL_STRICT_AUTH=false                       # true = drop unauthenticated outright

Without EMAIL_TRUSTED_AUTHSERV set, every inbound is shipped with verb=untrusted and the slack-team CLAUDE.md rule routes it to quarantine. Set EMAIL_TRUSTED_AUTHSERV to your provider's authserv-id (Gmail: mx.google.com, Fastmail: mail.fastmail.com) to enable DMARC-pass trust. Full design: specs/10/17-emaid-auth.md.

12. compose with support (optional)

The slack-team product is a generalist agent — it answers questions, summarizes threads, files issues. For a more focused experience in one particular channel (say #eng-support), compose the support product as a child group under the slack-team root. The result: most channels keep the slack-team persona; #eng-support gets the support persona — KB-first, cite-or-say-I-don't-know, escalate-when-stuck.

why compose, don't replace

Both products ship the same default skillset (diary, facts, recall-memories, users, issues, web). They differ in PERSONA.md + CLAUDE.md only. By running them as sibling groups under one Slack instance you get one bot identity in Slack (operator manages one app, one token, one onboarding flow) but two distinct behaviors, scoped by channel.

setup

With slack-team already deployed (sections 1–6 above done), add a support sub-group:

# 1. create the sub-group with the support product seed
arizuko group acme add 'slack:T123/channel/C456' main/support --product support

# 2. populate facts/ — copy your KB markdown into
#    /srv/data/arizuko_acme/groups/main/support/facts/
#    (one file per topic; the support persona's first move is /recall-memories)
$EDITOR /srv/data/arizuko_acme/groups/main/support/facts/.md

# 3. (optional) override the auto-seeded route to fire on mention only,
#    not on every msg. add_route takes ONE 'route' arg with the rule as
#    JSON (seq lower = higher priority; the bare 'add' above writes
#    seq=0; -3 wins over it).
mcpc @s tools-call add_route \
  route:='"{\"seq\":-3,\"match\":\"chat_jid=slack:T123/channel/C456 verb=mention\",\"target\":\"main/support\"}"'
mcpc @s tools-call add_route \
  route:='"{\"seq\":-2,\"match\":\"chat_jid=slack:T123/channel/C456\",\"target\":\"main/support#observe\"}"'

After add_route calls the agent has full per-channel scope: messages from C456 hit main/support's session, with its persona, its memory, its facts/ dir — isolated from the rest of slack-team. Threading state is per-group so cross-channel context doesn't leak.

what each piece gives you

composing more products

Same pattern, different child folder. Add main/eng with --product pm for engineering-channel project-management voice; main/marketing with --product creator; etc. Each child is one arizuko group add + one route. The slack-team parent stays generalist; specialized children inherit nothing from it (group-level isolation by design).

Limits: each child runs its own container per agent turn, so spawn budget matters. Tune via the parent's max_children (default 16 from v0.40.1 onward) at /dash/groups/main/settings.

tradeoff: when to NOT compose

Small teams with one busy channel: the slack-team persona is already capable. Composing support adds a folder, a route, a facts/ to maintain — only worth it when you can name the channel that needs different behavior. Start with slack-team flat; promote a channel to its own sub-group when you find yourself rewriting main/CLAUDE.md for one channel's quirks.

common issues

Bot dropped from the channel.
Slack removes the bot when a channel is archived or an admin kicks it. Re-invite with /invite @acme from inside the channel. No restart needed.
Bot is in the channel but doesn’t respond.
Check two things: (1) the route table matches this channel’s JID and target is not #observe for mentions (see step 7); (2) the calling user has at least interact in this folder — check /dash/ → Grants.
Slack token rejected / /slack/health returns 503.
Token was rotated or the app was reinstalled. Copy the new Bot User OAuth Token into SLACK_BOT_TOKEN in .env and restart: sudo systemctl restart arizuko_acme.
AI sidebar feels sluggish.
Slack’s assistant.threads.* endpoints are Tier 2 (rate-limited). Grep sudo journalctl -u arizuko_acme | grep 429 — if you see throttle warnings, the agent is calling pane_set_status / pane_set_prompts more often than Slack allows for that workspace.
Per-user memory not updating after OAuth login.
Linkage didn’t complete — visit https://<WEB_HOST>/auth/login as the user and sign in again with the provider they want linked. Check /dash/ → Users to see the resolved auth_users row and its linked identities.

go deeper