Skip to main content

Team Activity Digest

Overview

The digest feature generates a daily summary of a GitHub repository's activity and posts it to a Slack channel. It reads pull requests and commits from the previous day, summarises each contributor's work using an LLM, and surfaces highlights and potential blockers in a structured Slack message.

It can be triggered in two ways:

  • Automatically via a configurable cron schedule (opt-in per application)
  • Manually via the /digest Slack slash command (restricted to allowed users)

Architecture

Digest flow pipeline

Slash command flow (with SLACK_BOT_TOKEN configured in Core):

  1. Core receives /digest, posts "Generating digest for X…" as a public message, captures thread_ts
  2. Enqueues SQS task with callbacks.slack.{ channelId, threadTs }
  3. Returns immediately (empty 200 to Slack)
  4. Worker runs, orchestrator replies in the thread via chat.postMessage + thread_ts

Cron flow (no slash command involved):

  1. DigestScheduler fires based on the configured schedule
  2. Core posts "Generating digest for X…" to slackChannelId (bot token required)
  3. Enqueues SQS task with callbacks.slack.{ channelId } — no threadTs, no responseUrl
  4. Worker runs, orchestrator posts the result to channelId via chat.postMessage

Configuration

Digest is configured per application in config.json (or S3 equivalent). applyDigest is a field inside the digest object:

{
"projects": {
"my-project": {
"applications": {
"my-app": {
"repo": "owner/repo",
"slackChannelIds": ["C0123ABCD"],
"digest": {
"applyDigest": true,
"slackChannelId": "C0123ABCD",
"timezone": "America/New_York",
"scheduleHour": 9,
"scheduleDays": ["Mon", "Tue", "Wed", "Thu", "Fri"],
"verbosity": "detailed",
"cronEnabled": false,
"allowedSlackUserIds": ["U0123ABCD", "U0456EFGH"]
}
}
}
}
}
}

Fields

FieldTypeRequiredDefaultDescription
slackChannelIds (app, top-level)string[]No[]Channel IDs for inbound Slack command routing (C...). Not part of digest.
digest.applyDigestbooleanYesEnables digest for this application. Must be true. Lives inside the digest object.
digest.slackChannelIdstringYesTarget channel for cron-triggered digests. Accepts channel IDs (C...) or names (#general).
digest.timezonestringYesIANA timezone string (e.g. "America/New_York"). Used for schedule and date calculations.
digest.scheduleHournumberYesHour of day to run the cron (0–23, in timezone).
digest.scheduleDaysstring[]No["Mon"…"Fri"]Days of the week to run the cron. Accepts Mon, Tue, Wed, Thu, Fri, Sat, Sun.
digest.verbosity"brief" | "detailed"No"detailed"Controls LLM summarisation depth.
digest.cronEnabledbooleanNofalseSet to true to activate automatic cron scheduling. Cron is disabled by default.
digest.allowedSlackUserIdsstring[]NoSlack user IDs allowed to run /digest manually. If absent or empty, no one can trigger the command.

Access control

The /digest slash command is gated by digest.allowedSlackUserIds:

  • List absent or empty → command is blocked for everyone
  • List set → only listed users can trigger it, in any environment

To find a user's Slack ID: open their profile in Slack → menu → Copy member ID (format: U0XXXXXXX).


Environment Variables

Core

VariableRequiredDescription
SLACK_BOT_TOKENYes (for threading)Bot token used to post the "Generating digest…" message and create the reply thread. Without it, delivery falls back to response_url.
DIGEST_SQS_QUEUE_URLYesSQS queue URL for digest tasks.

Orchestrator

VariableRequiredDescription
SLACK_BOT_TOKENYesBot token used to post the final digest result. Must be the same app token as Core's.
DIGEST_WORKER_TASK_DEFINITIONYes (non-local)ECS task definition for the digest worker.
DIGEST_WORKER_CONTAINER_NAMENoContainer name override. Defaults to "worker".

Worker (workers/digest)

VariableRequiredDefaultDescription
TASK_IDYesTask ID from Redis. Set by ECS env or manually for local runs.
ORCHESTRATOR_URLYesURL of the orchestrator service.
GITHUB_TOKENYesGitHub personal access token or App token for fetching activity.
DIGEST_OPENAI_API_KEYYesOpenAI API key for LLM summarisation.
DIGEST_MAX_TOKENS_PER_UNITNo8000Token budget per contributor before diff truncation.
DIGEST_PARALLEL_LIMITNo5Max parallel contributor summaries.

Testing the Feature End-to-End

Prerequisites

Make sure the following are running locally:

  1. LocalStack (SQS):

    cd .local-aws
    docker compose up -d
  2. Core:

    cd core
    make dev

    Required .env entries:

    ENV=local
    SLACK_SIGNING_SECRET=...
    SLACK_BOT_TOKEN=xoxb-...
    DIGEST_SQS_QUEUE_URL=http://localhost:4566/000000000000/alakai-tasks
    AWS_ACCESS_KEY_ID=test
    AWS_SECRET_ACCESS_KEY=test
  3. Orchestrator:

    cd orchestrator
    make dev

    Required .env entries:

    ENV=local
    PORT=3001
    SLACK_BOT_TOKEN=xoxb-... # same token as Core
    SQS_QUEUE_URL=http://localhost:4566/000000000000/alakai-tasks
    REDIS_URL=redis://localhost:6379
    AWS_ACCESS_KEY_ID=test
    AWS_SECRET_ACCESS_KEY=test
    ORCHESTRATOR_URL=http://localhost:3001
  4. Redis: must be reachable at REDIS_URL.


Path A — Slash command (thread reply)

  1. Add your Slack user ID to digest.allowedSlackUserIds in config.json
  2. Restart Core
  3. Run /digest project=my-project [app=my-app] date=YYYY-MM-DD from any Slack channel the bot is in
  4. Slack shows a public "Generating digest for …" message
  5. Watch Orchestrator logs for the new task ID
  6. Update workers/digest/.env with TASK_ID=<uuid>
  7. Run the worker:
    cd workers/digest
    make run-once
  8. The digest appears as a thread reply in Slack

Path B — Slash command via Makefile (no Slack app needed)

cd core
make call-slack-digest project='my-project' date='YYYY-MM-DD'

Then follow steps 5–8 from Path A above.


Path C — Cron simulation (standalone channel message)

  1. Ensure digest.slackChannelId in config.json is set to a real channel the bot is in
  2. Trigger the cron simulation:
    cd core
    make call-digest-cron repo='owner/repo' date='YYYY-MM-DD'
  3. Watch Orchestrator logs for the task ID, then run the worker:
    cd workers/digest
    # update TASK_ID in .env
    make run-once
  4. The digest appears as a new standalone message in the configured channel

Verifying Redis state

redis-cli -u $REDIS_URL get "${REDIS_KEY_PREFIX}:<taskId>"

The JSON value contains payload (repo, date, digestConfig) and callbacks (channelId, threadTs, etc.).


Common issues

SymptomLikely causeFix
channel_not_found from orchestratorWrong SLACK_BOT_TOKEN in orchestrator (different app than Core)Use the same xoxb-… token in both services
Worker logs task not found in RedisWrong REDIS_KEY_PREFIX or mismatched TASK_IDCheck REDIS_KEY_PREFIX matches between orchestrator and worker
You don't have permission in SlackUser ID not in digest.allowedSlackUserIdsAdd the user's Slack ID to config.json
Cron never firesdigest.cronEnabled: falseSet digest.cronEnabled: true and restart Core
SQS credential errorsMissing LocalStack dummy credentialsSet AWS_ACCESS_KEY_ID=test and AWS_SECRET_ACCESS_KEY=test