Best Practices for Developing App Agents

Listed below are several best practices we recommend for creating apps with the agent feature. Following these tips will allow you to leverage the complete agents feature set, ensuring the look, feel, and behavior of an AI app in Slack provides a consistent experience that fosters trust and enhances usability.

Many of these best practices include using Block Kit blocks. Check out our Bolt documentation to learn how to include blocks in your code for app agents:

Content disclaimer

Add a disclaimer at the footer of app messages indicating that the response was generated by an LLM (large language model) and provide any disclaimers that may be appropriate or applicable to communicate with the user. An example of this might sound something like:

This content was generated by an LLM. Check generated content for vulnerabilities, and do not use to generate code that is visible outside of Slack. Review carefully before acting on the response; it may contain bias or hallucinations.

Implementing a content disclaimer

You can implement a disclaimer like this to each message by using a context block. Here is an example:


{
  "type": "context",
  "elements": [
    {
      "type": "mrkdwn",
      "text": "This tool uses AI to generate responses, so some information may be inaccurate."
    }
  ]
}

View this example in Block Kit Builder.

Gather feedback

Prompt the user to provide feedback on the accuracy and helpfulness of the response. Even a simple thumbs up/down reaction emoji will work, but consider allowing written feedback when the response was graded poorly so that you can learn more about what the issue was.

Implementing feedback gathering

You can collect feedback in a couple of different ways. Use interactive elements to provide a thumbs up or thumbs down button for the user to react to a message; then you can open a modal to provide further feedback. You can also subscribe to reaction_added events so that when a user reacts to a message with an unfavorable reaction, you can collect feedback on it.

References, citations, and annotations

Sources and attribution should be used and displayed consistently. There should be a concise way to reference internal messages and files from external sources. We recommend including these in each response that cited a source or used knowledge from a message or document to generate the output. Doing so builds trust with your app's users.

Implementing references

Use link formatting to cite sources inline and a context block to list references at the end of the message. To avoid clutter in the response, you may want to suppress unfurls if there are several sources.

Here is an example of citing a source in a context block that you could include at the end of a message:


{
    "type": "context",
    "elements": [
        {
            "type": "mrkdwn",
            "text": "Slack stands for 'Searchable Log of All Conversation and Knowledge.' <https://app.slack.com/slackhelp/en-US/115004071768|[1 Help Center]>"
        }
    ]
}

View this example in Block Kit Builder.

User onboarding and welcome message

Send a call to action or suggest next steps when a user interacts with an agent for the very first time. Once this requirement is completed, optimize for repeat use and avoid repetitive prompts and 'getting started' types of messaging. This is important especially when it is necessary for the user to sign in, connect an account, agree to terms of service, or review a code of conduct.

Implementing user onboarding

We recommend using an interactive element or link if an action is needed or the user needs to visit a document or external URL.

For an app that requires the user complete a login flow to access all of the features, a first message could include a block that looks something like this:


{
    "type": "rich_text",
    "block_id": "Vrzsu",
    "elements": [
        {
            "type": "rich_text_quote",
            "elements": [
                {
                    "type": "text",
                    "text": "It looks like you're not logged into the TeamworkDreamwork app.\n Sign in now to use this feature."
                }
            ]
        }
    ]
},
{
    "type": "actions",
    "block_id": "actionblock789",
    "elements": [
        {
            "type": "button",
            "style": "primary",
            "text": {
                "type": "plain_text",
                "text": "Sign in"
            },
            "value": "sign_in_123"
        },
        {
            "type": "button",
            "style": "danger",
            "text": {
                "type": "plain_text",
                "text": "Ignore"
            }
        }
    ]
}

View this example in Block Kit Builder.

Media support

Make it clear which media the app supports and gracefully fail when necessary. Where applicable, build apps that can handle a wide array of media types to provide the best user experience. Don't make the user guess which media types the app can send and receive; be clear about this when the user first visits the app or sends a file type that is unsupported. Refer to working with files for this guidance.

For example, if a user sends an image file and your app does not support receiving images, reply with a message like this:

It looks like the image didn't come through! πŸ“Έ Feel free to describe what you need help with, and I'll do my best to assist you. 😊

Basic formatting with Slack mrkdwn

Responses should be posted using Block Kit for complex messages or use Slack mrkdwn when sending rich text. The formatting system of Slack is different from the common markup language used elsewhere on the web. It is typical for LLMs to default to use this common markdown syntax unless prompted otherwise, which will not render correctly when posted in Slack.

