-
-
No-result: asked about a channel you can't see, Receipts returns nothing. It can't leak what Slack never gives it.
-
Found: the verbatim prior decision, who said it, the date, a Jump-to-source button, and 'searched as you (permission-aware)'.
-
Permission-aware: a channel you can't see returns “no prior decision”; as a member, the verbatim decision + a Jump-to-source link.
-
The pain: a settled decision resurfaces in a busy channel. "I can never find where these decisions land" is the moment you summon Receipts.
The argument you've already had
Every big Slack community has a Groundhog Day problem. Someone asks "wait, did we decide to drop Node 18?" — and three people who were there sigh, scroll up 900 messages, and paste the same answer they pasted last month. The decision exists. It's just un-findable, so it gets re-argued weekly, long-timers burn out repeating themselves, and newcomers get five different answers to one question.
Receipts ends that. You summon it — @Receipts did we decide to drop Node 18? — and it posts the actual prior message: the verbatim quote, who said it, which channel, the date, and a one-tap Jump link to the source. Not a summary. The receipt.
The one thing that makes it trustworthy
Receipts searches as you. It runs Slack's Real-Time Search API (assistant.search.context) scoped to the summoner's own permissions, so it can only ever surface something you're already allowed to see. Ask it about a decision made only in #leadership and you're not in #leadership? It says "no prior decision on record" — it structurally cannot leak across a channel boundary, because Slack never returns that message to us in the first place.
That's also why it can't hallucinate. The card renders only the verbatim text RTS returned plus its real permalink — every claim is one click from its source. There's no generative path onto the card; the language model only picks which real hit is the decision (and even that has a heuristic fallback, so the demo runs with no API key at all).
Why the required tech is load-bearing, not bolted on
Take RTS away and you're left with two broken options: index the workspace yourself (blind to everything said before you were installed, and a second stale copy of the truth) — or generic search that ignores who's asking (and leaks private decisions across permission boundaries). RTS-run-as-the-summoner is the only mechanism that is simultaneously live, complete, and permission-correct. Receipts doesn't use RTS; Receipts is RTS, with a judgment layer and a card renderer around it.
How it's built
A single long-lived bun process on Slack Socket Mode — no public URL, no inbound server, no database. Slack's own history is the datastore. When you summon it:
- Extract the topic keyword (
Node 18) — one Haiku call, or a deterministic heuristic when no key is set. - Progressive search — RTS is keyword-literal, so it tries the full phrase, then each token, then the question's distinctive words, deduping by permalink and stopping at three hits. Over-specific queries get rescued.
- Judge which hit states the settled decision (and whether anything later revised it).
- Render a native Block Kit card: quote, author, channel, date, an "asked & answered N×" counter, a "searched as you (permission-aware)" tag, and a Jump to message button.
The Pull receipts message shortcut is the same pipeline from a right-click, posting the card into that thread.
What I learned
The hard part wasn't the search — it was recall vs. precision on a literal keyword API. My first version fed RTS the whole question and matched almost nothing; the progressive-widening ladder is what turned it from a demo that worked on one phrasing into one that works on real, messy questions. The second lesson: the trust guarantee had to be architectural, not a promise. "We won't leak private channels" is worthless as a claim — running the search under the summoner's token makes it impossible, and that's the whole product.
Built with
Bun · TypeScript · Slack Bolt · Socket Mode · Real-Time Search API (assistant.search.context) · Block Kit · Anthropic (optional — heuristics carry it without a key)
Built With
- anthropic
- block-kit
- bun
- rts-api
- slack-bolt
- socket-mode
- typescript
Log in or sign up for Devpost to join the conversation.