Skip to content

brainwavesio/pi-digby

Repository files navigation

pi-digby

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.

What it does

  • 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.md and per-channel MEMORY.md / log.jsonl / context.jsonl histories
  • 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

Security model

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_TOKEN covering 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.

Architecture

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.

Prerequisites

  • 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

Quick start

All infrastructure lives in us-east-1.

1. Enable Bedrock model access

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.

2. Create the Slack app

api.slack.com/appsCreate New AppFrom an app manifest → paste deploy/slack-app-manifest.yml.

Then collect the two tokens:

  • Basic Information → App-Level Tokens → generate one with the connections:write scope → DIGBY_SLACK_APP_TOKEN (xapp-…)
  • Install App → install to your workspace → DIGBY_SLACK_BOT_TOKEN (xoxb-…)

3. Fork and configure GitHub Actions

Fork this repository, then in your fork:

  1. Enable workflows — the Actions tab on a fresh fork asks you to enable them.
  2. 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.

4. Deploy the infrastructure

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-digby

This 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.

5. Set the secrets

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.json

Leave 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.

6. Run the first deploy

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.

7. Invite the bot

Invite @digby to any channel you want it to monitor. Mention it to trigger a response.

Wiki setup (optional)

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:

// in your copy of deploy/secrets.example.json:
"DIGBY_COOKIE_SECRET": "<openssl rand -hex 32>",
"DIGBY_SLACK_CLIENT_ID": "...",
"DIGBY_SLACK_CLIENT_SECRET": "...",
"DIGBY_SLACK_TEAM_ID": "T..."
aws secretsmanager put-secret-value \
  --secret-id pi-digby/env \
  --secret-string file:///tmp/pi-digby-secrets.json

The 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.

Environment variables / secrets

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_KEY needed 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.

MCP servers

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.

Local development

npm install
npm run check    # lint + typecheck
npm run build    # compile to dist/
npm test         # run tests

To 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-dir

Documentation

Contributing

PRs welcome — see CONTRIBUTING.md. Please open an issue first for significant changes.

License

MIT — see LICENSE.

About

Your friendly neighbourhood Digby who gets shit done.

Resources

License

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Contributors