Follow the basic formatting guide to format app responses. With some success, LLMs can be prompted to "format messages for display in Slack" or explicitly instructed to "use single asterisks for bold text", for example.

Status updates

Keep the status accurate and continue to update it with new statuses as needed. If generation takes longer than expected or if there are multiple tasks working behind the scenes, keep the user informed that the app is still working. This ensures the user does not assume the app is "stuck" or "bugged" and gives them insight into what the app is doing to provide them an answer.

Implementing status updates

Do this by calling the assistant.threads.setStatus method to set a follow-up status.

status="is thinking..."

status="is searching company knowledge..."

status="spinning the digital hamster wheel..."

Continuing the conversation

Don't lose the thread. Ensure that the LLM generating the response is aware of the recent conversation history between the app and the end user. Users expect that the app understands which questions were already asked and which answers were already provided. If they can reference previous content without repeating themselves, it leads to a better user experience.

Implementing context

Call the conversations.replies method with the bot token using the chat's thread_ts parameter to gather the immediate message history. See how this is done using Bolt for JavaScript in the Assistant Template sample app.

App home

For apps that allow for any form of user settings (especially if the user can adjust them), use the app home as an additional surface to communicate these details. Here you can share information such as the app persona, usage notes, which model is being used, which data sources are being referenced to respond, which data sources can be configured, etc. This helps users understand how the app is set up so they know what to expect. Provide a clear landing page so users know where they can review and adjust settings without guessing where configuration changes can be taken (in chat, slash command, external page, etc.).

Implementing the App Home

First go to your app settings, select your app, and navigate to App Home under Features in the left navigation. This menu allows you to configure which tabs are available in your app, along with a few other configurations. Ensure your app is set to use the Home Tab. Then refer to the App Home guide for how to customize this surface.

Graceful error handling

Always gracefully handle any errors in processing by sending an appropriate message back to the user. As a last resort, clear the status so the app is not stuck 'thinking' indefinitely. It's important for the user to know when there are errors beyond their control and they need to try again or report a bug.

Something as simple as sending a message like this can go a long way for a user understanding why something isn't working:

Ope sorry! TeamworkDreamwork App isn't enabled for you.

Implementing graceful error handling

This will vary based on the types of errors you handle, but you can clear a status by calling the assistant.threads.setStatus method and sending an empty string in the status field.

Using modals in split view

Capture structured data from a user to record or send to an external system. Depending on the scenario, it can be tedious for users to converse with an agent to make sure the right information is captured. Pre-filling a modal with the approximate information will allow the user to review the information rather than needing to fill it out manually before submitting.

Implementing modals

Gather user input and take advantage of the initial_value / initial_options property to prefill the modal. For example, if an agent is used for gathering information for submitting an issue, the agent could send a message with a button that, when clicked, opens a modal for data collection. In that modal, you can use the plain-text input for fields and pre-populate them with user-provided data using the initial_value field.

Channel support

Be clear with users about where and how the app works. Does it work in channels? Does it support @-mentions? Or is it solely used in the assistant view? Set clear boundaries so that users know where they can rely on the agent. Meet users where they work, whether that is in a dedicated split-view container, offering a 1:1 interaction, or within channels where one or more users are asking questions.

Implementing channel support

Communicating where and how the app is available can be done in the App Home or during user onboarding. To support your app's use in channels, be sure to implement a listener and respond to the app_mention event. Check out how this is done in our Bolt for Python AI Chatbot sample app.

Choosing relevant prompts

We recommend setting dynamic prompts based on context clues from the interaction: the channel, user profile, connected company data, etc. Seeing the same prompts at every interaction reduces trust in the app and may suggest that the app is less connected to the workspace and not optimized for repeat usage.

Implementing relevant prompts

Use the assistant.threads.setSuggestedPrompts method to set suggested prompts. Update these regularly to make the app more engaging.

Chat titles

We recommend keeping chat titles updated. The thread title is set automatically after the user sends their first message. Consider updating the title to keep it accurate according to the conversation it describes.

Set the title initially to capture the first question from the user. At your discretion, update it when a significant number of messages have been exchanged that require a new description of the discussion taking place. This makes it easier to find a conversation when browsing the History.

Implementing chat titles

Use the assistant.threads.setTitle method to set the title of the thread.