Skip to content

Add 'agent: BoltAgent' listener argument#1437

Merged
mwbrooks merged 20 commits intomainfrom
feat-agent-argument
Feb 16, 2026
Merged

Add 'agent: BoltAgent' listener argument#1437
mwbrooks merged 20 commits intomainfrom
feat-agent-argument

Conversation

@mwbrooks
Copy link
Member

@mwbrooks mwbrooks commented Feb 11, 2026

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 96.49123% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.64%. Comparing base (868cedb) to head (3d7f305).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
slack_bolt/kwargs_injection/async_utils.py 87.50% 1 Missing ⚠️
slack_bolt/kwargs_injection/utils.py 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1437      +/-   ##
==========================================
+ Coverage   90.54%   90.64%   +0.09%     
==========================================
  Files         222      226       +4     
  Lines        7129     7182      +53     
==========================================
+ Hits         6455     6510      +55     
+ Misses        674      672       -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

Copy link
Contributor

@WilliamBergamin WilliamBergamin left a comment

Choose a reason for hiding this comment

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

Awesome work 💯 this is already starting to take shape 🚀 left a few comments

One more thing around testing, what do you think about adding some unit tests in tests/slack_bolt(_async)/kwargs_injection/test_args.py that ensure build_required_kwargs only creates a BoltAgent when the agent keyword argument is present in required_arg_names

Comment on lines 87 to 88
if "agent" in required_arg_names or "args" in required_arg_names:
all_available_args["agent"] = request.context.agent
Copy link
Contributor

Choose a reason for hiding this comment

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

Clever 💯 I like this and wonder if we should follow this pattern for other keyword arguments 🚀


What do you think about including a warning here that informs developers that the agent is an experimental feature subject to change?

From what I can tell we should be able to do this by taking advantage of the FutureWarning like this

import warnings

class ExperimentalWarning(FutureWarning):
    """Warning for features that are still in experimental phase."""
    pass

warnings.warn(
    "agent is experimental and may change in future versions.",
    category=ExperimentalWarning,
    stacklevel=2
)

IIUC every time a handler processes a request and uses the "agent" kwargs, this warning would be printed, this might be a bit annoying but it would be clear

Copy link
Member Author

Choose a reason for hiding this comment

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

Great idea, @WilliamBergamin! I didn't know about warnings but it's a very nice way to let developers know that they're accessing an experimental feature.

Commit cf5ef98 adds the above suggestion and implements it. I added you as a co-author because I took your code verbatim. 👌🏻

IIUC every time a handler processes a request and uses the "agent" kwargs, this warning would be printed, this might be a bit annoying but it would be clear

It looks like there is a Warning Filter and the default is to only print the first occurrence. Personally, I'd be okay with printing on every occurrence since it's an experimental feature, but my implementation is using the default.

Here is a preview of the warning:
image

Copy link
Member Author

Choose a reason for hiding this comment

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

I like this and wonder if we should follow this pattern for other keyword arguments

🙂 btw, I pulled this idea from the complete/fail handlers that are also constructed upon request. It's a cool feature of Python!

from .response import BoltResponse

# AI Agents & Assistants
from .agent import BoltAgent
Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 Maybe we should wait until the feature is GA before exporting it here?

Developers should still be able to import the class directly with something like
from slack_bolt.agent import BoltAgent

Copy link
Member Author

Choose a reason for hiding this comment

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

🤔 If possible, I'd prefer to export the BoltAgent now.

  1. Our work will be driven through developing a sample app, it would allow us to immediately understand what the production code will looks & feels like. For example, here is what the import would look like today..
  2. Our new ExperimentalWarning makes it clear that the developer is using something that's unstable and at their own risk.
  3. When it comes to remove the Experimental status, I'd prefer to not have to add additional code such as a public export. I'd feel more confident about removing the Experimental status if we had tested everything throughout the entire process.

👉🏻 That said, I trust you decision over ours since you're more experienced with Bolt Python. If it makes you feel more comfortable waiting until the feature is GA, just let me know.

Comment on lines 18 to 20
if TYPE_CHECKING:
from slack_bolt.agent.async_agent import AsyncBoltAgent

Copy link
Contributor

Choose a reason for hiding this comment

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

TIL: TYPE_CHECKING, it seems awesome and maybe we can use it elsewhere to improve our types checking for async stuff

IIUC this is to ensure that we

  • Do not import AsyncBoltAgent whenever AsyncBoltContext is imported
  • Static type checking around AsyncBoltAgent passes
  • AsyncBoltAgent is only imported when context.agent is invoked

Copy link
Member Author

Choose a reason for hiding this comment

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

Static type checking around AsyncBoltAgent passes

