Inspiration Slack is where modern organizations talk — and for millions of people using screen readers, low vision, or reading in a second language, a lot of that conversation quietly falls apart. An image posted with no alt-text is announced as just "image." A 600-character paragraph with no structure becomes one flat, unskippable wall of audio. An acronym nobody defined stops a new teammate cold.
Accessibility tooling almost always lives after the fact — a linter in your CI, an audit before a launch. But workplace communication is real-time and social, and nobody wants to be publicly corrected. We asked: what if accessibility was a quiet, helpful nudge at the moment of posting — private to the author, one click to fix, and smart enough to respect how people actually work?
That's AccessAlly: real-time accessibility for Slack, built for the Slack Agent for Organizations track.
What it does When someone posts a message or image, the matching detector fires and the author gets a private, ephemeral nudge with a one-click fix — no channel spam, no public shaming. Three detectors ship today, mapped to real WCAG success criteria:
Missing alt-text (WCAG 1.1.1) — vision AI writes alt-text; Approve reposts an accessible version. Walls of text (WCAG 1.3.1) — AI restructures the message with headings and bullets. Undefined jargon (WCAG 3.1.3) — Slack's Real-time Search API finds where a teammate already explained the term and links it.
Plus a per-channel accessibility score, /a11y commands, a native App Home dashboard with a workspace-wide compliance rollup, admin-controlled monitoring, a weekly digest, and CSV compliance export.
How we built it AccessAlly is a fully async Python app on Slack Bolt (Socket Mode) — no public URL needed. The design goal was that every detector is the same shape, so the product is a framework, not three bolted-together checks:
message / file_shared → Bolt handler (acks <3s, work in a background task) → Detector (protocol) → DetectionResult → Groq (vision + text) · Slack Real-time Search · MCP rules server → ephemeral Block Kit nudge + SQLite score store Two of the challenge's three eligible technologies do real work here:
AccessAlly's own MCP server (Model Context Protocol, stdio transport) serves the WCAG rulebook, an org glossary, and per-channel policy as tools. Because the ruleset is a pluggable MCP component, an organization can point AccessAlly at its own MCP server exposing an internal style guide — and the same agent enforces both public WCAG standards and house norms. One agent, your policies. The Real-time Search API (assistant.search.context) powers the jargon detector's "someone already explained this" moment — semantic, and permission-aware, so results respect the author's own access. The scoring model is deliberately simple and lives in stdlib SQLite. A channel's score over a rolling 7-day window is:
$$\text{score} = \left\lfloor 100 \times \left(1 - \frac{\text{issues_found}}{\max(\text{checks_run},, 1)}\right) \right\rceil$$
bucketed as $\ge 90$ 🟢, $70\text{–}89$ 🟡, and $< 70$ 🔴.
Everything degrades gracefully: Groq, RTS, MCP, and Slack failures are caught, logged, and fall back — they never crash a handler. A two-layer error-defense suite (35 tests) locks that in.
What we learned Semantic search will always return something. Our first jargon build treated any search hit as "prior definition found" — and cheerfully quoted a completely unrelated message as the definition of an invented term. We learned to gate on relevance: a hit is only quoted if it actually defines the term. Confident-but-wrong is worse than silent. Great detectors are mostly restraint. The jargon extractor over-fired on ordinary words like runbooks and failover. The wins came from what not to flag — an acronym-shape gate, a stoplist, and skipping terms the author already defined inline. Ack first, always. Slack's 3-second rule is unforgiving; every handler acks instantly and does the real work in a background task. Augment, don't edit. Slack won't let an app edit a user's uploaded file, so Approve alt-text reposts an accessible version instead of mutating the original — a platform constraint we turned into a cleaner trust story.
Challenges we faced The "app did not respond" ghost. A whole debugging session chased a dead /a11y command and a jargon detector that suddenly "missed" obvious acronyms. The code was fine — macOS had put the laptop to sleep, silently killing the Socket Mode connection for 73 minutes. The fix was one word: caffeinate -is. The lesson (check that your socket is alive before blaming your handlers) is now in our README. One upload, two events. A single image fires both file_shared and a file_share message — so we nudged twice. A TTL dedupe guard keyed on file_id fixed it. Block Kit's 2000-character button limit. A restructured wall of text is far too big to stash in a button's value, so we park fix payloads in a bounded in-memory registry and hand the button a short id. Verifying, not guessing, Slack's newest APIs. The Real-time Search API is new and moved from a single search:read scope to granular ones; we verified every endpoint and scope against docs.slack.dev and smoke-tested live rather than trusting memory. What's next Colour-contrast analysis, audio/video transcription, and multilingual support — each is a new detector against the same contract, so the app, nudges, scoring, and commands pick it up unchanged. Accessibility that ships where you already work.
Log in or sign up for Devpost to join the conversation.