A self-hosted AI agent that lives in your Slack workspace, powered by Claude via Amazon Bedrock. Digby responds to mentions, maintains persistent memory across conversations, can read and edit files, run shell commands, search the web via MCP, and trigger on a schedule — all from your existing Slack setup. The Slack connection is outbound-only (Socket Mode); the optional extras — the Linear agent's webhook and the wiki — need one public origin, typically a Cloudflare Tunnel.
- Slack bot (Socket Mode) — connects outbound over WebSocket
- Linear agent — (optional) assign tickets to the same agent running in your Slack
- Persistent memory — maintains global
MEMORY.mdand per-channelMEMORY.md/log.jsonl/context.jsonlhistories - Shell and file system tools — persistent
~directory: can clone repos, run apps and tools, and maintain a knowledge base - MCP integrations — plug in any MCP server (Linear, Exa search, and more) via
.pi/mcp.json, or just ask! - Scheduled events — configures itself to trigger on cron schedules: run standup, write Friday release notes, etc.
- Wiki knowledge base — (optional) web UI at
/w/for browsing~as a wiki, gated by Slack OAuth - Serve raw assets — (optional) view HTML and other artifacts directly at
/r/from the agent's~directory, gated by Slack OAuth
Digby is agentic and acts on your behalf. It has a root shell, so everything in the task environment is reachable from it — GH_TOKEN, the ECS task role, your MCP keys. There is no allowlist: anyone who can message it in your workspace can drive it. That is the design; it is meant to be frictionless.
Rules you give it — digby.json, MEMORY.md, the system prompt — are asks, not boundaries. They live on a volume the agent can write to.
So give it capability deliberately:
- Scope the credentials. Use a fine-grained
GH_TOKENcovering only the repos digby should touch, and keep the task role and MCP keys as tight. Assume anything in the environment is one conversation away from being used. - Trust the room. Only expose digby to people you would trust with those credentials directly — that is effectively what you are doing.
Trusted people alone are not sufficient: digby reads untrusted content (web pages, search results, Linear issues), so a hostile page summarised by a colleague you trust is the same attack. Credential scope is the control that holds either way.
Slack (Socket Mode)
└─ pi-digby (Node.js, AWS ECS Fargate)
├─ src/slack/router.ts — event classification and routing
├─ src/agent/setup.ts — AgentSession, tools, MCP runtime
├─ src/persistence/ — log.jsonl, context.jsonl, MEMORY.md
├─ src/events/ — scheduled event watcher
└─ /data (AWS EFS) — persistent storage across deploys
pi-digby is a single Node process. Each Slack channel runs in its own "lane" with a FIFO queue; threads get separate agent session directories. The LLM is Claude Sonnet (via Amazon Bedrock) with tool use. MCP servers are loaded at startup from .pi/mcp.json in the working directory.
- AWS account with admin credentials for the one-time stack creation (after that, deploys go through a scoped OIDC role)
- Slack workspace where you can create and install apps
- GitHub account to fork this repository — deploys run from GitHub Actions
- Node.js 20+ — local development only
All infrastructure lives in us-east-1.
Start here — it is the only step with an approval wait. In the AWS console: Amazon Bedrock → Model access (region us-east-1) → request access to Anthropic Claude Sonnet 4.6. The bot invokes the us. cross-region inference profile, which can route to us-east-2 and us-west-2, so enable access there too. Approval is usually quick, but not instant.
api.slack.com/apps → Create New App → From an app manifest → paste deploy/slack-app-manifest.yml.
Then collect the two tokens:
- Basic Information → App-Level Tokens → generate one with the
connections:writescope →DIGBY_SLACK_APP_TOKEN(xapp-…) - Install App → install to your workspace →
DIGBY_SLACK_BOT_TOKEN(xoxb-…)
Fork this repository, then in your fork:
- Enable workflows — the Actions tab on a fresh fork asks you to enable them.
- Add Actions variables (Settings → Secrets and variables → Actions → Variables):
| Variable | Required | Description |
|---|---|---|
AWS_ACCOUNT_ID |
Yes | Your 12-digit AWS account ID |
WIKI_BASE_URL |
No | Public origin the wiki is served from, e.g. https://digby.example.com (no trailing slash). Leave unset to run without the wiki — see Wiki setup. |
DD_SITE |
No | Datadog site, if you use Datadog (defaults to datadoghq.com) |
The deploy workflow authenticates to AWS with OIDC — no long-lived AWS keys in GitHub. The stack in the next step creates both the identity provider and the deploy role, trust-scoped to your fork.
aws cloudformation deploy \
--profile YOUR_AWS_PROFILE \
--region us-east-1 \
--stack-name pi-digby \
--template-file deploy/cloudformation.yml \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--parameter-overrides GitHubRepository=YOUR_GITHUB_USER/pi-digbyThis creates: VPC, ECS Fargate cluster/service, EFS volume, ECR repository, IAM roles, Secrets Manager secret, and CloudWatch log group. The service is created with zero running tasks — there is no image in ECR yet; the first CI deploy (step 6) builds one and starts the bot.
If your AWS account already has the GitHub Actions OIDC provider (accounts can only have one), add CreateOIDCProvider=false to the --parameter-overrides.
Copy deploy/secrets.example.json, fill in the two Slack tokens from step 2, and upload it:
cp deploy/secrets.example.json /tmp/pi-digby-secrets.json
# edit /tmp/pi-digby-secrets.json, then:
aws secretsmanager put-secret-value \
--profile YOUR_AWS_PROFILE \
--region us-east-1 \
--secret-id pi-digby/env \
--secret-string file:///tmp/pi-digby-secrets.jsonLeave the keys you don't use as empty strings — every key must be present (ECS fails the task launch on a missing key), but empty values just disable their feature. See the secrets table for what each optional key enables.
In your fork: Actions → Deploy → Run workflow (on main). This builds the Docker image, pushes it to ECR, and rolls the ECS service. From here on, every push to main deploys automatically.
Invite @digby to any channel you want it to monitor. Mention it to trigger a response.
The bot serves a read-only wiki of its memory and channel notes at /w/*, behind sign-in-with-Slack. It is off by default. The bot boots and works normally in Slack without it — the wiki is extra capability, not a requirement.
Turning it on takes four things, and all of them must be in place or the wiki stays off (the startup log names anything missing):
1. A public origin. The container listens on port 8080 but has no public ingress — the ECS security group is egress-only by design. Expose it with a Cloudflare Tunnel: create a tunnel, add a public hostname routing to http://localhost:8080, and put the tunnel token in pi-digby/env as CLOUDFLARE_TUNNEL_TOKEN. entrypoint.sh starts cloudflared whenever that token is present. Any other reverse proxy works too.
2. Slack OAuth redirect URL. On your Slack app (api.slack.com → OAuth & Permissions), add a redirect URL of <your origin>/auth/slack/callback, for example https://digby.example.com/auth/slack/callback. Slack only honours redirect URLs registered here, so this must match exactly.
3. The wiki secrets in pi-digby/env. put-secret-value replaces the whole secret, so don't upload just these four keys — fill them in alongside everything else in your secrets file from Quick start step 5 and re-upload the complete file:
aws secretsmanager put-secret-value \
--secret-id pi-digby/env \
--secret-string file:///tmp/pi-digby-secrets.jsonThe client ID and secret are on your Slack app's Basic Information page. DIGBY_SLACK_TEAM_ID is the ACL: only members of that workspace can sign in.
4. The WIKI_BASE_URL Actions variable, set to the same origin as step 1 with no trailing slash (https://digby.example.com). This is the one piece the app cannot work out for itself. It is used to build the OAuth redirect_uri, which must match what Slack has registered, and a Cloudflare Tunnel token carries no hostname — the routing lives in Cloudflare's config, not in the container.
All secrets are stored in AWS Secrets Manager as pi-digby/env (JSON). The ECS task reads them at startup.
| Key | Required | Description |
|---|---|---|
DIGBY_SLACK_APP_TOKEN |
Yes | Slack app-level token (xapp-…) for Socket Mode |
DIGBY_SLACK_BOT_TOKEN |
Yes | Slack bot OAuth token (xoxb-…) |
EXA_API_KEY |
No | Exa search API key (for the Exa MCP server) |
GH_TOKEN |
No | GitHub token for repo operations. Use a fine-grained token scoped to only the repos digby should touch — see Security model. |
BROWSER_USE_API_KEY |
No | browser-use cloud API key |
DD_API_KEY |
No | Datadog API key |
DD_APP_KEY |
No | Datadog application key |
LINEAR_API_KEY |
No | Linear API key (enables Linear agent) |
LINEAR_WEBHOOK_SECRET |
No | Linear webhook secret (enables Linear agent). Point Linear's webhook at <your origin>/webhooks/linear — needs the same public origin as Wiki setup step 1. |
DIGBY_COOKIE_SECRET |
No | 32-byte hex secret for wiki session cookies (openssl rand -hex 32) |
DIGBY_SLACK_CLIENT_ID |
No | Slack OAuth client ID (enables wiki sign-in) |
DIGBY_SLACK_CLIENT_SECRET |
No | Slack OAuth client secret (enables wiki sign-in) |
DIGBY_SLACK_TEAM_ID |
No | Slack workspace ID (T…) — wiki ACL |
CLOUDFLARE_TUNNEL_TOKEN |
No | Cloudflare Tunnel token — exposes the wiki without a public load balancer |
Note: Bedrock authentication uses the ECS task IAM role — no
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYneeded for the bot itself.
DIGBY_WIKI_BASE_URL is not a secret and does not belong in pi-digby/env. It is deployment config: set the WIKI_BASE_URL Actions variable and the deploy workflow passes it to CloudFormation as the WikiBaseUrl stack parameter, which becomes the container's DIGBY_WIKI_BASE_URL. Deploying by hand? Pass --parameter-overrides WikiBaseUrl=https://digby.example.com.
Configure MCP servers in .pi/mcp.json inside your working directory (/data on EFS). Example:
{
"mcpServers": {
"my-server": {
"url": "https://your-mcp-server.example.com/mcp",
"auth": "oauth"
}
}
}The repo ships with Linear and Exa MCP servers pre-configured as examples.
npm install
npm run check # lint + typecheck
npm run build # compile to dist/
npm test # run testsTo run the bot locally, set DIGBY_SLACK_APP_TOKEN and DIGBY_SLACK_BOT_TOKEN in your environment and point it at a local working directory:
node dist/main.js /path/to/working-dirdocs/deployment.md— full deployment guide, manual operations, secrets managementdocs/architecture.md— runtime model, lanes, message history vs agent contextdocs/aws-migration.md— infrastructure design decisions
PRs welcome — see CONTRIBUTING.md. Please open an issue first for significant changes.
MIT — see LICENSE.