This was my main motivation to add it. At first, I felt like it was the wrong approach, but it seems be correct and necessary with the Async modules.

Comment on lines 61 to 62
resolved_channel = channel or self._channel_id
resolved_thread_ts = thread_ts or self._thread_ts
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering here if falling back to the class instances channel_id, thread_ts, team_id and user_id is the best behavior 🤔

Like if a developer only passes one of these parameters and are unaware of the the fallback values they could end up chat streaming to the wrong location?
I'm not super familiar with chat_stream so this might be a non issue

Copy link
Contributor

Choose a reason for hiding this comment

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

💭 i agree with @WilliamBergamin! channel_id and thread_ts seem to work as a pair so if a user changes one they should be aware they need to change the other. Maybe we throw a warning when one of these params is changed but not the other 🤔 ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @WilliamBergamin and @srtaalej for your thorough review! I really appreciate that you're actually taking the time to think through the logic, rather than glance over the source code. 🙇🏻

I agree, a mismatch of arguments could lead to bugs that are difficult to diagnose. An all-or-nothing approach would be more fitting, where the developer either relies on the default values set by the context or provides all of the arguments. If a partial set of arguments are provided, then we can raise an error like @srtaalej suggested.

Commit 724ea5f disallows partial overrides of the arguments and I've added some tests around it. 🚀

Copy link
Member

Choose a reason for hiding this comment

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

🐣 ramble: I'm following this discussion curious! I agree the confusion of unknown defaults isn't good, but find extra effort in gathering the details I'm not changing...

At the moment the FIXME hints at the edge I'm finding where ts is unknown for app mentions in this class. We might find enhancement for this, but I'm wondering if overriding all defaults to stream to multiple different threads in the same channel for the same person at the same time - perhaps for subagent workflows - might be difficult.

Not a blocker at all for me but I want to share thought!

Copy link
Member Author

Choose a reason for hiding this comment

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

@zimeg Totally fair point! My best suggestion at moving forward is that it's easier to loosen constraints than tighten them later. So, if we start with the "all or nothing" approach above, then we can loosen it when we come up against your advanced use-cases. 🤔

Comment on lines 80 to 105
def test_agent_chat_stream_overrides_context_defaults(self):
"""Explicit kwargs to chat_stream() override context defaults."""
client = MagicMock(spec=WebClient)
client.chat_stream.return_value = MagicMock(spec=ChatStream)

agent = BoltAgentDirect(
client=client,
channel_id="C111",
thread_ts="1234567890.123456",
team_id="T111",
user_id="W222",
)
stream = agent.chat_stream(
channel="C999",
thread_ts="9999999999.999999",
recipient_team_id="T999",
recipient_user_id="U999",
)

client.chat_stream.assert_called_once_with(
channel="C999",
thread_ts="9999999999.999999",
recipient_team_id="T999",
recipient_user_id="U999",
)
assert stream is not None
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about creating a tests.slack_bolt.agent and tests.slack_bolt_async.agent directory where unit tests dedicated to the BoltAgent like these ones can live?

Copy link
Contributor

Choose a reason for hiding this comment

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

agreed 🙌 it looks like we're mixing unit tests with full app scenarios 🤔 might be nice to follow up on this in another pr ⭐

Copy link
Member Author

Choose a reason for hiding this comment

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

Excellent call @WilliamBergamin and @srtaalej! 🙇🏻 I should have caught this one before sharing the PR.

In commit 0492aa0, I've moved the agent unit tests to tests/slack_bolt/agent/ and tests/slack_bolt_async/agent/.

I kept the integration tests for agent in the current file, which test that the agent is injected into the context property, available to the listeners, and print an experimental warning when accessed.

If you'd like those moved under the unit test directory, I can do that. But it seems like they belong in the integration tests directory.

Copy link
Contributor

@srtaalej srtaalej left a comment

Choose a reason for hiding this comment

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

left some comments but its LGTM! thank you for bringing these changes in so quickly 🚀 ⭐ ⭐ ⭐

Comment on lines 80 to 105
def test_agent_chat_stream_overrides_context_defaults(self):
"""Explicit kwargs to chat_stream() override context defaults."""
client = MagicMock(spec=WebClient)
client.chat_stream.return_value = MagicMock(spec=ChatStream)

agent = BoltAgentDirect(
client=client,
channel_id="C111",
thread_ts="1234567890.123456",
team_id="T111",
user_id="W222",
)
stream = agent.chat_stream(
channel="C999",
thread_ts="9999999999.999999",
recipient_team_id="T999",
recipient_user_id="U999",
)

client.chat_stream.assert_called_once_with(
channel="C999",
thread_ts="9999999999.999999",
recipient_team_id="T999",
recipient_user_id="U999",
)
assert stream is not None
Copy link
Contributor

