---
title: "Assistant Context"
description: "Learn how Slack AI Assistants maintain context across conversations. Implement handlers for thread lifecycle events that track context changes, update suggested prompts, and ensure your assistant stays relevant as conversations evolve."
canonical_url: "https://vercel.com/academy/slack-agents/assistant-thread-context-changed"
md_url: "https://vercel.com/academy/slack-agents/assistant-thread-context-changed.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-04-09T11:08:45.110Z"
content_type: "lesson"
course: "slack-agents"
course_title: "Slack Agents on Vercel with the AI SDK"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Assistant Context

# Handle assistant threads that adapt as users navigate Slack

Slack's AI Assistant interface persists as users navigate channels—it's a split-view that stays open while they browse. The `assistant_thread_started` event fires when users open your assistant. The `assistant_thread_context_changed` event fires when they switch channels while it's open. Handle these to set and update suggested prompts dynamically.

## Outcome

Handle assistant thread lifecycle events and use AI to generate context-aware suggested prompts based on the channel the user is viewing.

## Fast Track

1. Create handlers for `assistant_thread_started` and `assistant_thread_context_changed`
2. Use AI to generate context-aware prompts based on channel name
3. Set suggested prompts with `client.assistant.threads.setSuggestedPrompts()`
4. Test by opening assistant and switching channels—prompts adapt to context

\*\*Warning: Assistant Availability\*\*

**Enterprise Sandbox/Grid:** Assistant features work out of the box\
**Pro/Business+:** May require admin enablement + user opt-in (Preferences → Navigation → App agents & assistants)

If assistants aren't available in your workspace, **skip this lesson**—your bot still works fine. The template already has placeholder handlers you can enhance later.

## Building on Previous Lessons

- **From [Bolt Middleware](./bolt-nitro-middleware-and-logging)**: Use `context.correlation` for tracking thread lifecycle
- **Sets up Section 4:** You'll learn AI orchestration patterns—system prompts, tools, status updates, error resilience

## How Assistant Threads Work

Slack's AI Assistant is a persistent split-view that stays open as users navigate:

1. **`assistant_thread_started`** - User opens assistant → set initial suggested prompts
2. **`assistant_thread_context_changed`** - User switches channels → update prompts based on new context

Both events include `assistant_thread.channel_id` (where assistant lives) and `assistant_thread.context.channel_id` (where user is browsing).

## Hands-On Exercise 3.4

Implement handlers for both assistant thread events:

**Requirements:**

1. Add required scopes to manifest: `channels:read`, `groups:read`, `im:read`, `mpim:read` (needed for `conversations.info`)
2. Create `server/listeners/events/assistant-thread-started.ts` with generic starter prompts
3. Create `server/listeners/events/assistant-thread-context-changed.ts` that generates AI prompts
4. Fetch channel name with `conversations.info()` when context changes
5. Use `generateObject` with `gpt-4o-mini` to create 3 context-relevant prompts
6. Call `client.assistant.threads.setSuggestedPrompts()` with AI-generated prompts
7. Add correlation logging to all handlers
8. Register both in `server/listeners/events/index.ts`

**Implementation hints:**

- Prompts format: `{ title: "Label (max 75 chars)", message: "Full prompt text" }`
- Use schema: `z.object({ prompts: z.array(z.object({ title: z.string().max(75), message: z.string() })).length(3) })`
- Prompt AI: "User is viewing #${channelName}. Suggest 3 specific, helpful assistant prompts relevant to this channel"
- Graceful fallback: If AI or `conversations.info` fails, use static generic prompts
- Zod's `.length(3)` enforces exactly 3 prompts (Slack supports 1-10)
- Titles >75 chars get truncated by Slack—`.max(75)` prevents this

\*\*Warning: Required Scopes for Channel Info\*\*

`conversations.info()` requires **`:read`** scopes to fetch channel metadata (name, topic, etc.):

- `channels:read` - Public channels
- `groups:read` - Private channels
- `im:read` - Direct messages
- `mpim:read` - Group DMs

Your manifest already has `channels:history` (for reading messages), but that's different from `channels:read` (for metadata). Add all four `:read` scopes to your manifest and run `slack run` to reinstall. Without these, `conversations.info()` returns `missing_scope` errors.

## Try It

