Run the pi coding agent in Slack. Mention it in a channel or write to it directly: each Slack thread gets its own pi session and its own working directory, kept across restarts.
npx pi-mom-slackInspired by mom, the Slack bot removed from pi-mono on 30 April 2026. Its last release, @mariozechner/pi-mom 0.70.6, is still on npm but tied to the pre-1.0 pi under the old @mariozechner scope. Not affiliated with Earendil.
- One pi session per thread. Follow-ups in the thread keep the context, even after the bot restarts.
- Mentions, direct messages, and plain replies in a thread the bot already answers.
- Files both ways. Attachments land in the thread's working directory (images go to the model when it reads them); the agent sends files back with its
slack_uploadtool. - Visible progress. π on your message, a status line updated as tools run, then β
or
β οΈ . - Commands:
!stop,!status,!model,!help. - Robust by default. A run cut by a restart resumes on its own (at most twice), a Slack connection that stays down makes the process exit for its supervisor to restart it, the startup message says why the bot restarted, the agent's shell never sees the Slack tokens or other credentials, and a second process on the same app is detected.
- Your pi, as configured. Model, credentials and skills come from
~/.pi/agent/, likepiin your terminal.
You need Node.js 22.19 or later, and pi set up on the machine that runs the bot: run npx @earendil-works/pi-coding-agent once and log in with /login (or set your provider's API key).
Create the app from the manifest (opens Slack with everything filled in), pick your workspace, click Next, review, and click Create.
Or by hand: api.slack.com/apps β Create New App β From a manifest β paste manifest.json.
- App token (
xapp-β¦): Basic Information β App-Level Tokens β Generate Token and Scopes, any name, scopeconnections:write. - Bot token (
xoxb-β¦): Install App β Install to Workspace β allow, then copy the Bot User OAuth Token.
In Slack: your profile β β― β Copy member ID (it looks like U012ABCDEF). Only the IDs you list can talk to the bot.
Create a .env file where you run the bot:
SLACK_APP_TOKEN=xapp-β¦
SLACK_BOT_TOKEN=xoxb-β¦
PI_SLACK_ALLOWED_USERS=U012ABCDEF,U034GHIJKLThen:
npx pi-mom-slackThe bot sends you a direct message when it is up. In a channel, invite it with /invite @pi (the name you gave the app; the screenshots use pi-mom-demo), then mention it: @pi write a script thatβ¦. In a direct message, just write.
| You do | The bot |
|---|---|
@pi <request> in a channel |
starts a thread with its own pi session |
| reply in that thread | continues the same session, no mention needed |
| write to it directly | answers in a thread under your message; reply there to continue |
| attach files | saves them in the thread's working directory under files/ |
!stop |
stops the current run |
!status |
idle or running (who, for how long), model, working directory |
!model |
lists the models you have credentials for |
!model sonnet |
switches this thread's model (exact provider/id or a unique part of it) |
!model default sonnet |
also changes pi's default model |
!help |
lists the commands |
| Variable | Default | |
|---|---|---|
SLACK_APP_TOKEN |
required | app-level token, xapp-β¦ |
SLACK_BOT_TOKEN |
required | bot token, xoxb-β¦ |
PI_SLACK_ALLOWED_USERS |
required | Slack member IDs, comma separated |
PI_SLACK_DATA_DIR |
~/.pi/slack |
thread working directories, run markers, state |
PI_SLACK_ENV_FILE |
./.env |
file read for the variables above |
PI_SLACK_DEBUG |
logs the type of each Slack event | |
PI_SLACK_SOCKET_DOWN_LIMIT_MS |
180000 |
if the Slack connection stays down this long, the process exits so a supervisor restarts it |
Variables already set in the environment win over the file. Everything read from the file, plus any variable whose name looks like a credential (TOKEN, SECRET, API_KEY, PASSWORDβ¦), is removed from the environment of the agent's shell. pi itself still reads your provider keys.
The model, thinking level, skills and context files (AGENTS.md) come from pi's own configuration. pi extensions are not loaded, since many expect a terminal.
Keep one process per Slack app: Slack spreads events across connections at random, so a second process with the same tokens silently takes some messages. The bot warns you in a direct message when it sees more than one connection.
On a server you reach over SSH, from the folder that holds your .env:
scripts/deploy-ssh.sh my-server # sends this commit, builds, copies .env, starts a user systemd service
ssh my-server journalctl --user -u pi-mom-slack -fThe server needs Node.js 22.19+, pi logged in, and linger for your user (sudo loginctl enable-linger $USER, once), so the service keeps running after you log out. It needs no GitHub access. Run the script again to update.
Or write the user systemd unit yourself, for example:
# ~/.config/systemd/user/pi-mom-slack.service
[Unit]
Description=pi in Slack
After=network-online.target
[Service]
WorkingDirectory=%h/pi-slack
ExecStart=/usr/bin/env npx --yes pi-mom-slack@0.1.1
Restart=on-failure
[Install]
WantedBy=default.targetsystemctl --user enable --now pi-mom-slack
sudo loginctl enable-linger $USER # keep it running after logoutPin the version and update it on purpose: the startup message lists what changed. pi-mom-slack@latest also works, but then every restart may run a release you have not read, including the automatic pi updates. Under systemd, npx must be on the unit's PATH (with nvm, set Environment=PATH=β¦ to nvm's bin folder), or use scripts/deploy-ssh.sh, which does it for you.
pi-mom-slack is not a port of mom. It keeps the idea (pi in Slack, one command) and builds on the current pi SDK.
| mom 0.70.6 | pi-mom-slack | |
|---|---|---|
| pi | own tools on pi 0.70 (@mariozechner) |
pi's SDK and tools, @earendil-works 1.x, updated with pi |
| Conversation unit | one context per channel, with the channel log | one pi session per thread |
| Configuration | its own folder (~/.pi/mom/) |
your pi: model, credentials, skills, AGENTS.md |
| Sandbox | Docker mode | runs as the bot's user; use a dedicated user or a container |
| Scheduled events | yes (one-shot, periodic, immediate) | not yet |
| Memory files | MEMORY.md, global and per channel |
pi's context files |
| Commands | !stop, !status, !model, !help |
|
| Extending | customTools, commands, hooks |
The agent runs pi's tools, bash included, as the user running the bot, on that machine. Treat access to the bot like shell access: list only people you would give a shell to, and run it under a dedicated user or in a container if the machine holds anything sensitive. Removing credentials from the agent's shell prevents mistakes, not a determined agent.
The package is also a library. createSlackBot takes the same settings as the CLI, plus extension points:
import { defineTool } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
import { configFromEnv, createSlackBot } from "pi-mom-slack";
const bot = createSlackBot({
...configFromEnv(), // tokens, allowed users, data dir (or pass them yourself)
systemNote: "You work for the data team. Answer in English.",
customTools: (thread) => [ // extra pi tools, built per thread
defineTool({
name: "deploy_preview",
label: "Deploy a preview",
description: "Deploy the working directory as a preview.",
parameters: Type.Object({}),
execute: async () => ({ content: [{ type: "text", text: await deploy(thread.workspace) }], details: {} }),
}),
],
commands: { // !version in a thread
version: () => "build 42",
},
hooks: {
beforeRun: ({ text, user }) => text, // return a string to change what the model receives
afterRun: ({ thread }, result, error) => {}, // after each run, successful or not
onStart: ({ reason }) => {}, // once connected
},
});
await bot.start();
// later: await bot.stop("SIGTERM");thread gives key, channel, threadTs, workspace, and post(text) / upload(file, name) for the thread. A complete example: examples/custom-bot.ts.
A daily GitHub Action checks for a new pi release. When the tests pass with it, a new patch version of pi-mom-slack is published; when they fail, pi itself is asked to fix the code and the fix arrives as a pull request reviewed by a human. Nothing is published without green tests. See .github/workflows/release.yml. For a fork: set the OPENROUTER_API_KEY secret (and optionally a PI_MODEL variable, provider/model), and configure npm trusted publishing for release.yml.
npm install
npm test # no network: Slack is faked, pi runs against a local scripted model
npm run check
npm start # runs src/cli.ts directlyMIT












