Take your agent-shell sessions anywhere. Chat with your AI agents from your phone or any device.
Pairs well with meta-agent-shell for monitoring and coordinating multiple agents.
| Emacs | Slack (message from phone) | Slack (follow-up from Emacs) |
|---|---|---|
![]() |
![]() |
![]() |
agent-shell-to-go mirrors your agent-shell conversations to external messaging platforms, enabling bidirectional communication. Send messages from your phone, approve permissions on the go, and monitor your AI agents from anywhere.
Currently supported:
- Slack (via Socket Mode)
Planned/possible integrations:
- Matrix
- Discord
- Telegram
- Per-project channels - each project gets its own Slack channel automatically
- Each agent-shell session gets its own thread within the project channel
- Messages flow bidirectionally (Emacs β messaging platform)
- Real-time updates via WebSocket
- Message queuing - messages sent while the agent is busy are queued and processed automatically
- Permission requests with reaction-based approval
- Mode switching via commands (
!yolo,!safe,!plan) - Start new agents remotely via slash commands
- Image uploads - images created anywhere in the project are automatically uploaded to Slack (requires
fswatch) - Error forwarding - agent startup failures and API errors are automatically reported to the Slack thread
- Works with any agent-shell agent (Claude Code, Gemini, etc.)
No one can interact with your agents until you explicitly set an allowlist. Additionally, it is recommended to limit workspace membership to just you and your agent.
You must set an allowlist of Slack user IDs who can interact with your agents:
;; Allow specific users to control agents
(setq agent-shell-to-go-authorized-users '("U01234567" "U89ABCDEF"))
;; Or if you already have user-id configured, reuse it:
(setq agent-shell-to-go-authorized-users (list agent-shell-to-go-user-id))Without this setting, all Slack interactions are ignored.
Unauthorized users:
- Cannot send messages to agent threads (silently ignored)
- Cannot use reactions to approve permissions or control messages
- Cannot use slash commands (get an ephemeral "not authorized" message)
To find your Slack user ID: click your profile β three dots β "Copy member ID".
Anyone authorized can:
- Send prompts to Claude Code running on your machine
- Approve permission requests (file edits, command execution, etc.)
- Start new agent sessions via slash commands
- Run in a VM - For maximum isolation, run Emacs and your agents inside a VM. This limits the blast radius if an agent is compromised or tricked into running malicious commands. Or just YOLO.
- Run particular agents in containers - For agents working on untrusted code or risky tasks, consider running them in containers for additional isolation.
- Limit workspace membership - Only invite trusted people to your Slack workspace. The allowlist protects you, but defense in depth is wise.
- Opt out of Slack's ML training - Slack uses customer data for ML features like emoji/channel recommendations. To opt out, Workspace Owners can email
feedback@slack.comwith subject "Slack Global model opt-out request" and your workspace URL. See Slack's privacy principles. - Keep your Slack tokens secure (treat them like SSH keys)
- Go to https://api.slack.com/apps
- Click "Create New App" β "From an app manifest"
- Select your workspace
- Paste the contents of
slack-app-manifest.yaml - Click "Create"
- Go to "OAuth & Permissions" β "Install to Workspace" β copy the Bot Token (
xoxb-...) - Go to "Basic Information" β "App-Level Tokens" β "Generate Token" with
connections:writescope β copy it (xapp-...) - Get your channel ID (right-click channel β "View channel details" β scroll to bottom)
- Invite the bot to your channel:
/invite @agent-shell-to-go
Skip to Configure credentials.
Click to expand step-by-step guide
-
Create the app
- Go to https://api.slack.com/apps
- Click "Create New App" β "From scratch"
- Name it something like "agent-shell-to-go"
- Select your workspace
-
Enable Socket Mode
- In the sidebar, click "Socket Mode"
- Toggle "Enable Socket Mode" ON
- When prompted, create an app-level token:
- Name it "websocket" (or anything)
- Add the
connections:writescope - Click "Generate"
- Save this token (starts with
xapp-) - you'll need it later
-
Add Bot Token Scopes
- In the sidebar, click "OAuth & Permissions"
- Scroll to "Scopes" β "Bot Token Scopes"
- Add these scopes:
chat:write- send messageschannels:history- read channel messageschannels:read- see channel inforeactions:read- see emoji reactions
-
Subscribe to Events
- In the sidebar, click "Event Subscriptions"
- Toggle "Enable Events" ON
- Expand "Subscribe to bot events"
- Add these events:
message.channels- receive messages in channelsreaction_added- receive emoji reactionsreaction_removed- receive reaction removals (for unhide/re-truncate)
- Click "Save Changes"
-
Add Slash Commands
- In the sidebar, click "Slash Commands"
- Create these commands:
/new-project- Description: "Create a new project and start an agent"/new-agent- Description: "Start new agent in a folder"/new-agent-container- Description: "Start new agent in a container"/projects- Description: "List open projects from Emacs"
-
Install the App
- In the sidebar, click "Install App"
- Click "Install to Workspace"
- Review permissions and click "Allow"
- Copy the "Bot User OAuth Token" (starts with
xoxb-)
-
Set up your channel
- Create a channel or use an existing one (e.g.,
#agent-shell) - Invite the bot: type
/invite @your-bot-namein the channel - Get the channel ID:
- Right-click the channel name β "View channel details"
- Scroll to the bottom and copy the Channel ID (starts with
C)
- Create a channel or use an existing one (e.g.,
These credentials are extremely sensitive. Anyone with these tokens can send messages to your Slack channel - and your Emacs will execute them as agent-shell prompts. Treat them like SSH keys.
(setq agent-shell-to-go-bot-token (string-trim (shell-command-to-string "pass slack/agent-shell-bot-token")))
(setq agent-shell-to-go-channel-id (string-trim (shell-command-to-string "pass slack/agent-shell-channel-id")))
(setq agent-shell-to-go-app-token (string-trim (shell-command-to-string "pass slack/agent-shell-app-token")))(defun my/keychain-get (service account)
(string-trim (shell-command-to-string
(format "security find-generic-password -s '%s' -a '%s' -w" service account))))
(setq agent-shell-to-go-bot-token (my/keychain-get "agent-shell-to-go" "bot-token"))
(setq agent-shell-to-go-channel-id (my/keychain-get "agent-shell-to-go" "channel-id"))
(setq agent-shell-to-go-app-token (my/keychain-get "agent-shell-to-go" "app-token"))
(setq agent-shell-to-go-user-id (my/keychain-get "agent-shell-to-go" "user-id"))To add credentials to Keychain:
security add-generic-password -s "agent-shell-to-go" -a "bot-token" -w "xoxb-your-token"
security add-generic-password -s "agent-shell-to-go" -a "channel-id" -w "C0123456789"
security add-generic-password -s "agent-shell-to-go" -a "app-token" -w "xapp-your-token"
security add-generic-password -s "agent-shell-to-go" -a "user-id" -w "U0123456789"To get your user ID: click your profile in Slack β three dots β "Copy member ID".
Create a .env file (default: ~/.doom.d/.env):
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_CHANNEL_ID=C0123456789
SLACK_APP_TOKEN=xapp-your-app-token
Make sure this file is gitignored if your config is in a repository.
(use-package agent-shell-to-go
:load-path "~/code/agent-shell-to-go"
:after agent-shell
:config
(agent-shell-to-go-setup))Requires the websocket package (available on MELPA).
For automatic image uploads, install fswatch:
brew install fswatch # macOSOnce set up, every new agent-shell session automatically:
- Creates a Slack thread
- Connects via WebSocket for real-time updates
- Mirrors your conversation bidirectionally
You can now chat with Claude (or any agent) from your phone while away from your computer.
Send these in the Slack thread to control the session:
| Command | Description |
|---|---|
!yolo |
Bypass all permissions (dangerous!) |
!safe |
Accept edits mode |
!plan |
Plan mode |
!mode |
Show current mode |
!stop |
Interrupt the agent |
!restart |
Kill and restart agent with transcript |
!queue |
Show pending queued messages |
!clearqueue |
Clear all pending queued messages |
!latest |
Jump to bottom of thread |
!debug |
Show session debug info |
!help |
Show available commands |
Use these anywhere in the channel (not in threads):
| Command | Description |
|---|---|
/new-project <name> |
Create a new project folder (runs setup function if configured) and start an agent |
/new-agent [folder] |
Start a new agent (defaults to channel's project, then configured folder) |
/new-agent-container [folder] |
Start a new agent in a container (like C-u prefix) |
/projects |
List open projects from Emacs (each as a separate message for easy copy) |
React to messages in the thread:
Permission requests:
| Emoji | Action |
|---|---|
| β or π | Allow once |
| π or β | Always allow |
| β or π | Reject |
Message visibility:
| Emoji | Action |
|---|---|
| π or π | Hide message completely (remove to unhide) |
| π | Glance at hidden message (~500 chars, remove to collapse) |
| π | Full read (show complete output, remove to collapse) |
Feedback:
| Emoji | Action |
|---|---|
| β€οΈ (or other hearts) | Send appreciation to the agent ("The user heart reacted to: ...") |
| π | Create an org-mode TODO file from the message (scheduled for today) |
Long messages are automatically truncated to 500 characters with :eyes: _for more_ at the end. Add the π reaction to see the full content.
;; Change the .env file location
(setq agent-shell-to-go-env-file "~/.config/agent-shell/.env")
;; Or set credentials directly (not recommended)
(setq agent-shell-to-go-bot-token "xoxb-...")
(setq agent-shell-to-go-channel-id "C...")
(setq agent-shell-to-go-app-token "xapp-...")
;; Default folder for /new-agent when no folder is specified
(setq agent-shell-to-go-default-folder "~/code")
;; Custom function to start agents (e.g., your own claude-code wrapper)
(setq agent-shell-to-go-start-agent-function #'my/start-claude-code)
;; Custom function to set up new projects (for /new-project command)
;; Called with (PROJECT-NAME BASE-DIR CALLBACK), should call CALLBACK with project-dir when done
(setq agent-shell-to-go-new-project-function #'my/new-python-project)
;; Directory for bookmark TODOs (default: ~/org/todo/)
(setq agent-shell-to-go-todo-directory "~/org/todo/")
;; Image upload rate limit (default: 30 per minute, nil to disable)
(setq agent-shell-to-go-image-upload-rate-limit 30)
;; Hide tool call outputs by default (just show β
/β)
;; Use π/π reactions to expand (default: t shows full output)
(setq agent-shell-to-go-show-tool-output nil)If you see repeated WebSocket closed / WebSocket disconnect requested, reconnecting... messages, Slack is actively rejecting the connection. Common causes:
- Events not enabled - Go to your Slack app settings β "Event Subscriptions" β make sure "Enable Events" is toggled ON
- Missing event subscriptions - Under "Subscribe to bot events", verify you have
message.channels,reaction_added, andreaction_removed - App token expired - Regenerate the app-level token in "Basic Information" β "App-Level Tokens"
To debug, enable logging:
(setq agent-shell-to-go-debug t)Then check *Messages* buffer for agent-shell-to-go: prefixed logs.
If Slack disables your app (you'll get an email), or if events stop arriving after re-enabling:
- Go to Slack app settings β "Event Subscriptions" β re-enable events
- Reconnect the websocket in Emacs:
(agent-shell-to-go--websocket-connect)
The existing websocket connection becomes stale when Slack disables/re-enables events - you need to establish a fresh connection.
If agents show Authentication required or OAuth token has expired errors in the Slack thread, the Claude CLI's OAuth token needs refreshing. Run claude setup-token in a terminal to get a long-lived token, then set it:
export CLAUDE_CODE_OAUTH_TOKEN=<token>Or persist it for Emacs:
;; Save token to ~/.ssh/claude-oauth-token (chmod 600), then:
(setenv "CLAUDE_CODE_OAUTH_TOKEN"
(string-trim (with-temp-buffer
(insert-file-contents "~/.ssh/claude-oauth-token")
(buffer-string))))Note: the regular OAuth token (from claude login) expires frequently. The setup-token long-lived token is more reliable for always-on setups.
If your agent gets stuck when trying to create a file in a directory that doesn't exist, Emacs may be prompting for confirmation. Doom Emacs has a doom-create-missing-directories-h hook that prompts y-or-n-p before creating directories.
To auto-create directories without prompting, add this to your config:
;; Override Doom's directory creation to not prompt (for agent-shell compatibility)
(advice-add 'doom-create-missing-directories-h :override
(lambda ()
(unless (file-remote-p buffer-file-name)
(let ((parent-directory (file-name-directory buffer-file-name)))
(when (and parent-directory (not (file-directory-p parent-directory)))
(make-directory parent-directory 'parents)
t)))))If you chat with Claude a lot, you'll likely hit Slack's free tier message limit (90 days of history). Consider a paid workspace to avoid this.
Unfortunately, Slack doesn't support bulk message deletion via API. The only "fast" cleanup option would be archiving and recreating channels, but bot tokens cannot unarchive channels (requires a user token), which breaks the workflow and causes confusion in mobile apps.
For now, the only cleanup option is agent-shell-to-go-cleanup-old-threads which deletes messages one by one (slow but works).
- Image uploads - images written by the agent are automatically uploaded to Slack
- Bookmarks - bookmark reaction creates org-mode TODO scheduled for today
- Better UTF-8 and unicode handling (now uses curl)
- Per-project channels - each project gets its own Slack channel automatically
- Message queuing - messages sent while agent is busy are queued automatically
- Three-state message expansion - collapsed (icon only), glance (π, ~500 chars), full read (π)
- Cloudflare Worker relay - Slack's Socket Mode requires your laptop to be online; when it sleeps or loses WiFi, Slack accumulates delivery failures and eventually disables the app. A Cloudflare Worker relay would maintain the Slack Socket Mode connection 24/7, queue messages while you're offline, and forward them when Emacs reconnects.
- Matrix integration
- Discord integration
- Telegram integration
Pairs well with meta-agent-shell - A supervisory agent that monitors all your sessions. Search across agents, send messages between them, and manage your fleet of AI agents from Slack.
GPL-3.0