1. **Enable and open assistant:**
   - If needed: Slack Preferences → Navigation → enable your app under "App agents & assistants"
   - Click assistant icon in top-right of Slack (or use split-view)
   - Verify 3 suggested prompts appear

2. **Test context changes:**
   - Keep assistant open, switch to a different channel (e.g., #engineering)
   - Check if prompts update to context-specific suggestions
   - Switch to another channel (e.g., #marketing) and observe behavior
   - **Verify in logs:**
     - `"Assistant thread context changed"` with channel IDs
     - `"Generated AI prompts for context"` with channel name
     - `setSuggestedPrompts` API call succeeds (`"ok":true`)

3. **Verify logs:**
   ```
   [INFO] bolt-app {
     event_id: '...',
     user_id: 'U09D6B53WP4',
     channel_id: 'D09EFQZUW6P',
     thread_ts: '...'
   } Assistant thread started
   ```

## Commit

```bash
git add -A
git commit -m "feat(assistant): handle thread lifecycle events

- Create assistant_thread_started handler with suggested prompts
- Create assistant_thread_context_changed handler for navigation tracking
- Add correlation logging to both handlers
- Register handlers in events/index.ts"
```

## Done-When

- [x] Created `server/listeners/events/assistant-thread-started.ts`
- [x] Created `server/listeners/events/assistant-thread-context-changed.ts`
- [x] Both handlers set suggested prompts with `setSuggestedPrompts()`
- [x] All handlers include correlation logging
- [x] Registered both events in `server/listeners/events/index.ts`
- [x] Tested opening assistant and switching channels

## Step-by-Step Solution

### Step 1: Create assistant\_thread\_started handler

Create `server/listeners/events/assistant-thread-started.ts`:

```typescript title="/slack-agent/server/listeners/events/assistant-thread-started.ts"
import type { AllMiddlewareArgs, SlackEventMiddlewareArgs } from "@slack/bolt";

export const assistantThreadStartedCallback = async ({
  event,
  client,
  logger,
  context,
}: AllMiddlewareArgs & SlackEventMiddlewareArgs<"assistant_thread_started">) => {
  const { assistant_thread } = event;
  
  logger.info({
    ...context.correlation,
    user_id: assistant_thread.user_id,
    channel_id: assistant_thread.channel_id,
    thread_ts: assistant_thread.thread_ts,
  }, "Assistant thread started");
  
  // Set initial suggested prompts
  const prompts = [
    { title: "Tell me a joke", message: "Tell me a programming joke" },
    { title: "Random fact", message: "Share a random tech fact" },
    { title: "Hello there!", message: "Hello! How are you today?" },
  ];
  
  try {
    await client.assistant.threads.setSuggestedPrompts({
      channel_id: assistant_thread.channel_id,
      thread_ts: assistant_thread.thread_ts,
      prompts,
    });
  } catch (error) {
    logger.error({
      ...context.correlation,
      error: error instanceof Error ? error.message : String(error),
    }, "Failed to set suggested prompts");
  }
};
```

### Step 2: Create assistant\_thread\_context\_changed handler

Create `server/listeners/events/assistant-thread-context-changed.ts`:

```typescript title="/slack-agent/server/listeners/events/assistant-thread-context-changed.ts"
import type { AllMiddlewareArgs, SlackEventMiddlewareArgs } from "@slack/bolt";
import { generateObject } from "ai";
import { z } from "zod";

export const assistantThreadContextChangedCallback = async ({
  event,
  client,
  logger,
  context,
}: AllMiddlewareArgs & SlackEventMiddlewareArgs<"assistant_thread_context_changed">) => {
  const { assistant_thread } = event;
  // Two channel IDs: assistant_thread.channel_id = where assistant lives (DM)
  //                   assistant_thread.context.channel_id = where user is browsing
  const currentChannelId = assistant_thread.context?.channel_id;
  
  logger.info({
    ...context.correlation,
    thread_ts: assistant_thread.thread_ts,
    assistant_dm: assistant_thread.channel_id,
    browsing_channel: currentChannelId,
  }, "Assistant thread context changed");
  
  // Generate context-aware prompts with AI
  let prompts = [
    { title: "What's this about?", message: "Summarize this channel's recent activity" },
    { title: "Help me understand", message: "Explain what's happening here" },
    { title: "Anything important?", message: "Highlight key points from recent messages" },
  ];
  
  try {
    // Fetch channel name to give AI context
    if (currentChannelId) {
      const channelInfo = await client.conversations.info({ channel: currentChannelId });
      const channelName = channelInfo.channel?.name || "unknown-channel";
      
      // Use AI to generate context-specific prompts
      const aiPrompts = await generateObject({
        model: "openai/gpt-4o-mini",
        schema: z.object({
          prompts: z.array(z.object({
            title: z.string().max(75),
            message: z.string(),
          })).length(3),
        }),
        prompt: `User is viewing Slack channel #${channelName}. Suggest 3 helpful, specific assistant prompts relevant to this channel context. Keep titles under 75 characters.`,
      });
      
      prompts = aiPrompts.object.prompts;
      logger.info({
        ...context.correlation,
        channel: channelName,
      }, "Generated AI prompts for context");
    }
  } catch (error) {
    logger.error({
      ...context.correlation,
      error: error instanceof Error ? error.message : String(error),
    }, "Failed to generate context-aware prompts (API or AI error), using fallback");
    // Falls back to static prompts defined above
  }
  
  try {
    await client.assistant.threads.setSuggestedPrompts({
      channel_id: assistant_thread.channel_id,
      thread_ts: assistant_thread.thread_ts,
      prompts,
    });
  } catch (error) {
    logger.error({
      ...context.correlation,
      error: error instanceof Error ? error.message : String(error),
    }, "Failed to set suggested prompts");
  }
};
```

### Step 3: Register both handlers

Update `server/listeners/events/index.ts`:

```typescript title="/slack-agent/server/listeners/events/index.ts"
import type { App } from "@slack/bolt";
import appHomeOpenedCallback from "./app-home-opened";
import appMentionCallback from "./app-mention";
import { assistantThreadStartedCallback } from "./assistant-thread-started";
import { assistantThreadContextChangedCallback } from "./assistant-thread-context-changed"; // Add import

