About AutoFlow — Slack-Native Workflow Automation Architect

Inspiration

Business teams rarely ask for automation in a form a developer can ship. They say “automate supplier invoice approval under €5,000” and expect a system to appear. In practice that request hides a discovery process: classification, owners, exception routing, compliance checks, and a human sign-off step. Today that distillation happens across days of meetings and a shared doc nobody finishes.

We built AutoFlow to compress that loop to minutes, inside the tool where the request is already spoken — Slack. A vague mention becomes a structured, implementation-ready automation blueprint with risk, compliance, and human-in-the-loop analysis, delivered as a rich interactive message.

What we built

A Slack-native agent that:

  • Takes a vague request via @mention (or the dashboard), asks up to three clarifying questions, and produces a full blueprint: classification, business goal, trigger, inputs, systems involved, numbered workflow steps, human-in-the-loop points, a risk & compliance checklist, complexity rating, suggested technical implementation, MVP scope, and a handoff summary.
  • Surfaces the blueprint as Slack Block Kit UI with action buttons — Approve MVP, Request Changes, Generate Implementation Tasks, Export Blueprint — that drive the next step.
  • Mirrors everything in a local React dashboard for monitoring, a search bar, and live system-health indicators.

Two of the required platform technologies are first-class:

  1. MCP server — the five internal tools (classifyProcessTool, generateBlueprintTool, riskAssessmentTool, complexityEstimatorTool, implementationPlanTool) are exposed over a JSON-RPC 2.0 endpoint at /api/mcp/*, so any external agent can discover and invoke them.
  2. Real-Time Search API — full-text search across blueprints, interaction logs, and Slack message history at /api/search/*.

How we built it

  • Backend: Node.js + TypeScript, Express, @slack/bolt, zod, and pino logging. A deterministic, rule-based architect (automationArchitect.ts) is the stable core; an optional OpenAI-compatible LLM path enhances the prose when configured, falling back cleanly if unavailable.
  • Agent logic: a small tool registry (tools/registry.ts) holds the five tools. The architect orchestrates them in sequence: classify → generate → risk → complexity → plan.
  • Slack delivery: slack/blockKit.ts builds the message blocks and enforces Slack's limits and uniqueness rules before say().
  • Frontend: React 18 + Vite, plain CSS, lucide-react icons. Talks to the backend over a typed API client.
  • MCP + search: tools/mcpServer.ts implements the JSON-RPC surface; api/search.ts implements the real-time index.

What we learned

  • Slack Block Kit is strict, not forgiving. Messages fail wholesale on a single bad field, so validation has to happen before delivery, not after.
  • MCP is a clean contract for agent-to-agent use. Exposing our tools over JSON-RPC meant we had to give every tool a real JSON Schema — which also forced the architect's inputs to be honest about what they require.
  • Determinism beats flakiness for a live demo. Keeping a rule-based path as the default and treating the LLM as an enhancement made the project reproducible under judging.

Challenges we faced

The hardest problem was making Block Kit messages that never get rejected. Slack returns a single opaque invalid_blocks error for the whole message, with no partial render — so one bad character or duplicate id kills the entire blueprint, and the user sees nothing. We hit three distinct failure modes, each caught only by reading the exact error array from the logs:

  1. Special-character escaping. Slack treats <...> as a link and & as an entity. Text like cost < 5000 or R&D & Ops was being parsed as malformed markup. We replaced a greedy "preserve anything in brackets" rule with a strict allow-list of real Slack tokens (<https://…>, <@U…>, <#C…>, <!channel>), escaping everything else first, then truncating to the 3,000-character cap (escaping before truncation, so the & → &amp; expansion can't push text over the limit).
  2. Duplicate action_id. All four suggested-prompt chips originally shared one action_id, which Slack rejects. Each now gets a unique, index-suffixed id.
  3. action_id on non-interactive elements. A safety net that de-duplicated ids was initially applied to every element — including header and context text, which Slack forbids from carrying action_id. We restricted de-duplication to interactive element types only (button, select, overflow, datepicker, radio_buttons, checkboxes, …).

The lasting fix was a finalizeBlocks stage that sanitizes every text field to Slack's limits and asserts unique, schema-valid action_ids across the whole message before it is sent — so invalid_blocks can no longer recur from future block additions.## Inspiration

*Note that the backend is using a free tier open source LLM model api, so expect a bit more latency in response.

Built With

Share this project:

Updates