Choose a reason for hiding this comment

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

agreed 🙌 it looks like we're mixing unit tests with full app scenarios 🤔 might be nice to follow up on this in another pr ⭐

    Adds a custom ExperimentalWarning (subclass of FutureWarning) that is
    emitted when a listener explicitly requests the `agent` argument,
    informing developers that this feature is experimental and subject to
    change.

    Co-Authored-By: William Bergamin <wbergamin@salesforce.com>
Split agent tests so unit tests live in tests/slack_bolt/agent/ and
tests/slack_bolt_async/agent/, matching the existing convention where
test directories mirror the source layout. Integration tests that
dispatch through App remain in scenario_tests/.
@mwbrooks
Copy link
Member Author

Thanks a ton for the amazing feedback @WilliamBergamin @srtaalej! ❤️ Truly, I appreciate that you've taken the time to think deeply about the changes - especially since I'm still quite new to the Bolt Python codebase.

I've followed up on the above feedback, so please take a second look when you have a spare moment. 🙇🏻

Copy link
Member

@zimeg zimeg left a comment

Choose a reason for hiding this comment

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

@mwbrooks Such an impressive PR! I echo the excitement for these changes landing as experimental so fast 👾 ✨

A few comments I left are non-blocking and ask questions on error handling and context setups. I'm hoping I left enough praise too since these are great changes all around.

Whenever's right we should merge this for iterations next 🚢 💨

Comment on lines 61 to 62
resolved_channel = channel or self._channel_id
resolved_thread_ts = thread_ts or self._thread_ts
Copy link
Member

Choose a reason for hiding this comment

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

🐣 ramble: I'm following this discussion curious! I agree the confusion of unknown defaults isn't good, but find extra effort in gathering the details I'm not changing...

At the moment the FIXME hints at the edge I'm finding where ts is unknown for app mentions in this class. We might find enhancement for this, but I'm wondering if overriding all defaults to stream to multiple different threads in the same channel for the same person at the same time - perhaps for subagent workflows - might be difficult.

Not a blocker at all for me but I want to share thought!

Comment on lines 72 to 75
if resolved_thread_ts is None:
raise ValueError(
"thread_ts is required: provide it as an argument or ensure thread_ts is set in the event context"
)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if resolved_thread_ts is None:
raise ValueError(
"thread_ts is required: provide it as an argument or ensure thread_ts is set in the event context"
)

🪓 thought(non-blocking): Perhaps against above comments too, but I have small preference to pass missing values to the API for errors from upstream. I realize though that catching error earlier might be preferred, and we have validation logic for similar parameters too!

Copy link
Member Author

Choose a reason for hiding this comment

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

➕ I agree, we should allow the underlying chat_stream implementation and Slack API enforce the argument error handling. Commit 29ffbb8 removes this logic for all of the params. Thanks for the catch @zimeg! 🙇🏻

`AsyncBoltAgent` instance
"""
if "agent" not in self:
from slack_bolt.agent.async_agent import AsyncBoltAgent
Copy link
Member

Choose a reason for hiding this comment

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

👁️‍🗨️ question: Perhaps similar to above discussion, but I'm wondering if we can comment inline a quick note for this moreso dynamic import? I notice similar pattern in the following file and would be tempted to move this without thinking too much...

Copy link
Contributor

Choose a reason for hiding this comment

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


When this experiment is removed we probably want to remove the dynamic import as it can lead runtime error

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for catching this @zimeg. I agree, I'd probably be the developer who "refactored" the code to readability only to introduce an unintended side effect. Commit c2a590d adds a comment.

I'm not 100% certain that we'll keep the agent argument. If we do, then we'll want to revisit @WilliamBergamin's comment about removing the dynamic import to avoid runtime errors.

"set_status",
"set_title",
"set_suggested_prompts",
"agent",
Copy link
Member

Choose a reason for hiding this comment

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

🪬 quibble: I'm wanting this to be alphabetical so much ahhaha!

Copy link
Member

Choose a reason for hiding this comment

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

🌚 question: I'm curious if agent should be included with "context" or kept to kwargs injections?

This is unclear to me since the functions and values above seem to map well to details included with an incoming request, but I'd find it odd to access agent in such pattern:

streamer = context.agent.chat_stream()

It might just be unfamiliar to me! I'm also unsure if the same message context can be gathered from the kwargs setups for chat_stream creations and don't consider this blocking at all FWIW.

Copy link
Member Author

Choose a reason for hiding this comment

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

@zimeg Fantastic comment and catch! 🎣 I agree, we don't want the context.agent.chat_stream() pattern and only want def handler(agent) as an argument.

I wish I would have noticed this sooner because the refactor removed a lot of code and simplified the implementation and moves all of the agent construction to a single place (kwargs injection) 😅

Commit f10f94d removes context.agent.*.

Comment on lines 83 to 108
def test_agent_accessible_via_context(self):
app = App(client=self.web_client)

state = {"called": False}

def assert_target_called():
count = 0
while state["called"] is False and count < 20:
sleep(0.1)
count += 1
assert state["called"] is True
state["called"] = False

@app.event("app_mention")
def handle_mention(context: BoltContext):
agent = context.agent
assert agent is not None
assert isinstance(agent, BoltAgentDirect)
# Verify the same instance is returned on subsequent access
assert context.agent is agent
state["called"] = True

request = BoltRequest(body=app_mention_event_body, mode="socket_mode")
response = app.dispatch(request)
assert response.status == 200
assert_target_called()
Copy link
Member

Choose a reason for hiding this comment

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

🧪 note: Ahaha I notice we might be expecting context and kwargs arguments to match for the agent! Thanks for including these tests!

@lukegalbraithrussell lukegalbraithrussell requested a review from a team as a code owner February 12, 2026 19:22
Copy link
Contributor

@WilliamBergamin WilliamBergamin left a comment

Choose a reason for hiding this comment

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

Looks good 💯 Left a few comments but they are none blocking

`AsyncBoltAgent` instance
"""
if "agent" not in self:
from slack_bolt.agent.async_agent import AsyncBoltAgent
Copy link
Contributor

