Message Gateway
Security — Think Before Enabling
A Message Gateway channel opens an external pathway (Slack DM, GitHub @mention) that can prompt agents holding your worktree’s full surface area: code, MCP servers, env vars, and any credentials in scope. Worth thinking through before you flip it on, but not categorically dangerous.
Things working in your favor:
- Email-based user matching. Inbound Slack messages are aligned to Agor users via the
users:read.emailscope; GitHub mentions are aligned viauser_map(login → email) or the sender’s public GitHub email. When a match exists, the spawned session is attributed to that real Agor user — so prompts run under their identity and audit trail, not as a generic bot. Unmatched senders are rejected (or surfaced to admins). - Encrypted credentials at rest. Bot tokens, app tokens, and private keys are stored encrypted in the daemon database.
- Outbound-only transport. Slack uses Socket Mode and GitHub uses API polling — no public webhook URLs to defend.
- Per-channel agent config. Permission modes, model, and MCP server attachments are scoped to the channel, not global.
Things that still warrant caution:
- Channels open external pathways into agent context. Anyone who can DM the bot (or
@mentionit on a watched repo) can ask the agent to read, write, or execute code on the target worktree. If the channel runs intrustmode with broad MCP access, that pathway is effectively a remote prompt seat — treat it as such. - Restrict who can reach the bot. Limit Slack DM scope (private workspace or app distribution settings); narrow GitHub
watch_reposto repos where mentions are intended. - Use dedicated worktrees. Don’t point channels at worktrees holding production secrets. Create isolated worktrees scoped to gateway use.
- Pick the right permission mode.
supervisedormanualmodes preserve human-in-the-loop on tool calls;trustis fine for bounded, well-understood automations but removes that gate. - Mind the Unix isolation mode. In
simplemode the agent runs as the daemon user; instrictmode it runs as the matched Agor user’s Unix identity, which is what you want when channels span teams. - Rotate tokens. Bot/app tokens and channel keys should be rotated like any other secret.
The mental model: a gateway channel is a remote terminal into an agent that holds the same authority as a human prompting that worktree. Configure who can reach it, what it can do, and which identity it runs as — and the rest is normal Agor surface area.
The Message Gateway connects external messaging platforms to Agor’s agent sessions. Message your Slack bot or mention @agor on a GitHub PR, and Agor spins up a coding session on the right worktree with the right agent — then routes responses back to the platform. Follow-up messages continue the conversation in the same session.
Currently supports Slack and GitHub. Discord, WhatsApp, and Telegram connectors are planned.
What is a Channel?
A Channel is a portal into a worktree. It defines:
- A home worktree — the persistent worktree where every session spawned by this channel will operate. Think of it as a command center, like an assistant worktree that supervises a project.
- An agentic tool configuration — which coding agent to use (Claude Code, Codex, Gemini, OpenCode), which model, permission mode, and which MCP servers to attach.
- Platform credentials — bot token, app token, and other secrets needed to send and receive messages (encrypted at rest).
You can create multiple channels for the same or different worktrees. For example:
- “Optimus Prime” — Claude Code (Opus 4.6) on your main assistant worktree, with Slack + GitHub MCP servers
- “Scout” — Claude Code (Sonnet 4.5) on a lighter review worktree, trust mode, no MCP servers
- “Codex Runner” — Codex on a
superset-frontendworktree for quick JS tasks
Each channel gets a unique channel key (a UUID) that authenticates inbound messages. The Slack integration uses Socket Mode and the GitHub integration uses API polling — both are outbound-only, so no public webhook URLs are needed.
How It Works
When a message arrives from a platform (e.g., a Slack DM or a GitHub @agor mention), the gateway processes it through a simple pipeline:
- Authenticate. The gateway looks up the channel by its key and verifies it’s enabled.
- Map thread to session. If this is a new thread, create a fresh Agor session on the channel’s target worktree with the channel’s agentic tool configuration. If this thread has been seen before, route to the existing session.
- Send prompt. The message text is sent to the session via Agor’s standard prompt flow — task creation, executor spawn, the full pipeline.
- Agent runs. The coding agent (Claude Code, Codex, etc.) processes the prompt with full access to the worktree, MCP servers, and any configured tools.
- Route response back. When the agent produces a response, Agor’s outbound routing hook intercepts it and posts it back into the same platform thread. Formatting is adapted per platform (Slack mrkdwn conversion, GitHub markdown pass-through).
- Repeat. Follow-up messages in the same thread continue the same session — the agent has full conversation history.
Slack DM Agor Daemon Agent
──────── ─────────── ─────
│ │ │
│ "Fix the login bug" │ │
├─────────────────────────────>│ │
│ │ Create session │
│ │ + thread mapping │
│ │ │
│ │ Send prompt │
│ ├──────────────────────────>│
│ │ │
│ │ Agent response │
│ │<──────────────────────────┤
│ "I've fixed the login..." │ │
│<─────────────────────────────┤ │
│ │ │
│ "Can you also add tests?" │ │
├─────────────────────────────>│ │
│ │ Route to same session │
│ ├──────────────────────────>│
│ │ │The outbound routing is a lightweight after-hook on message creation. For sessions without a gateway mapping (the vast majority), it’s a single database lookup that returns immediately — effectively a no-op.
Setting Up Slack Integration
1. Create a Slack App
- Go to api.slack.com/apps and create a new app.
- Under OAuth & Permissions, add the
chat:writebot scope. - Under App Home, enable the Messages Tab and check “Allow users to send Slash commands and messages from the messages tab.”
- Under Event Subscriptions, subscribe to the
message.imbot event. - Under Socket Mode, enable Socket Mode and generate an App-Level Token (
xapp-...). - Install the app to your workspace and note the Bot User OAuth Token (
xoxb-...).
2. Create a Channel in Agor
- Open Settings (gear icon) and navigate to the Gateway tab.
- Click Add Channel.
- Fill in:
- Name — Give it a memorable name (e.g., “Optimus Prime”).
- Channel Type — Select
slack. - Target Worktree — Pick the worktree where sessions will run.
- Bot Token — Paste the
xoxb-...token. - App Token — Paste the
xapp-...token (required for receiving DMs via Socket Mode).
- Optionally expand Agentic Tool Configuration to customize the agent, model, permission mode, and MCP servers for sessions created by this channel.
- Save. The daemon will immediately start a Socket Mode listener for this channel.
3. DM Your Bot
Open Slack, find your bot under Apps, and send it a message. You should see system debug messages confirming session creation, followed by the agent’s response — all in the same DM thread.
Setting Up GitHub Integration
How It Works
When someone mentions @agor (or your app’s name) in a PR or issue comment, Agor picks it up via API polling and creates a session. The session runs on the target worktree, and the agent’s final response is posted back as a PR/issue comment under the app’s bot identity (e.g., agor[bot]).
Key differences from Slack:
- Polling, not WebSocket — Agor polls the GitHub API every 15 seconds (configurable). No public endpoint needed.
- Per-PR/issue sessions — Each PR or issue gets its own session. All
@agormentions within the same PR route to the same session. - Last-message-only responses — Unlike Slack (which streams every message), only the agent’s final response is posted to GitHub. Intermediate progress is visible in the Agor UI.
- Bot identity — Responses are posted as the GitHub App’s bot user, not as any human user.
1. Create a GitHub App
You can create the app through Agor’s setup wizard or manually:
Via Agor UI (recommended):
- Open Settings → Gateway tab → Add Channel.
- Select
githubas the channel type. - Click “Create GitHub App” — this opens GitHub’s App Manifest flow.
- Name your app (default: “Agor”) and click Create.
- Agor captures the credentials automatically.
Manually:
- Go to your GitHub org’s Settings → Developer settings → GitHub Apps → New GitHub App.
- Set permissions:
Contents: Read,Issues: Read & Write,Pull requests: Read & Write,Metadata: Read. - Subscribe to events:
issue_comment,issues,pull_request,pull_request_review,pull_request_review_comment. - Disable webhooks (set
Activeto false) — Agor uses polling. - After creating, generate a private key and note the App ID.
2. Install the App
- Go to your GitHub App’s page → Install App.
- Select your organization and choose which repositories Agor should access.
- Click Install.
3. Create a Channel in Agor
- Open Settings → Gateway tab → Add Channel.
- Fill in:
- Name — e.g., “GitHub: preset-io”
- Channel Type — Select
github. - Target Worktree — Pick the worktree where sessions will run. Ideally, this is an assistant worktree configured for GitHub work.
- App ID — Your GitHub App’s ID.
- Private Key — The PEM private key (encrypted at rest).
- Installation ID — Found via the setup wizard or GitHub’s API.
- Optionally configure:
- Watch Repos — Specific repos to monitor (empty = all repos in the installation).
- Poll Interval — How often to check for new mentions (default: 15 seconds).
- Mention Name — The keyword to trigger on (default:
agor, matches@agor).
- Save. Polling starts immediately.
4. Mention @agor on a PR
Comment @agor review this PR on any PR in a watched repo. Within 15 seconds:
- The comment gets a 👀 reaction (instant feedback).
- A “Processing…” comment appears with a link to the Agor session.
- The agent reviews the PR.
- The “Processing…” comment is replaced with the agent’s response.
Follow-up @agor mentions on the same PR continue the same session — the agent has full conversation history.
Setting Up the Assistant
The gateway routes messages to a session on the target worktree. What the agent does with those messages is up to you — configured via the assistant’s instructions on the target worktree.
A minimal assistant prompt might be:
You handle GitHub mentions for preset-io/agor.
When mentioned on a PR:
- Create a worktree for the PR branch
- Spawn a child agent to review the changes
- The child should comment on the PR when done
- Include links to Agor sessions in your replies
When mentioned on an issue:
- Read the issue and all comments
- Respond with analysis or next stepsWith a capable model (like Claude Opus) and Agor MCP tools available, even a minimal prompt produces good results — the agent figures out how to use worktrees, spawn sessions, and interact with GitHub. Over time, the assistant learns the codebase, team conventions, and review patterns across PRs.
Agentic Tool Configuration
Each channel can define its own agent configuration, overriding the user’s defaults:
| Setting | Description | Default |
|---|---|---|
| Agent | Which coding tool to use (Claude Code, Codex, Gemini, OpenCode) | Claude Code |
| Model | Model alias or specific model ID | User’s default |
| Permission Mode | How the agent handles tool approvals (trust, auto, supervised, manual) | Agent’s default |
| MCP Servers | Which MCP servers the agent can access | None |
The gateway resolves configuration with a fallback chain: channel config > user defaults > system defaults. This means you only need to configure what you want to override.
Architecture
The gateway is built as a set of loosely coupled components:
- Gateway Service (
/gateway) — Orchestrates inbound routing (platform to session) and outbound routing (session to platform). Custom FeathersJS service. - Gateway Channels (
/gateway-channels) — CRUD for channel configurations with encrypted credential storage. - Thread-Session Map (
/thread-session-map) — Persists the mapping between platform thread IDs and Agor session IDs. - Connector Layer — Platform-agnostic
GatewayConnectorinterface with a registry pattern. Currently:SlackConnector(Socket Mode) andGitHubConnector(API polling). Adding a new platform means implementingsendMessage()and optionallystartListening(). - Outbound Hook — FeathersJS
after.createhook on the messages service. Fire-and-forget; never blocks message creation.
Adding a New Platform
The connector interface is intentionally minimal:
interface GatewayConnector {
readonly channelType: ChannelType;
sendMessage(req: { threadId: string; text: string }): Promise<string>;
startListening?(callback: (msg: InboundMessage) => void): Promise<void>;
stopListening?(): Promise<void>;
formatMessage?(markdown: string): string;
}Implement this interface, register it in the connector registry, and the gateway service handles the rest — authentication, thread mapping, session lifecycle, and outbound routing all work automatically.
Best Practices
- Use trust or auto mode for unattended agents. If you want the agent to operate without human approval on each tool call, set the permission mode accordingly.
- Attach relevant MCP servers. A Slack-connected agent that also has GitHub MCP can create PRs, check CI status, and post updates — all from a DM.
- One worktree per concern. Create separate channels for different projects or responsibilities rather than funneling everything through one worktree.
- Name channels descriptively. When you have multiple channels, names like “Frontend Review Bot (Sonnet)” are much more useful than “Channel 1.”
- Keep credentials rotated. Bot tokens and app tokens are encrypted at rest, but rotate them periodically through the channel edit UI.
Troubleshooting
Slack
- “Sending messages to this app has been turned off.” Enable the Messages Tab in your Slack app’s App Home settings, and subscribe to the
message.imbot event. - Bot doesn’t respond to DMs. Check that Socket Mode is enabled, the
app_tokenis set in the channel config, and the daemon logs show[gateway] Socket Mode listener started for channel "...". - Response appears in logs but not in Slack. The
bot_tokenmay lack thechat:writescope, or the bot may not have permission to DM the user. Check Slack app permissions. - Infinite message loop. The connector filters out bot messages (
bot_idorsubtype: bot_message). If you see loops, check that your Slack app’s bot user is properly configured.
GitHub
- Bot doesn’t respond to
@agormentions. Check daemon logs for[github] Watching N reposand[github] Starting poll loop. Verify the App ID, private key, and installation ID are correct. Ensure the app is installed on the org with the right repos selected. - “GitHub App authentication failed.” The private key or app ID may be wrong. Re-check credentials. If you regenerated the private key on GitHub, update it in the channel config.
- Mentions detected but no session created. The
require_mentionsetting may be filtering incorrectly. Check thatmention_namematches your app’s slug (default:agor). Mentions inside code blocks are ignored by design. - Response posted as wrong user. GitHub comments should appear as the app’s bot identity (e.g.,
agor[bot]). If comments appear as a human user, something is bypassing the connector and using a personalGITHUB_TOKENinstead. - Rate limit errors. At 15s polling with 5 repos, you use ~1,200 req/hour out of 5,000. If you’re monitoring many repos, increase
poll_interval_msor usewatch_reposto narrow scope.
General
- Agent session created but no response. Verify that the coding agent (Claude Code, etc.) is authenticated inside the daemon’s environment. Check daemon logs for executor errors.
Related Reading
- Worktree Scheduler — Automate recurring work on worktrees (complements gateway for event-driven vs. time-driven automation).
- Agor MCP Server — Give gateway-spawned agents full self-awareness and orchestration capabilities.
- Agent SDK Comparison — Choose the right coding agent for your channel’s use case.
- Spawned Subsessions — Gateway-created sessions can themselves spawn child sessions for parallel work.