Skip to content

Conversation

@mwbrooks
Copy link
Member

Changelog

Do not include in our Changelog because this is marked as an experimental feature.

Summary

This pull request adds a agent: BoltAgent listener argument, which will be used to access AI agent-related features such as chat_stream and more.

  • Introduces BoltAgent and AsyncBoltAgent classes that provide a convenient chat_stream() method pre-configured with event context defaults (channel_id, thread_ts, team_id, user_id)
  • Wires agent into Bolt's kwargs injection system so listeners can declare it as a parameter (e.g. def handle(agent: BoltAgent)) or access it via context.agent
  • BoltAgent construction is deferred - only created when the listener actually requests the agent or args parameter, avoiding unnecessary object creation on every dispatch

Experimental

The agent: BoltAgent class and listener argument are marked as an experimental feature. We are categorizing experimental features as semver:patch until the experimental status is removed.

No exists tests were modified and the goal is to be a low risk feature to add to main.

Example

@app.event("app_mention")
def handle_mention(agent: BoltAgent):
    stream = agent.chat_stream()
    stream.append(markdown_text="Hello!")
    stream.stop()

Known limitations

chat_stream() currently only works when thread_ts is available in the event context (DMs and threaded replies). Top-level channel messages do not have thread_ts, and ts is not yet provided to BoltAgent - tracked as a FIXME in the code. I've experimented with a solution, but I'd prefer to introduce it in a separate PR so that we can review it independently.

Testing

→ Modified Sample App with agent: BoltAgent and agent.chat_stream()

# Create a modified copy of AI Agent Sample app that uses `agent.chat_stream()`
$ slack create my-app --template https://github.com/slack-samples/bolt-python-assistant-template --branch feat-agent-argument
$ cd my-app/

# Setup the app
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install -r requirements.txt

# Run the app
$ slack run

# Test the app
# → Open DM → Send a message → Confirm it works
# → @Mention Reply in thread → Confirm it works

# Clean up
$ slack delete -f
$ cd ..
$ rm -rf my-app/

Category

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter
  • Document pages under /docs
  • Others

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

  • I've read and understood the Contributing Guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've run ./scripts/install_all_and_run_tests.sh after making the changes.

@mwbrooks mwbrooks added this to the 1.27.1 milestone Feb 11, 2026
@mwbrooks mwbrooks self-assigned this Feb 11, 2026
@mwbrooks mwbrooks requested a review from a team as a code owner February 11, 2026 01:02
@codecov
Copy link

codecov bot commented Feb 11, 2026

Codecov Report

❌ Patch coverage is 91.04478% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.59%. Comparing base (868cedb) to head (c7e0089).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
slack_bolt/agent/agent.py 88.88% 2 Missing ⚠️
slack_bolt/agent/async_agent.py 88.88% 2 Missing ⚠️
slack_bolt/kwargs_injection/async_utils.py 75.00% 1 Missing ⚠️
slack_bolt/kwargs_injection/utils.py 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1437      +/-   ##
==========================================
+ Coverage   90.54%   90.59%   +0.05%     
==========================================
  Files         222      225       +3     
  Lines        7129     7190      +61     
==========================================
+ Hits         6455     6514      +59     
- Misses        674      676       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@mwbrooks mwbrooks marked this pull request as draft February 11, 2026 01:04
…ependency

AsyncBoltAgent imports AsyncWebClient which requires aiohttp. Eagerly
importing it from the agent package __init__ breaks environments where
aiohttp is not installed, since slack_bolt/__init__.py imports BoltAgent
from this package. Follows the existing convention of not adding async
module imports at the top level.
…7 compat

Replace AsyncMock usage with coroutine-returning MagicMock wrappers,
matching the pattern used in the sync test suite. This avoids the
Python 3.8+ AsyncMock and the need for the mock backport package.
@mwbrooks mwbrooks marked this pull request as ready for review February 11, 2026 05:36
Copy link
Member Author

@mwbrooks mwbrooks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments for the kind and knowledgeable reviewers!

Comment on lines +10 to +11
Experimental:
This API is experimental and may change in future releases.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: We're using an "Experimental" warning while we developer this feature. Rather than working on a long-standing branch, we'd like to merge into main under a semver:patch then release a semver:minor when the experimental status is removed.

Comment on lines +13 to +14
FIXME: chat_stream() only works when thread_ts is available (DMs and threaded replies).
It does not work on channel messages because ts is not provided to BoltAgent yet.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: Important callout. I'd like to add ts support in a follow-up PR so that we can discuss the best approach.

next_keys_required: bool = True, # False for listeners / middleware / error handlers
) -> Dict[str, Any]:
all_available_args = {
all_available_args: Dict[str, Any] = {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: This fixed a linter warning

if name == "args":
if isinstance(request, AsyncBoltRequest):
kwargs[name] = AsyncArgs(**all_available_args) # type: ignore[arg-type]
kwargs[name] = AsyncArgs(**all_available_args)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: The above fix allows us to remove this type ignore

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant