Skip to content

flex3-org/baton

Repository files navigation

🏃 Baton — continuity, handled

Baton is a Slack agent that keeps work moving while someone is out of office (OOO). Built for the Slack Agent Builder Challenge with Bolt for Python + Google Gemini.

When someone goes OOO, Baton:

  1. Handoff brief (F1) — reconstructs their open loops from live Slack context: threads awaiting their reply, promises they made, and decisions blocked on them. Renders a Block Kit brief assigned to a cover person.
  2. Live coverage (F2) — while they're out, when someone @mentions them in a channel, Baton replies in-thread: either answering from the person's own message history (with a cited link) or routing to the cover person with a context card.
  3. Re-entry brief (F3) — when they're back, a short streamed digest: what was decided, what the cover person handled, and the top 3 things needing them today.

Architecture

                         Slack (Socket Mode, xoxb + xapp)
                                     │
        ┌────────────────────────────┼─────────────────────────────┐
        │                            │                             │
  slash commands              message events                interactive
  /baton-handoff        (mention scan for OOO user)         actions / modal / Home
  /baton-back                       │                             │
        └──────────────┬────────────┴───────────────┬─────────────┘
                       ▼                             ▼
                 listeners/*  ──────────────►  flows.py  (orchestration)
                                                    │
             ┌──────────────────────┬───────────────┼───────────────┐
             ▼                      ▼               ▼               ▼
        core/retriever        core/heuristics   core/llm        db/store
     RTS │ History (flag)     (pure regex)     (Gemini,        (sqlite3:
     assistant.search.context  no Slack        JSON-only)      handoffs,
       → replies/history                                        open_loops,
                                                                coverage_log)
                       │
                       ▼
                 surfaces/blocks.py + copy.py  (all Block Kit + copy)

Design principle: core/ modules are Slack-free and unit-testable. flows.py is the only place core logic, Slack posting, and the DB meet. All user-facing copy and emoji live in surfaces/copy.py.

Repo layout

app.py                       # entrypoint — Socket Mode Bolt app
manifest.json                # app config: commands, events, scopes, AI feature
.slack/hooks.json            # optional: `slack run` support
src/baton/
  config.py                  # env + tunable constants (caps, thresholds, weights)
  context.py                 # AppContext: config, store, gemini, retriever, name cache
  flows.py                   # F1/F2/F3 orchestration (core → Slack → DB)
  util.py                    # pure helpers (links, mention parsing, dates)
  core/
    models.py                # Msg, Snippet, Candidate
    retriever.py             # Retriever protocol + RtsRetriever + HistoryRetriever
    heuristics.py            # pure pre-filter regex + awaiting-reply logic
    llm.py                   # Gemini calls + defensive JSON parsing + prompts
    openloops.py             # F1 pipeline + ranking
    coverage.py              # F2 answer/route decision
    briefs.py                # F3 re-entry payload builder
  surfaces/
    blocks.py                # every Block Kit builder
    copy.py                  # all copy + emoji vocabulary
  db/
    schema.sql               # 3 tables
    store.py                 # typed sqlite3 helpers
scripts/seed.py              # demo seeder
tests/                       # pytest units

Setup

1. Install dependencies

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

2. Create the Slack app from the manifest

  • Slack CLI: slack create baton --template <this repo> then it reads manifest.json, or
  • Manually: https://api.slack.com/appsCreate New AppFrom a manifest → paste manifest.json.

Then:

  • Basic Information → App-Level Tokens: create a token with connections:write (this is your SLACK_APP_TOKEN, xapp-…).
  • OAuth & Permissions: install to your workspace → copy the Bot User OAuth Token (SLACK_BOT_TOKEN, xoxb-…).
  • Invite the bot to the channels it should watch: /invite @Baton.

3. The user token (for RTS only)

assistant.search.context (Real-Time Search) must be called with a user token (xoxp-…) carrying search:read.public (+ the other search:read.* scopes for IMs/private/files). This comes from the OOO user authorizing the app's user scopes during OAuth. After install, copy the User OAuth Token into SLACK_USER_TOKEN.

If you don't set this up, leave RETRIEVER=history (the default) — Baton works fully without a user token by scanning conversations.history over the bot's channels.

4. Gemini key

Get an API key from Google AI StudioGEMINI_API_KEY. Default model is gemini-flash-latest (override with GEMINI_MODEL; gemini-3.5-flash also works). Note gemini-2.5-flash is deprecated for new keys.

5. Environment

cp .env.sample .env   # then fill in the tokens/keys

Running

Primary (reliable):

source .venv/bin/activate
python3 app.py

Via Slack CLI:

slack run     # uses .slack/hooks.json + slack-cli-hooks

On boot Baton applies the SQLite schema, selects a retriever from RETRIEVER, and opens a Socket Mode connection. Open the Baton app's Home tab to see the idle state.


Seeding a demo

python3 scripts/seed.py --user U0123ABC        # the OOO user's id

The seeder posts a scripted set into SEED_CHANNELS: 3 awaiting-reply threads, 2 promises, 2 blocked decisions, and 2 already-resolved loops — then prints a checklist.

Author identity matters. Baton keys promises/resolved loops on the message author. Bot-posted messages are authored by the bot, so:

  • Set SEED_AS_USER_TOKEN to post those as the real OOO user, or
  • Leave it unset and the seeder prints the exact texts to hand-post as that user (post the fulfillment replies in-thread).

Then run /baton-handoff @cover until:2026-07-20 — the brief should contain the seeded loops and exclude the 2 resolved ones (they're logged, not shown).


Two mechanics worth understanding

Mention detection for a human user

app_mention only fires for mentions of the bot. To notice when someone @mentions an OOO user, Baton subscribes to message.channels/message.groups and, for every active handoff, scans the raw event text for <@{OOO_USER_ID}> (listeners/events.pyutil.contains_mention). Bot messages and edited/deleted/system subtypes are ignored so Baton never reacts to itself or crashes on a malformed event.

RTS → replies deepening

The retriever follows the documented Real-Time Search pattern:

assistant.search.context (user token)   →  identify relevant thread/channel
        │
        ▼
conversations.replies (thread)  or  conversations.history (channel)   (bot token)

Baton never uses the legacy search:read scope or search.messages/search.all. HistoryRetriever implements the same Retriever interface by scanning conversations.history over the bot's channels — switch with RETRIEVER=rts|history. Both are capped at ~200 messages / ~30 LLM-classified candidates.


Testing

source .venv/bin/activate
pytest -q

Covers: commitment/decision regex heuristics, awaiting-reply thread logic, loop ranking (deadline → type weight → recency), defensive JSON parsing of LLM output, and a retriever interface-parity smoke test (both backends against a fake client).


Definition of done

# Check How to verify
1 slack run / python app.py starts clean; Home renders both states run + open Home tab
2 /baton-handoff brief includes seeded loops, excludes the 2 resolved seed then run command
3 Mentioning the OOO user answers (seeded Q) or routes (novel Q) in-thread @mention in a seeded channel
4 /baton-back (or Assistant panel) streams a re-entry brief from coverage_log run after some coverage
5 Both retrievers pass the same smoke test via the env flag pytest -q
6 pytest green pytest -q

Items 1–4 need a live workspace + tokens (and, for RTS, the user-token OAuth). Items 5–6 run fully offline.


Not built (roadmap only)

Calendar integrations · team handoffs · analytics · GitHub integration · offboarding mode · scheduling.

About

pass you baton to baton and be carefree. your buddy slack agent.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages