← Back to architecture

Slack

chat integration · knowledge in the messenger

Company knowledge — right in your Slack chat

An employee messages the bot in a DM — Query Engine answers under their own permissions, with links to sources. Everything the web chat can do, without leaving the messenger.

Slack is one more thin window into the same conversation: Query Engine does the thinking and searching under the employee's identity, Slack merely carries the message in and the answer out. All that's new lives at the edges: connection, identity bridge, deferred delivery. One conversation model across all surfaces →

Three Slacks — don't confuse them

→ outbound Alerts alerts to a channel via webhook, impersonal Notifications
← inbound Assistant personal conversation under the employee's ACL this page
← inbound · data Ingestion channel history into the knowledge base (ETL) Harvester
01

Connection

Admin installs the Achilles app into the workspace once — the bot token and signing secret land in slack_settings, like SMTP in Email. The screen is platform settings, a single toggle turns the bot on.

The install model is internal-app: the customer registers the app in their own workspace. That's exactly how it should be for self-hosted.

02

Identity

The sender is resolved to an account via identity_mapping (source='slack') — the sign-in identity, distinct from the content identity of the Slack source in Knowledge Store. If the workspace's provisioned email matches, the link is automatic; if not, a link code: whoever signed in on the web gets a code and returns it to the bot in a DM (linking the Slack that returned it to the account that issued it). Only an admin creates the account (by invitation) — Slack membership grants no access.

From there on it's only user_id → role → ACL.

03

Conversation

A thread is a conversation: replies within it continue the conversation, a new message opens a new one. Context is truncated by tokens — the engine's shared mechanism, not its own. "Summarize" is an ordinary question to the bot, not a command.

Channels · @mention · slash command — v2.

04

Delivery

Slack expects an acknowledgment within 3 seconds, but the model takes longer: intake acknowledges immediately, processing goes to the interactive queue, the answer comes back as a post. It survives restarts and retries.

A repeated Slack event is dropped by event_id; the public endpoint is rate-limited.

What it looks like

The answer is scoped to Maxim's permissions: anything he can't see won't appear in it. The follow-up stays in the thread of the same conversation.

First contact — linking the account with a code:

The code is visible only in the signed-in browser and comes back through Slack — a forwarded link can't hijack the account. Linking screen →

What's in the database

identity_mapping Auth · edited + source='slack' → sign-in identity bridge
link_tokens Auth · shared one-time link code (signed in on the web → returned in a DM)
conversations.meta Query Engine conversation key (team, channel, thread_ts)
dedup:job:slack-event-… Cache & Workers idempotency for repeated events
slack_settings workspace connection · shared core singleton · CHECK (id = 1)
id BigInteger PKCHECK always 1 · singleton
team Text NULL workspace id (T…) · NULL until connected
team_name Text NULL workspace name for the UI (company.slack.com) · from auth.test
bot_token_enc Text NULL OAuth bot token (xoxb-…), AES-256-GCM ciphertext · → encryption
signing_secret_enc Text NULL secret for signing inbound requests, ciphertext
bot_user_id Text NULL bot id (U…) · filters out own messages and mentions
enabled Boolean NOT NULLDEFAULT master switch · DEFAULT false · off → the bot stays silent
last_test_ok Boolean NULL result of the last "Test connection"
last_test_at DateTime(tz) NULL when it was tested · feeds the status chip on the screen
created_at DateTime(tz) DEFAULT server_default=now()
updated_at DateTime(tz) DEFAULT server_default=now() · trigger set_updated_at()

is_available() — a model property, not a column: enabled and team · bot_token_enc · signing_secret_enc all filled in. Mirrors SMTP in Email.

Checks: Slack signatureack < 3 sidentity resolutionretry dedupthread = conversationACL pass-throughunlinked usersettings singleton

API

The webhook is anonymous by design: the lock is the Slack signature, the rate-limit window is fail-closed.