diff --git a/docs/content/concepts/assistant.md b/docs/content/concepts/assistant.md
new file mode 100644
index 000000000..7a878220e
--- /dev/null
+++ b/docs/content/concepts/assistant.md
@@ -0,0 +1,175 @@
+---
+title: Agents & Assistants
+lang: en
+slug: /concepts/assistant
+---
+
+:::info[This feature requires a paid plan]
+If you don't have a paid workspace for development, you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free.
+:::
+
+Agents and assistants comprise a new messaging experience for Slack. If you're unfamiliar with using agents and assistants within Slack, you'll want to read the [API documentation on the subject](https://api.slack.com/docs/apps/ai). Then come back here to implement them with Bolt!
+
+## Configuring your app to support assistants
+
+1. Within [App Settings](https://api.slack.com/apps), enable the **Agents & Assistants** feature.
+
+2. Within the App Settings **OAuth & Permissions** page, add the following scopes:
+ * [`assistant:write`](https://api.slack.com/scopes/assistant:write)
+ * [`chat:write`](https://api.slack.com/scopes/chat:write)
+ * [`im:history`](https://api.slack.com/scopes/im:history)
+
+3. Within the App Settings **Event Subscriptions** page, subscribe to the following events:
+ * [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started)
+ * [`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed)
+ * [`message.im`](https://api.slack.com/events/message.im)
+
+:::info
+You _could_ implement your own assistants by [listening](/concepts/event-listening) for the `assistant_thread_started`, `assistant_thread_context_changed`, and `message.im` events. That being said, using the `Assistant` class will streamline the process. And we already wrote this nice guide for you!
+:::
+
+## The `Assistant` class instance
+
+The [`Assistant`](/reference#the-assistantconfig-configuration-object) can be used to handle the incoming events expected from a user interacting with an assistant in Slack. A typical flow would look like:
+
+1. [The user starts a thread](#handling-new-thread). The `Assistant` class handles the incoming [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started) event.
+2. [The thread context may change at any point](#handling-thread-context-changes). The `Assistant` class can handle any incoming [`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed) events. The class also provides a default `context` store to keep track of thread context changes as the user moves through Slack.
+3. [The user responds](#handling-user-response). The `Assistant` class handles the incoming [`message.im`](https://api.slack.com/events/message.im) event.
+
+```ts
+const assistant = new Assistant({
+ // If you prefer to not use the provided DefaultThreadContextStore,
+ // you can use your own optional threadContextStore
+ threadContextStore: {
+ get: async ({ context, client, payload }) => {},
+ save: async ({ context, client, payload }) => {},
+ },
+ threadStarted: async ({ say, saveThreadContext, setStatus, setSuggestedPrompts, setTitle }) => {},
+ // threadContextChanged is optional
+ // If you use your own optional threadContextStore you likely won't use it
+ threadContextChanged: async ({ say, setStatus, setSuggestedPrompts, setTitle }) => {},
+ userMessage: async ({ say, getThreadContext, setStatus, setSuggestedPrompts, setTitle }) => {},
+});
+```
+
+While the `assistant_thread_started` and `assistant_thread_context_changed` events do provide Slack-client thread context information, the `message.im` event does not. Any subsequent user message events won't contain thread context data. For that reason, Bolt not only provides a way to store thread context — the `threadContextStore` property — but it also provides a `DefaultThreadContextStore` instance that is utilized by default. This implementation relies on storing and retrieving [message metadata](https://api.slack.com/metadata/using) as the user interacts with the assistant.
+
+If you do provide your own `threadContextStore` property, it must feature `get` and `save` methods.
+
+:::tip
+Be sure to give the [assistants reference docs](/reference#assistants) a look!
+:::
+
+## Handling a new thread {#handling-new-thread}
+
+When the user opens a new thread with your assistant, the [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started) event will be sent to your app. Capture this with the `threadStarted` handler to allow your app to respond.
+
+In the example below, the app is sending a message — containing thread context [message metadata](https://api.slack.com/metadata/using) behind the scenes — to the user, along with a single [prompt](https://api.slack.com/methods/assistant.threads.setSuggestedPrompts).
+
+```js
+...
+ threadStarted: async ({ event, say, setSuggestedPrompts, saveThreadContext }) => {
+ const { context } = event.assistant_thread;
+
+ await say('Hi, how can I help?');
+
+ const prompts = [{
+ title: 'Fun Slack fact',
+ message: 'Give me a fun fact about Slack, please!',
+ }];
+
+ // Provide the user up to 4 optional, preset prompts to choose from.
+ await setSuggestedPrompts({ prompts, title: 'Here are some suggested options:' });
+ },
+...
+```
+
+:::tip
+When a user opens an assistant thread while in a channel, the channel info is stored as the thread's `AssistantThreadContext` data. You can grab that info using the `getThreadContext()` utility, as subsequent user message event payloads won't include the channel info.
+:::
+
+## Handling thread context changes {#handling-thread-context-changes}
+
+When the user switches channels, the [`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed) event will be sent to your app. Capture this with the `threadContextChanged` handler.
+
+```js
+...
+ threadContextChanged: async ({ saveThreadContext }) => {
+ await saveThreadContext();
+ },
+...
+```
+
+If you use the built-in `AssistantThreadContextStore` without any custom configuration, you can skip this — the updated thread context data is automatically saved as [message metadata](https://api.slack.com/metadata/using) on the first reply from the assistant bot.
+
+## Handling the user response {#handling-user-response}
+
+When the user messages your assistant, the [`message.im`](https://api.slack.com/events/message.im) event will be sent to your app. Capture this with the `userMessage` handler.
+
+Messages sent to the assistant do not contain a [subtype](https://api.slack.com/events/message#subtypes) and must be deduced based on their shape and any provided [message metadata](https://api.slack.com/metadata/using).
+
+There are three [utilities](/reference#the-assistantconfig-configuration-object) that are particularly useful in curating the user experience:
+* `say`
+* `setTitle`
+* `setStatus`
+
+The following example uses the [OpenAI API client](https://platform.openai.com/docs/api-reference/introduction), but you can substitute it with the AI client of your choice.
+
+ ```js
+ ...
+ userMessage: async ({ client, message, say, setTitle, setStatus }) => {
+ const { channel, thread_ts } = message;
+
+ try {
+ // Set the status of the Assistant to give the appearance of active processing.
+ await setStatus('is typing..');
+
+ // Retrieve the Assistant thread history for context of question being asked
+ const thread = await client.conversations.replies({
+ channel,
+ ts: thread_ts,
+ oldest: thread_ts,
+ });
+
+ // Prepare and tag each message for LLM processing
+ const userMessage = { role: 'user', content: message.text };
+ const threadHistory = thread.messages.map((m) => {
+ const role = m.bot_id ? 'assistant' : 'user';
+ return { role, content: m.text };
+ });
+
+ const messages = [
+ { role: 'system', content: DEFAULT_SYSTEM_CONTENT },
+ ...threadHistory,
+ userMessage,
+ ];
+
+ // Send message history and newest question to LLM
+ const llmResponse = await openai.chat.completions.create({
+ model: 'gpt-4o-mini',
+ n: 1,
+ messages,
+ });
+
+ // Provide a response to the user
+ await say(llmResponse.choices[0].message.content);
+
+ } catch (e) {
+ console.error(e);
+
+ // Send message to advise user and clear processing status if a failure occurs
+ await say('Sorry, something went wrong!');
+ }
+ },
+});
+
+app.assistant(assistant);
+```
+
+## Full example : App Agent & Assistant Template
+
+Below is the `app.js` file of the [App Agent & Assistant Template repo](https://github.com/slack-samples/bolt-js-assistant-template/) we've created for you to build off of.
+
+```js reference title="app.js"
+https://github.com/slack-samples/bolt-js-assistant-template/blob/main/app.js
+```
\ No newline at end of file
diff --git a/docs/content/reference.md b/docs/content/reference.md
index a71dab74b..7b0f0324b 100644
--- a/docs/content/reference.md
+++ b/docs/content/reference.md
@@ -151,6 +151,28 @@ Bolt's client is an instance of `WebClient` from the [Node Slack SDK](https://to
:::
+## Agents & Assistants
+
+### The `AssistantConfig` configuration object
+
+| Property | Required? | Description |
+|---|---|---|
+|`threadContextStore` | Optional | When provided, must have the required methods to get and save thread context, which will override the `getThreadContext` and `saveThreadContext` utilities.
If not provided, a `DefaultAssistantContextStore` instance is used.
+| `threadStarted` | Required | Executes when the user opens the assistant container or otherwise begins a new chat, thus sending the [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started) event.
+| `threadContextChanged` | Optional | Executes when a user switches channels while the assistant container is open, thus sending the [`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed) event.
If not provided, context will be saved using the AssistantContextStore's `save` method (either the `DefaultAssistantContextStore` instance or provided `threadContextStore`).
+| `userMessage` | Required | Executes when a [message](https://api.slack.com/events/message) is received, thus sending the [`message.im`](https://api.slack.com/events/message.im) event. These messages do not contain a subtype and must be deduced based on their shape and metadata (if provided). Bolt handles this deduction out of the box for those using the `Assistant` class.
+
+### Assistant utilities
+
+Utility | Description
+|---|---|
+| `getThreadContext` | Alias for `AssistantContextStore.get()` method. Executed if custom `AssistantContextStore` value is provided.
If not provided, the `DefaultAssistantContextStore` instance will retrieve the most recent context saved to the instance.
+| `saveThreadContext` | Alias for `AssistantContextStore.save()`. Executed if `AssistantContextStore` value is provided.
If not provided, the `DefaultAssistantContextStore` instance will save the `assistant_thread.context` to the instance and attach it to the initial assistant message that was sent to the thread.
+| `say(message: string)` | Alias for the `postMessage` method.
Sends a message to the current assistant thread.
+| `setTitle(title: string)` | [Sets the title](https://api.slack.com/methods/assistant.threads.setTitle) of the assistant thread to capture the initial topic/question.
+| `setStatus(status: string)` | Sets the [status](https://api.slack.com/methods/assistant.threads.setStatus) of the assistant to give the appearance of active processing.
+| `setSuggestedPrompts({ prompts: [{ title: string; message: string; }] })` | Provides the user up to 4 optional, preset [prompts](https://api.slack.com/methods/assistant.threads.setSuggestedPrompts) to choose from.
+
## Framework error types
Bolt includes a set of error types to make errors easier to handle, with more specific contextual information. Below is a non-exhaustive list of error codes you may run into during development:
diff --git a/docs/sidebars.js b/docs/sidebars.js
index e5442a016..9bbd80361 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -33,6 +33,7 @@ const sidebars = {
'concepts/publishing-views',
],
},
+ 'concepts/assistant',
'concepts/custom-steps',
{
type: 'category',