Choose a reason for hiding this comment

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


When this experiment is removed we probably want to remove the dynamic import as it can lead runtime error

all_available_args[k] = v

# Defer agent creation to avoid constructing AsyncBoltAgent on every request
if "agent" in required_arg_names or "args" in required_arg_names:
Copy link
Contributor

Choose a reason for hiding this comment

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

Just noticed this but why would we want to initialize this if "args" in required_arg_names? Shouldn't it be only when "agent" in required_arg_names?

Copy link
Member Author

Choose a reason for hiding this comment

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

@WilliamBergamin thanks for catching this!

The "args" in required_arg_names check exists because Args has an agent field, so when a listener uses def handle(args), the Args object is constructed with all_available_args - which means agent needs to be in all_available_args or it defaults to None.

However, I agree we should only construct the agent when "agent" in required_arg_names. If someone uses args.agent, it'll be None. That seems to be the experience we want.

It also simplifies our implementation further. 🧹 ✨

Copy link
Member Author

Choose a reason for hiding this comment

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

Commit 721b634 removes the "args" in required_arg_names.

Comment on lines 91 to 96
if "agent" in required_arg_names:
warnings.warn(
"The agent listener argument is experimental and may change in future versions.",
category=ExperimentalWarning,
stacklevel=2, # Point to the caller, not this internal helper
)
Copy link
Contributor

Choose a reason for hiding this comment

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

I like that we have a separate block for this it will make the PR to clean up the experiment simple 💯

Copy link
Member Author

Choose a reason for hiding this comment

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

The ExperimentalWarning was a great suggestion @WilliamBergamin! Thanks for it!


class ExperimentalWarning(FutureWarning):
"""Warning for features that are still in experimental phase."""

Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: I don't think we need this empty line

Copy link
Member Author

Choose a reason for hiding this comment

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

🫡 Roger! Commit 0195ee2 removes the whitespace. I'm still a little unclear on when Python wants/requires empty lines and when it doesn't.

from slack_bolt.agent.agent import BoltAgent


class TestBoltAgent:
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice 💯 makes things easier to follow since it aligns with this projects tests strategy

@mwbrooks mwbrooks added the experiment Experimental feature documented with ExperimentalWarning and pydoc Experiment section label Feb 16, 2026
@mwbrooks
Copy link
Member Author

mwbrooks commented Feb 16, 2026

🙇🏻 Thank you @WilliamBergamin @zimeg @srtaalej! This has been incredible feedback that's transformed this pull request from it's original state. I believe the code is now simpler, better documented, and the feature is scoped tighter. We also now have the ExperimentalWarning class to leverage for our future work and I've added an experiment label to put onto each pull request. 👾 🚀

🧪 I've confirmed that this still works with our sample app branch. ✨

:shipit: I'll merge this pull request now and we can iterate on it!

@mwbrooks mwbrooks merged commit 1ad642e into main Feb 16, 2026
16 checks passed
@mwbrooks mwbrooks deleted the feat-agent-argument branch February 16, 2026 06:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:async area:sync enhancement New feature or request experiment Experimental feature documented with ExperimentalWarning and pydoc Experiment section semver:patch tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants