Skip to content

Latest commit

 

History

History
259 lines (191 loc) · 10.7 KB

File metadata and controls

259 lines (191 loc) · 10.7 KB

Babbla — Deployment Runbook

This document covers running Babbla as an always-on service: locally (no container), locally via Docker Compose, and on Eyevinn OSC for persistent hosting.


What runs

Babbla is a single long-lived Python process (babbla.app). It connects to Slack over Socket Mode, which means it opens an outbound WebSocket — no inbound port, no load balancer, no TLS termination required. State (conversation history, per-thread context) is stored in a SQLite database at the path set by BABBLA_DB (default babbla.db beside the working directory; on the container it is on a named Docker volume at /state/babbla.db).

A separate one-shot command (babbla.digest --once) generates and posts the weekly digest; it exits after sending and can be triggered by a cron job or a scheduled container run.


Required env / secrets

Variable Required Purpose
SLACK_BOT_TOKEN Yes Slack bot token (xoxb-…)
SLACK_APP_TOKEN Yes Slack app-level token (xapp-…, Socket Mode)
GITHUB_TOKEN Yes Fine-grained PAT, read-only, scoped to the target repo
ANTHROPIC_API_KEY Optional locally / required on OSC Claude API key; omit on a developer laptop to use a Claude subscription (Path B)
BABBLA_GITHUB_MCP Set to binary in the image Selects the bundled github-mcp-server binary instead of pulling via Docker

All secrets are injected at runtime — never baked into the image or committed to the repository.

Model and effort tuning

Babbla has two tiers: the Ask tier (interactive Asks, scheduled digests, quiz, and ADR runs) and the Classifier tier (lobby routing and personal-intent — pure label-emitters). Set BABBLA_MODEL to change the shared default for both tiers; existing deployments that don't set it are unchanged. Each tier can then be tuned independently with four optional knobs:

BABBLA_ASK_MODEL=...            # overrides BABBLA_MODEL for the Ask tier
BABBLA_ASK_EFFORT=high          # low|medium|high|xhigh|max
BABBLA_ASK_FALLBACK_MODEL=...
BABBLA_ASK_MAX_TURNS=16
BABBLA_ASK_MAX_BUDGET_USD=2.0

BABBLA_CLASSIFIER_MODEL=...     # overrides BABBLA_MODEL for the Classifier tier
BABBLA_CLASSIFIER_EFFORT=low
BABBLA_CLASSIFIER_FALLBACK_MODEL=
BABBLA_CLASSIFIER_MAX_TURNS=1
BABBLA_CLASSIFIER_MAX_BUDGET_USD=

All settings are optional and inert until set. See .env.example for the full commented block. Run babbla-doctor (or python -m babbla.doctor) to see the resolved tiers.


GITHUB_TOKEN scopes

The token must be a fine-grained Personal Access Token scoped to the target repository (or organisation) with the following read-only permissions:

Scope Needed for
contents (read) Fetching files, commits, and branches
metadata (read) Repository metadata (always required for fine-grained PATs)
actions (read) Workflow run digests (deploy digest type only)

For a plain Q&A or branch-type digest, contents + metadata is sufficient. Add actions:read only if you configure a deploy-type channel in config/channels.yaml.


Local run (no container, no API key)

This is the fastest path on a developer laptop that already has a Claude Code CLI subscription. The subscription credential is used automatically — no ANTHROPIC_API_KEY needed.

# 1. Install dependencies (once)
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"

# 2. Load secrets from .env
set -a && source .env && set +a

# 3. Start the always-on bot
.venv/bin/python -m babbla.app

# 3b. (Separate terminal) Run a one-shot digest manually
.venv/bin/python -m babbla.digest --once

config/channels.yaml maps Slack channel IDs to GitHub projects. Edit it to point at your target repo before starting.


Local container (Docker Compose)

The docker-compose.yml at the repo root builds the image locally (which includes the bundled github-mcp-server binary) and injects secrets from .env:

docker compose up --build

The compose file mounts ./config as read-only at /data (so channels.yaml is picked up) and stores the SQLite database on a separate named volume (babbla-state) at /state/babbla.db. This keeps the config mount read-only while giving SQLite a writable location.

To tail logs: docker compose logs -f babbla

To stop: docker compose down (the babbla-state volume persists; add -v to also remove it)


OSC deploy (Eyevinn Open Source Cloud)

The steps below use placeholder values; substitute your real project/service identifiers.

1. Build and push the image

docker build -t ghcr.io/<org>/babbla:latest .
docker push ghcr.io/<org>/babbla:latest

2. Create an always-on instance

In the OSC console (or via the OSC CLI), create an Always-On service instance using the pushed image. Always-On instances restart automatically on failure — no external watchdog needed.

3. Inject secrets

Configure the following environment variables in the OSC service settings (not in source control):

SLACK_BOT_TOKEN=xoxb-…
SLACK_APP_TOKEN=xapp-…
GITHUB_TOKEN=github_pat_…
ANTHROPIC_API_KEY=sk-ant-…
BABBLA_GITHUB_MCP=binary
BABBLA_CONFIG=/data/channels.yaml
BABBLA_DB=/state/babbla.db

4. Attach persistent volumes

Attach two volumes to the service:

Mount path Purpose
/data channels.yaml configuration (read-only recommended)
/state SQLite database (babbla.db) — must be writable

Upload your config/channels.yaml to the /data volume before the first start. The Slack channel mapping must include your channel IDs (C0XXXXXXXXX) and GitHub repo targets.

5. Confirm one instance stays running

After deploy, verify in the OSC console that exactly one instance shows status Running. Check the service logs for the [APP] connected to Slack line. Send a test mention in the configured channel (C0XXXXXXXXX) and confirm a reply arrives.


Personal subscriptions

Users manage their subscriptions by DMing Babbla in plain language — "follow MyTV", "what am I following?", "make my digest weekly", "only show me security in MyTV". This works out of the box; no Slack-app change is required (the natural-language intent classifier is always wired).

The Personal Digest (delivered by DM) additionally requires a personal_digest: block in config/channels.yaml (see the commented example there).

Optional /babbla slash command. The code also ships an equivalent /babbla subscribe|unsubscribe|list|digest handler, but it does not fire unless you register a /babbla slash command in the Slack app (Socket Mode, no request URL; adds the commands scope). It is redundant with the plain-language DM path above, so registering it is optional.

files:write scope (per-project skills): if any project binding declares a skills: list, the bot token additionally needs the files:write OAuth scope. Babbla uses it to upload skill-produced artifacts back to the asking surface via files_upload_v2. Without this scope the ask still answers normally — the artifact upload degrades to a logged no-op rather than a failed interaction.

groups:read scope (private personal subscriptions, ADR 0017): a user may follow / DM-ask / topic-filter / receive-in-digest a private project only while they are a live member of its bound private channel. Babbla verifies this with a Slack conversations.members read, which needs the groups:read OAuth scope to see private-channel membership (Babbla must also be a member of the channel — it already is, to receive mentions). The check is fail-closed: without this scope (or on any lookup error) the membership probe returns "not a member", so private content is simply withheld — never leaked. Public/internal projects are unaffected and incur no membership lookup.


Running Babbla in Docker (and per-project skills)

The Agent SDK always drives the claude CLI subprocess — true for plain Q&A and for skills alike. So per-project skills add no new runtime; they reuse the same SDK→CLI path. Most of the container checklist is required for any Babbla deploy, not just skills:

Required for all of Babbla (not skills-specific):

  • Bundle the claude CLI in the image (the SDK shells out to it; it is not pure-Python). Pin a version compatible with the installed claude-agent-sdk.
  • Auth, one of: mount the Path-B subscription credentials into the container's $HOME/.claude (read-only is fine), or set ANTHROPIC_API_KEY (Path A — already supported; ANTHROPIC_API_KEY is intentionally optional in app.py).
  • GitHub MCP launcher: set BABBLA_GITHUB_MCP=binary and install the github-mcp-server binary in the image, so Babbla does not try to docker run the MCP server from inside its own container (which would need Docker-in-Docker). The skilled path reuses the same mcp_servers, so this one setting covers both.
  • Persist CLAUDE_CONFIG_DIR (default ~/.claude) on a writable volume that survives restarts: thread-scoped conversation resume (ADR 0013) reads the CLI's session transcripts from there. This matters for skilled and non-skilled threads.

Skills-specific (small):

  • Writable scratch: skills write to a per-thread scratch dir under $TMPDIR. On a --read-only container, mount a tmpfs and point TMPDIR at it. The scratch is wiped per ask, so it can be fully ephemeral.
  • Bake config/skills/ into the image (like config/channels.yaml), or set BABBLA_SKILLS_POOL to a mounted path. An unknown skill name fails fast at config load, so a missing pool is loud, not silent.
  • Slack files:write scope (above) for artifact upload.

Isolation gets better in a container: a clean image has no operator ~/.claude/CLAUDE.md or user-global skills, so the skilled path's setting_sources=["project"] from a fresh scratch is airtight by construction.


Auth note: subscription vs. API key

On a developer laptop, Babbla can authenticate to Claude through the Claude Code CLI subscription (Path B) without any ANTHROPIC_API_KEY. This works because the Claude Agent SDK falls back to the local CLI credential when no API key is present.

This credential is laptop-bound — it lives in the local CLI session and cannot be transferred to a server. For OSC (or any remote host), you must inject a dedicated ANTHROPIC_API_KEY as a secret. Using the subscription credential as a server secret (e.g., by exporting the CLI token) is a fragile interim that will break on token rotation and is not supported.

In short: local development → subscription (no key needed); OSC/server → inject ANTHROPIC_API_KEY.