Answers a question posted in Slack using only the messages that channel already contains, in a thread.

UsesOpenAISlack

Answer channel questions from what was already said

When someone posts a message in your chosen Slack channel that contains the ask word ('!ask' unless you change it), the rest of the message is treated as a question; other messages do not start the workflow at all. The channel's 20 most recent messages are read, the AI works out a short search phrase, the channel's last 200 messages are searched for it, and the AI drafts an answer from the messages found alone.

The answer is posted in a thread under the question, with a line saying how many earlier messages it read. When nothing relevant turns up it says so instead of improvising, and when the draft quotes a figure that appears in none of the messages, the draft is withheld and the closest messages are posted instead. If the AI does not respond, the thread is told it cannot answer right now and the run is marked as failed. Mentions in anything it posts are defused, so nobody is pinged.

You need a Slack connection and an OpenAI connection, the Slack channel to watch, and a line or two describing your team. It only sees messages posted in the channel itself, and only the last 200: replies written inside threads, and anything older, cannot be found.

SlackSlackWhen someone posts “!ask” in a channel. Save it as request.When someone posts “!ask” in a channel. Save it as request.
ValuesSet askWord to !ask.Set askWord to !ask.
ValuesSet teamContext to blank.Set teamContext to blank.
ValuesSet recentToRead to 20.Set recentToRead to 20.
ValuesSet olderToFind to 15.Set olderToFind to 15.
TextTrim askWord and save it as askPhrase.Trim askWord and save it as askPhrase.
ValuesSet questionText to blank.Set questionText to blank.
Decisions & LoopsIf a step below fails, save the error as readErrorIf a step below fails, save the error as readError
Try
TextTrim request.text and save it as questionText.Trim request.text and save it as questionText.
On error
No steps
Decisions & LoopsIf questionText is empty or questionText is nullIf questionText is empty or questionText is null
Yes
Decisions & LoopsFinish the run: That message had no text, so there was nothing to answer.Finish the run: That message had no text, so there was nothing to answer.
No
No steps
Decisions & LoopsIf questionText doesn't contain askPhrase (ignoring case)If questionText doesn't contain askPhrase (ignoring case)
Yes
Decisions & LoopsFinish the run: That message did not contain the ask word (askPhrase), so it was not a question for this bot. If you changed askWord, put the same word in the Keyword filter on the trigger step, so that only messages containing it start a run.Finish the run: That message did not contain the ask word (askPhrase), so it was not a question for this bot. If you changed askWord, put the same word in the Keyword filter on the trigger step, so that only messages containing it start a run.
No
No steps
TextTrim questionText with askPhrase replaced by (ignoring case) and save it as question.Trim questionText with askPhrase replaced by (ignoring case) and save it as question.
Decisions & LoopsIf question is emptyIf question is empty
Yes
Decisions & LoopsFinish the run: That message was only the ask word, with no question after it.Finish the run: That message was only the ask word, with no question after it.
No
No steps
ListsStart an empty list and save it as excerpts.Start an empty list and save it as excerpts.
ListsStart an empty list and save it as usedTexts.Start an empty list and save it as usedTexts.
SlackSlackGet recent messages in request.channel_id. Save it as recent.Get recent messages in request.channel_id. Save it as recent.
Decisions & LoopsFor each message in recent.messages:For each message in recent.messages:
TextTrim message.text and save it as messageText.Trim message.text and save it as messageText.
Decisions & LoopsIf messageText is empty or messageText is nullIf messageText is empty or messageText is null
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf messageText contains askPhrase (ignoring case)If messageText contains askPhrase (ignoring case)
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf usedTexts contains messageTextIf usedTexts contains messageText
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ListsAdd messageText to the end of usedTexts and save it as usedTexts.Add messageText to the end of usedTexts and save it as usedTexts.
ListsAdd messageText truncated to 300 characters using … as the ellipsis to the end of excerpts and save it as excerpts.Add messageText truncated to 300 characters using … as the ellipsis to the end of excerpts and save it as excerpts.
TextShorten excerpts joined by a blank line to 1500 characters and save it as recentContext.Shorten excerpts joined by a blank line to 1500 characters and save it as recentContext.
ValuesSet searchPromptTop to You turn a colleague's question into a search phrase for a Slack channel's message history.Set searchPromptTop to You turn a colleague's question into a search phrase for a Slack channel's message history. Reply with one word, or two or three words side by side, most likely to appear exactly as written in a message that answers the question. The search only finds messages that contain your whole reply as written, so shorter is better. Reply with the words only - no quotes, no punctuation, no explanation. Team context: teamContext.
ValuesSet searchPrompt to searchPromptTopSet searchPrompt to searchPromptTop What the channel has been saying: recentContext Question: question.
ValuesSet searchPhrase to blank.Set searchPhrase to blank.
Decisions & LoopsIf a step below fails, save the error as phraseErrorIf a step below fails, save the error as phraseError
Try
OpenAIOpenAIAsk OpenAI: searchPrompt. Save it as searchReply.Ask OpenAI: searchPrompt. Save it as searchReply.
TextTrim searchReply.response_text with replaced by " (ignoring case) and save it as searchPhrase.Trim searchReply.response_text with replaced by " (ignoring case) and save it as searchPhrase.
On error
No steps
Decisions & LoopsIf searchPhrase is null or the length of searchPhrase is greater than 60If searchPhrase is null or the length of searchPhrase is greater than 60
Yes
ValuesChange searchPhrase to blank.Change searchPhrase to blank.
No
No steps
Decisions & LoopsIf searchPhrase isn't emptyIf searchPhrase isn't empty
Yes
SlackSlackFind messages matching searchPhrase. Save it as older.Find messages matching searchPhrase. Save it as older.
Decisions & LoopsFor each message in older.messages:For each message in older.messages:
Text Trim message.text and save it as messageText. Trim message.text and save it as messageText.
Decisions & Loops If messageText is empty or messageText is null If messageText is empty or messageText is null
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & Loops If messageText contains askPhrase (ignoring case) If messageText contains askPhrase (ignoring case)
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & Loops If usedTexts contains messageText If usedTexts contains messageText
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Lists Add messageText to the end of usedTexts and save it as usedTexts. Add messageText to the end of usedTexts and save it as usedTexts.
Lists Add messageText truncated to 300 characters using … as the ellipsis to the end of excerpts and save it as excerpts. Add messageText truncated to 300 characters using … as the ellipsis to the end of excerpts and save it as excerpts.
No
No steps
TextJoin excerpts with a blank line and save it as evidence.Join excerpts with a blank line and save it as evidence.
ListsCount the items in excerpts and save it as usedCount.Count the items in excerpts and save it as usedCount.
Decisions & LoopsIf usedCount is 0If usedCount is 0
Yes
SlackSlackReply in a thread in request.channel_id.Reply in a thread in request.channel_id. Reply To: request.message_tsI could not find anything in this channel about that. This bot only answers from messages already posted here, so someone in the channel may know more.
Decisions & LoopsFinish the run: No earlier message in this channel matched the question.Finish the run: No earlier message in this channel matched the question.
No
No steps
TextReplace the pattern <[@!](?:[^>|]*\|)?([^>|]*)> with @$1 in evidence and save it as safeEvidence.Replace the pattern <[@!](?:[^>|]*\|)?([^>|]*)> with @$1 in evidence and save it as safeEvidence.
ValuesSet quoted to Set quoted to The closest messages I found: safeEvidence truncated to 1200 characters using … as the ellipsis.
ValuesSet answerPromptTop to You are answering a colleague's question in Slack using ONLY the channel messages quoted below.Set answerPromptTop to You are answering a colleague's question in Slack using ONLY the channel messages quoted below. Rules: - Use only what these messages say. Add nothing from general knowledge. - Never invent a number, date, price, name or link. - If the messages do not answer the question, reply with exactly: NOT FOUND - Otherwise answer in at most four short sentences of plain language. - Do not use @ mentions and do not mention these rules. Team context: teamContext.
ValuesSet answerPromptMid to answerPromptTopSet answerPromptMid to answerPromptTop Question: question.
ValuesSet answerPrompt to answerPromptMidSet answerPrompt to answerPromptMid Channel messages: evidence.
ValuesSet draft to blank.Set draft to blank.
ValuesSet draftProblem to blank.Set draftProblem to blank.
Decisions & LoopsIf a step below fails, save the error as draftErrorIf a step below fails, save the error as draftError
Try
OpenAIOpenAIAsk OpenAI: answerPrompt. Save it as answerReply.Ask OpenAI: answerPrompt. Save it as answerReply.
TextTrim answerReply.response_text and save it as draft.Trim answerReply.response_text and save it as draft.
On error
ValuesChange draftProblem to draftError.message.Change draftProblem to draftError.message.
Decisions & LoopsIf draftProblem isn't blankIf draftProblem isn't blank
Yes
SlackSlackReply in a thread in request.channel_id.Reply in a thread in request.channel_id. Reply To: request.message_tsI can't answer this right now: the AI service did not respond. Try asking again later.@quoted
Decisions & LoopsStop and report a problem: The AI step failed (draftProblem), so the question was not answered; the closest messages were posted instead. Check the OpenAI connection.Stop and report a problem: The AI step failed (draftProblem), so the question was not answered; the closest messages were posted instead. Check the OpenAI connection.
Else if @draft is empty or @draft is "null" or @draft start…
SlackSlackReply in a thread in request.channel_id.Reply in a thread in request.channel_id. Reply To: request.message_tsI could not put an answer together from this channel's history.@quoted
Decisions & LoopsFinish the run: The channel's history did not answer the question, so the messages themselves were posted instead.Finish the run: The channel's history did not answer the question, so the messages themselves were posted instead.
Otherwise
No steps
TextReplace the pattern <[@!](?:[^>|]*\|)?([^>|]*)> with @$1 in draft and save it as answer.Replace the pattern <[@!](?:[^>|]*\|)?([^>|]*)> with @$1 in draft and save it as answer.
TextExtract all matches from answer with , replaced by (ignoring case) and save it as draftNumbers.Extract all matches from answer with , replaced by (ignoring case) and save it as draftNumbers.
ValuesSet sourceText to evidenceSet sourceText to evidence question teamContext.
TextExtract all matches from sourceText with , replaced by (ignoring case) and save it as sourceNumbers.Extract all matches from sourceText with , replaced by (ignoring case) and save it as sourceNumbers.
ValuesSet unsourced to blank.Set unsourced to blank.
Decisions & LoopsFor each figure in draftNumbers:For each figure in draftNumbers:
Decisions & LoopsIf sourceNumbers doesn't contain figureIf sourceNumbers doesn't contain figure
Yes
ValuesChange unsourced to figure.Change unsourced to figure.
Decisions & LoopsStop repeating.Stop repeating.
No
No steps
Decisions & LoopsIf unsourced isn't emptyIf unsourced isn't empty
Yes
SlackSlackReply in a thread in request.channel_id.Reply in a thread in request.channel_id. Reply To: request.message_tsI drafted an answer but it used a figure (unsourced) that no message in this channel mentions, so I have not posted it.@quoted
Decisions & LoopsFinish the run: The draft contained a figure the channel never mentioned, so it was withheld and the source messages were posted instead.Finish the run: The draft contained a figure the channel never mentioned, so it was withheld and the source messages were posted instead.
No
No steps
SlackSlackReply in a thread in request.channel_id.Reply in a thread in request.channel_id. Reply To: request.message_tsanswer _Answered only from earlier messages in this channel (usedCount read)._