const register = (app: App) => {
  app.event("app_home_opened", appHomeOpenedCallback);
  app.event("app_mention", appMentionCallback);
  app.event("assistant_thread_started", assistantThreadStartedCallback);
  app.event("assistant_thread_context_changed", assistantThreadContextChangedCallback); // Add registration
};

export default { register };
```

## Troubleshooting

**Assistant doesn't appear:**

- Verify manifest has `assistant:write` scope and assistant events subscribed
- Run `slack run` to reinstall after manifest changes
- Check user enabled your app in Preferences → Navigation → App agents & assistants

**Events not firing:**

- Handlers must be registered in `server/listeners/events/index.ts`
- Check logs for event payloads to verify they're reaching your server
- Assistants may not be available on all workspace types

**Suggested prompts don't appear:**

- Verify `setSuggestedPrompts()` is called in your handler
- Prompts array cannot be empty
- Prompt titles must be ≤75 characters
- Check logs to verify API calls succeed (`"ok":true` in response)

**Assistant responds with blank messages:**

- The template's AI response handler (`server/lib/ai/respond-to-message.ts`) uses `stepCountIs(5)` to limit tool calls. If the assistant tries to call multiple tools (fetching thread context, updating status, etc.), it hits the limit before generating text and sends a blank response.
- **Quick fix:** Increase the limit in `respond-to-message.ts`: change `stopWhen: stepCountIs(5)` to `stopWhen: stepCountIs(10)` or more as needed.
- **Why it exists:** Step limits prevent infinite tool-calling loops. You'll learn about agent loop control in Section 5 (AI Orchestration). For now, just bump the limit so the assistant can work.
- See [AI SDK Loop Control docs](https://ai-sdk.dev/docs/agents/loop-control) for details on `stopWhen`, `stepCountIs`, and other conditions.

## What's Next

You've built handlers for Slack's interaction surfaces—commands, shortcuts, modals, App Home, and assistant threads. These trigger actions but don't leverage the bot's full AI capabilities. Section 5 (AI Orchestration & Tools) teaches you to combine interaction surfaces with intelligent AI responses: system prompts that shape behavior, tools that fetch context and take actions, streaming status updates, and error resilience. Your bot stops being a form processor and becomes an actual agent.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
