-
Notifications
You must be signed in to change notification settings - Fork 2
bot_connectors_combined
Reviewed against main at ca3cf76cb on 2026-09-20.
This document consolidates Bot Connector System, Channel connectors, and the implementation review. It describes the checked-out implementation. Slack setup requirements below reflect the application's diagnostics; no live Slack or WhatsApp installation was tested during this review.
Slack and WhatsApp are the registered messaging bot transports. Discord and Telegram are extension targets exercised through mock capability tests, not implemented live connectors. The web simulator has been removed: its remaining service code is not registered and its former HTTP routes are unavailable.
Recent work includes:
- Workflow-specific Slack channel routes and WhatsApp slug routes.
- Route authorization, Run-mode workflow access, and clearer access failures.
- Shared channel capabilities for streaming, edits, reactions, and progress.
- Durable conversation bindings and continuity across turns and restarts.
- Separate editable follow-up replies and reaction cleanup.
- Slack diagnostics for scopes and Socket Mode, plus manual delivery checks.
- Slack triggers with shared JSON matching/mapping and bounded channel context.
- Connector settings in the workflow capabilities panel.
Slack / WhatsApp
-> adapter: decode events, resolve route, normalize message
-> BotConversationManager: authorize, start/resume, handle follow-ups
-> prepare workflow/profile route or saved chat capabilities
-> shared query/session execution
-> BotEventFilter: replies, progress, blocking input, completion
-> platform formatter and outgoing messages
| Component | Source relative to repository root | Responsibility |
|---|---|---|
| Connector and conversation manager | agent_go/cmd/server/services/bot_connector.go |
Contract, route integration, lifecycle |
| Event filter | agent_go/cmd/server/services/bot_event_filter.go |
Replies, progress, feedback, completion |
| Event adapter | agent_go/cmd/server/bot_event_adapter.go |
Bridge session events into the service interface |
| Session starter | agent_go/cmd/server/bot_session_starter.go |
Enter shared session execution |
| Slack adapter | agent_go/cmd/server/services/slack_service.go |
Slack transport and messages |
| WhatsApp adapter/manager |
agent_go/cmd/server/services/whatsapp_service.go, agent_go/cmd/server/services/whatsapp_manager.go
|
Transport and account/device management |
| Configuration API |
agent_go/cmd/server/bot_routes.go, agent_go/cmd/server/bot_config_routes.go
|
Connector and shared settings |
| Workflow UI | frontend/src/components/workflow/WorkflowBotsPanel.tsx |
Workflow routes and setup |
| Slack diagnostics | agent_go/cmd/server/services/slack_connection_diagnostics.go |
Shared settings/Builder connection test |
Server startup wires execution callbacks, workflow access, route preparation, event subscriptions, and secrets loading into the manager.
type BotConnector interface {
NotificationConnector
Capabilities() ChannelCapabilities
StartListening(ctx context.Context) error
StopListening()
SendThreadMessage(ctx context.Context, threadID ThreadID, message string) (string, error)
SendThreadMessageWithBlocks(ctx context.Context, threadID ThreadID, message string, blocks []MessageBlock) (string, error)
UpdateMessage(ctx context.Context, threadID ThreadID, messageID string, newText string) error
AddReaction(ctx context.Context, channelID, messageTS, emoji string) error
RemoveReaction(ctx context.Context, channelID, messageTS, emoji string) error
GetThreadHistory(ctx context.Context, threadID ThreadID) ([]ThreadMessage, error)
GetChannelName(ctx context.Context, channelID string) string
SetMessageHandler(handler BotMessageHandler)
SetInteractionHandler(handler BotInteractionHandler)
GetFormatter() MessageFormatter
}NotificationConnector supplies Name, IsEnabled, and SendNotification.
SupportsThreads() is no longer the contract. Zero capability values disable
features.
| Capability | Behavior | Slack | |
|---|---|---|---|
Threads |
Read and continue threads | Yes | No |
MessageEdits |
Update replies/progress | Yes | No |
StreamingReplies |
Stream text; also requires edits | Yes | No |
Reactions |
Acceptance/processing indicators | Yes | No |
MessageDeletion |
Remove temporary messages; requires BotMessageDeleter
|
Yes | No |
ProgressUpdates |
Temporary status; requires edits or deletion | Yes | No |
WorkflowProgress |
Permit route-requested workflow detail | Yes | Yes |
ChannelHistory |
Optional bounded ChannelHistoryReader
|
Yes | No |
Adapters translate common reaction names to their platform representation. History callers authorize the source channel; adapters enforce bounded time, message, and byte budgets. Attachment JSON remains opaque to the shared history interface. Capabilities describe presentation, not authorization.
Workflow connector settings live in the workflow capabilities panel. Slack uses channel routes; WhatsApp supports slug routes. Preparation can select a workflow or product/profile conversation instead of generic multi-agent chat.
- Slack workflow routes execute through a route-scoped bot principal and a workflow access check. Sender information remains audit metadata. Slack also applies route-specific email restrictions when configured.
- WhatsApp workflow routes use the paired workspace account and its workflow access. A saved slug identifies a destination; it does not itself grant access.
- Product/profile routes carry their own owner and conversation metadata.
- Generic chat must resolve a workspace identity.
_global.allowed_emails, merged withBOT_ALLOWED_EMAILS, filters generic messages with a resolved email. It is not a universal gate for configured routes.
Current deployed workflow routing is oriented around Run access. Preserve authorization and execution scope on follow-ups and resumes as well as starts.
These are the generic request builder's sources. Routed Slack workflow requests can return earlier through the dedicated workflow-turn preparer; generic defaults must not be assumed to override routed workflow configuration.
| Setting | Current source |
|---|---|
| Servers, skills, tools, code-execution mode | User's _users/<id>/multiagent-config.json
|
| Selected global secrets, browser settings, notification reference | Saved chat capabilities when present |
| Saved primary LLM configuration | Saved chat capabilities when present |
| Delegation tiers | Workspace config/delegation-tier-config.json
|
| Provider API keys | Encrypted workspace provider-key storage via LoadProviderKeys
|
| User secrets | Server-side loader for the resolved workspace user |
| Workflow and conversation metadata | Route preparation and active conversation |
Without saved chat capabilities, the generic builder selects no MCP servers and
discovers skills. An explicit saved configuration is handled as saved.
_global.default_servers, _global.default_skills, and _global.provider_api_keys
are not the runtime sources for this builder.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/bot/connectors |
List connector settings/status |
| GET | /api/bot/connectors/{platform} |
Read settings |
| POST | /api/bot/connectors/{platform} |
Save settings |
| POST | /api/bot/connectors/{platform}/test |
Test configuration |
| GET / POST | /api/bot/config |
Read/save shared _global settings |
The shared API still accepts legacy tier, provider-key, server, and skill fields.
Stored fields are not necessarily consumed by the current request builder.
The old PUT /api/bot/connectors/_global is not the registered save method.
Bot sessions are regular chat sessions with bot metadata on their on-disk manifest. The old SQL table description does not describe current persistence adequately.
- An explicit mention opens a conversational bot thread after access checks. Independently configured Slack triggers use a separate entry path.
- The manager prepares execution and starts the event filter.
- While running, mentions can deliver follow-ups. Plain replies can also be accepted in single-user threads. The running-session path requires a mention when history contains multiple users or cannot be read.
- Blocking sessions process answers through their blocking-response path.
- Completion clears reactions and retains the session entry. It does not post the old generic “Session completed.” status message.
- Later turns can reuse the conversation/session identity with a separate execution. The completed-session path also accepts admitted plain replies; the running-session mention rule must not be assumed to cover every state.
Non-mention messages require an existing session or a valid durable binding before entering the manager. A channel route alone does not make every ordinary message start a conversational session.
A thread reply that tags another user but not the bot is addressed to a colleague: the bot stays silent (no reply, no run, no reaction) no matter how few people are in the thread, and such a reply never revives a conversation after restart. Blocking answers and control commands still bypass this rule, and a message that tags the bot (even alongside others) is processed. Other users' tags on processed messages are rewritten to @DisplayName so model turns read names instead of opaque IDs.
Slack saves per-thread bindings under config/slack-threads/<hash>.json.
Restoration checks the route key, preventing a changed route from adopting old
history. WhatsApp also supports durable bindings, isolated by account/device.
An hourly janitor prunes completed/failed in-memory entries after seven days of inactivity. This is not immediate deletion at completion or deletion of all persisted history.
Thread-less conversations normally use a one-hour inactivity window. A later message can start a fresh conversation with a short prior-history preamble; product/profile conversations have separate continuity behavior. Changing a thread-less route creates a conversation boundary to avoid mixing workflows.
Thread-less controls require @: for example @status, @resume, @continue,
@full, and @concise. Bare @resume opens a picker; a selector can
identify a session directly. There are no session end commands: words like
done, reset, or stop, with or without @, are delivered to the agent
as ordinary text. Conversations end through the inactivity window, route
changes (@switch/@off), or plan rejection.
The filter handles streaming chunks, main-agent text, delegation lifecycle, completion, plan approval, human feedback, and errors. Capabilities and detail mode control presentation. Follow-ups have separate editable reply boundaries.
Completion normally requires a completion signal, no pending delegations, and no blocking input. Mirrored workflow work can defer manager cleanup until it drains. Temporary progress is cleaned up on completion, cancellation, and blocking input. If deletion fails, editable progress can become terminal status.
| Blocking event | Handling |
|---|---|
| Plan approval | Approval sends “Approved. Execute the plan.”; rejection cancels; other text becomes feedback |
| Human feedback with request ID | Submit through NotificationManager.ReceiveNotification to the waiting operation |
| Human feedback without request ID / fallback | Deliver a session follow-up |
Human feedback is not always a new agent turn. Preserving the request ID matters for resuming the waiting operation.
With PUBLIC_URL, supported workspace paths in Markdown links and bare paths
are converted to file links. URLs use the filter's user identity when available;
they are not always generated with uid=default.
The shared diagnostic checks the bot token, granted scopes, and the app token's ability to open a Socket Mode connection. It posts no test message and returns neither credentials nor WebSocket URLs.
Required bot scopes checked by the implementation:
app_mentions:read chat:write reactions:write
channels:history groups:history channels:read
groups:read users:read users:read.email
files:read and chat:write.public are optional feature scopes. Missing granted
scopes require updating permissions and reinstalling the app. If Slack does not
return granted scopes, diagnostics require manual verification.
Manual delivery checks remain required even when token checks pass:
- Enable Socket Mode in the same Slack app.
- Enable and save
app_mention,message.channels, andmessage.groupsevent subscriptions. Socket Mode does not require a Request URL. - Invite the bot to the target channel.
- Verify an initial mention and a plain reply in its single-user thread.
Tokens cannot read event-subscription settings. Passing the connection test is not proof of end-to-end delivery.
-
WhatsApp runs as the paired user, in their own mode, and continues
their own chat: their crew chat, or the Builder chat the web restores for a
workflow.
@listalso shows other owners' crews under "Other crews (read-only)"; those run in Run mode in the user's own reader chat. -
Channels, private channels and group DMs are groups: the turn runs as
the route (the workflow or crew), always in Run mode. The sender is named in
the prompt (
From: <name> <email> (Slack)) and kept as the audit actor. -
A 1:1 DM with a workflow's or crew's own bot runs as the sender's
AgentWorks account, in that account's own mode: owner or editor gets the
full chat, a reader gets Run mode, anyone without access is refused. The
sender must be a full member of the app's Slack team (no guests, no Slack
Connect users), Slack must confirm the conversation is a 1:1 IM with them,
and their Slack email must match exactly one enabled account in
users.json. The query boundary and every tool call re-check the mapping. One user, one chat: a DM continues the sender's own chat — for a crew, the chat their web UI (and WhatsApp) continues; for a workflow, the Builder chat their web UI restores — whichever DM thread it arrives in. The shared bot does not take DMs. -
Replies and follow-ups. In a 1:1 DM the bot replies directly (not in a
thread): top-level DM messages share one conversation keyed by the DM
channel (
slackThreadOptionskipsthread_tswhen the thread is the channel). A reply made inside a DM thread is answered in that thread, in the same chat. Channel threads are unchanged. In every bot conversation (DM, channel thread, WhatsApp) a message sent while a turn runs steers the running CLI, like the web chat; schedules, webhooks and Slack trigger runs queue. -
The agent's
slacktool has two modes. In an owner's full-mode chat (web, WhatsApp, 1:1 DM) it takes any Slack Web API method (e.g.views.publishto set the bot's App Home tab; the Slack tab has an "Ask AI to publish the Home tab" button). In Run mode (channels, read-only users) it keeps the channel-scoped read-and-reply allowlist, since anyone who can post in a channel can steer the agent. The token stays backend-owned either way.
DMs need the im:history and im:read scopes, the message.im event, and
App Home → Messages Tab with "Allow users to send Slash commands and messages
from the messages tab". The generated app manifest sets all of these;
"Save & test" reports missing DM scopes without failing channel bots.
Sources: agent_go/cmd/server/services/slack_dm.go,
agent_go/cmd/server/slack_dm.go, docs/design/bot_identity_model.md.
Route owners can configure human_message or trusted_app triggers with
matching, payload mappings, route selections, and optional bounded context.
Trusted-app matching checks configured app/bot identity. The matcher excludes
ordinary thread replies, the bot's own user messages, and unsupported subtypes.
Context is restricted to the triggering channel and excludes later messages. Validated limits are 1–100 messages and 1–1440 lookback minutes, with optional thread inclusion. Trigger settings come from route configuration, not incoming message instructions.
Sources: agent_go/cmd/server/services/slack_trigger.go and
agent_go/cmd/server/slack_trigger.go.
- Implement
BotConnector, its formatter, and explicit capabilities. - Normalize events into
BotIncomingMessageandThreadID. - Implement optional deletion/history interfaces for advertised features.
- Integrate authenticated settings and routing, preserving target-scoped Run access and account/route isolation.
- Register with the existing manager during startup.
- Wire Builder tools/guidance through existing
product.yamlpolicies. - Test transport behavior, authorization, feedback, completion, and restoration.
Mock tests using Discord or Telegram names establish shared behavior only, not a working production integration.
Confirmed gaps in the original core document:
| Finding | Consequence | Correction here |
|---|---|---|
Universal allowed_emails claim |
Wrong operational access boundary | Separate generic, Slack-route, and WhatsApp-account checks |
_global claimed as runtime config source |
Settings may not affect execution | Identify user/workspace sources and routed preparation |
| Mention-only and immediate removal claims | Wrong continuation expectations | Describe running/completed states and durable bindings |
| Obsolete interface | New adapter cannot satisfy contract | Include capabilities, reactions, and channel-name methods |
| Removed simulator/UI presented as current | Unavailable routes and setup instructions | State removal and workflow panel replacement |
| All feedback described as follow-up | Misses waiting-request delivery | Explain notification submission by request ID |
Seven targeted tests passed in agent_go/cmd/server/services during review:
TestChannelCapabilitiesDriveStreamingForAnyPlatformTestProgressCleanupUsesCapabilitiesRatherThanPlatformTestProductionChannelCapabilitiesTestBlockingHumanFeedbackResponseSubmitsNotificationTestThreadlessCompletedSameRouteReusesSessionIDTestThreadlessSessionRestoresDurableBindingAfterManagerRestartTestSlackWorkflowAuthorizationStillUsesRoutePrincipal
These validate selected shared behaviors, not a full connector audit or live Slack/WhatsApp delivery. This consolidation changes documentation only.
Reviewed the connector implementation on main at ca3cf76cb, focusing on
authorization, conversation lifecycle, and Slack event handling. The shared
architecture is reasonable, but the following reproduced bugs prevent sign-off.
All three findings are closed: the P1 and the mention-guard P2 are fixed
(see notes below), and the reset P2 is moot after the removal of session
end commands.
Source line references below refer to the reviewed revision.
Source: bot_connector.go, lines 902–907; route-change detection is at lines 1283–1292.
authorizeWorkflowRouteForMessage overwrites active.RouteKey and workflow
metadata before handleExistingSession snapshots oldRouteKey. The subsequent
comparison therefore sees the incoming route as the existing route and misses
the switch.
Reproduction: create a completed WhatsApp conversation for workflow A, install
a successful workflow-access callback, and send a message routed to workflow B
through HandleIncomingMessage. The new execution receives A's session ID.
This retains conversation history and risks carrying native resume state across
unrelated workflows instead of establishing a new conversation boundary.
Fix: compare the previous and incoming routes before mutating active session
state. Start a fresh conversation when the target changes. Add a regression
through HandleIncomingMessage with authorization enabled: the existing
route-switch test calls handleExistingSession directly and misses this ordering.
Fixed: authorizeWorkflowRouteForMessage now leaves the active session
untouched when the granted route differs from the session's stored route key
on a thread-less platform (routeChangeKeepsSession), so
handleExistingSession sees the switch and starts a fresh conversation. The
access check, msg.PresetWorkflow, and msg.WorkspaceUserID updates are
unchanged, so the fresh session still starts under the incoming route.
Regression coverage: TestThreadlessRouteSwitchViaIncomingMessageStartsFresh
(switch starts fresh, no restored session, preset_query_id targets the new
workflow) and TestThreadlessSameRouteViaIncomingMessageReusesSession
(same-route replies still continue the conversation).
Session end commands (@reset, @done, and aliases) were removed: the
@reset path no longer exists, so this reproduction cannot run. Residual
note: runSession cleanup still persists the binding unconditionally after
cancellation, which also fires for route-change cancels. If a canceled
session's persist ever lands after a newer session's binding, it could
overwrite it; no reproduction exists today.
Source: bot_connector.go, lines 1394–1408; the running-session guard is at lines 1366–1382.
The running-session branch ignores non-mention replies when a thread contains multiple users. The completed/failed branch does not apply that guard and immediately starts another execution with the existing conversation identity.
Reproduction: retain a completed Slack session whose thread history contains
Alice and Bob, then deliver Bob's ordinary reply to Alice with IsMention=false.
The start-session callback fires even though the bot was not addressed.
Fix: apply the multi-user mention policy before restarting completed or failed threaded sessions, while retaining the intended blocking-feedback behavior. Cover both running and completed states in regression tests.
Fixed: the running branch's ignore-with-reaction policy is now a shared
ignoreMultiUserNonMention helper, and the completed/failed branch applies
it before restarting. An outstanding blocking prompt still gets its answer:
like the running branch, awaiting messages route to handleBlockingResponse
first and bypass the guard. Thread-less platforms are unaffected (the helper
never ignores where threads don't exist). Regression coverage:
TestCompletedMultiUserThreadNonMentionStaysOut (the reported bug),
TestCompletedMultiUserThreadMentionRestarts,
TestCompletedSingleUserThreadNonMentionRestarts, and
TestCompletedAwaitingFeedbackBypassesMentionGuard.
- The full existing services suite passed: from
agent_go, rungo test ./cmd/server/services -count=1. - Three temporary regression tests asserted the intended behavior and failed,
confirming each finding:
TestReviewRouteSwitchDoesNotReuseOldConversation,TestReviewResetDoesNotResurrectBinding, andTestReviewCompletedMultiUserThreadRequiresMention. - The temporary tests were removed after reproduction; they are not committed regression coverage. Production code was not changed.
- Live Slack/WhatsApp delivery and the full server test suite were not exercised.
Existing passing tests therefore do not establish correctness for these three lifecycle paths. Reproduce and fix them, then retain regression coverage before closing the findings.
Auto-synced from docs/ on main. Edit there, not here.