Field notes

The SME Slack bot: architecture and boundaries

How the PursuitAgent SME bot asks for input, what it does with the answer, and what it deliberately refuses to do. A short tour of the boundary between the bot and the human.

The PursuitAgent engineering team 4 min read Engineering

The SME Slack bot is a small program with a strict boundary. It asks one question at a time, captures one answer, and writes that answer back to a draft packet. It does not draft, it does not summarize, and it does not chase. This post explains the architecture and — more importantly — what we deliberately decided the bot is not allowed to do.

What the bot is for

A proposal manager opens a draft packet, marks four questions as “needs SME input,” and assigns each to a person. The bot reads the assignments and posts a direct message to each SME with the question, the requested format, and the deadline. The SME types or pastes an answer, the bot files it on the draft packet, and the proposal manager sees it appear in the response builder.

That is the entire feature. The thing that surprised us in design was how much we had to refuse to build.

Architecture

The bot is a Hono service with three main responsibilities:

  • Subscribe to draft-packet “needs SME input” events from the proposal module.
  • Render a Slack interactive message with the question, the input field, and the deadline.
  • Capture the SME’s response, validate it against the expected shape, and write it back to the draft packet via the proposals module’s internal API.

The bot does not use a long-lived database. It writes everything back to the proposal record. If the bot dies, no SME input is lost and no state is orphaned. We treat the bot as a transport layer, not a system of record.

// Bot subscribes to packet events; one event = one Slack DM.
proposalEvents.on("sme-input-requested", async (req) => {
  const slackUser = await resolveSlackUser(req.smeEmail);
  const message = renderQuestionPrompt(req.question, req.deadline);
  await slack.chat.postMessage({ channel: slackUser, ...message });
});

The Slack message uses interactive Block Kit blocks. The SME sees the question, a multiline text input, an optional file-upload affordance, and two buttons: Submit and Decline (with reason). There is no third button. There is no “AI assist” button on the SME’s side, deliberately.

What the bot refuses to do

We made four decisions during the build that look small but compound.

The bot does not draft for the SME. A natural temptation is to pre-fill the input field with a draft pulled from the KB. We don’t do that. The reason is the same reason we wrote about elsewhere: the SME’s value is in the specific knowledge the KB doesn’t have. If the bot pre-fills with KB content, the SME edits the pre-fill instead of contributing the missing information, and the answer regresses to what the KB already said. The pre-fill goes in the draft packet the proposal manager prepares, not in the SME’s input field.

The bot does not chase. SMEs do not get a “friendly reminder” eight hours before deadline. They get one message at assignment, and if they don’t answer, the proposal manager sees a missed-SLA flag in the response builder and decides what to do. The chase is a human decision, not a bot decision. Qorus’s research on the SME bottleneck — 48% of teams citing this as their top problem for five years running — is partly a tooling problem and partly a culture problem. The bot doesn’t fix the culture by spamming.

The bot does not summarize. Some teams asked us to add an “AI summary” of long SME inputs to make the proposal manager’s life easier. We declined. The proposal manager needs to read what the SME wrote, in the SME’s words, because that’s the source of the next draft. A summary becomes a translation layer, and translation layers introduce hallucinations. The bot writes the SME’s answer through verbatim.

The bot does not infer follow-up questions. When an SME’s answer is incomplete, the bot does not ask, “would you also clarify X?” That decision belongs to the proposal manager, who knows the response strategy. The bot files the answer and stops. The next SME ask is a separate event, requested by the proposal manager.

The transport-layer principle

The thing the bot does well: getting a question in front of the right person without that person logging into a tool they don’t use. The thing the bot does badly: anything that requires understanding the response strategy.

Treating the bot as transport — not as judgment — keeps the boundary clean. The proposal manager owns judgment. The bot owns delivery.

What’s next in the SME series

We have a few related pieces this month: the SME draft packet explains what we ship to the SME alongside the bot’s question, and the SME collaboration series (starting Day 132) goes deeper on the workflow patterns the bot supports. The bot is one tool in a larger system; this post is just the wiring.

Sources

  1. 1. Qorus — Winning proposals: how to stop wrangling SMEs
  2. 2. Slack API — interactive messages