Skip to content

feat: expose tool_use and agent through ToolContext to decorated tools #557

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

dbschmigelski
Copy link
Member

@dbschmigelski dbschmigelski commented Jul 28, 2025

Description

Previously, @tool-decorated functions could not access the same contextual information available to module-based tools, specifically the tool_use_id and other framework-provided data. This limitation made it impossible for decorated tools to implement features like global storage indexing or request tracking that required access to tool execution metadata.

Solution

Reserved keys are an issue for decorated tools. This is because the user is the one who can freely add parameters. Rather than making an assumption about override behavior, and potential breaking changes in the event we want to pass along new information. The proposal opts to use one additional keyword, tool_context.
The keyword will NOT be breaking for customers who may already have a tool defined with a tool_context param. This is because we will add a new annotation like

# default to False, if True injects param named tool_context with ToolContext 
# object. Alternatively, a custom param name can be used by passing a string
@tool(context: str | bool) 

@tool(context=True)
def some_tool(message: str, tool_context: ToolContext) -> str:
    ....
@tool(context="custom_context_name")
def some_other_tool(message: str, custom_context_name: ToolContext) -> str:
    ....

Module and class based tools(AgentTool/PythonAgentTool) do not have the same as the user does not have control over the inputs. HOWEVER, they do have control over kwargs which are passed along to tools. These kwargs are passed in via invocation_state. For class the invocation_state is kept as a dict, meaning we can add new fields.

For module, unfortunately, they are unpacked.
Invocation state is used to store BOTH user defined kwargs, and internal data (1,2,3...) This means there is are existing hidden keywords which can conflict with customer tool definitions. If we add more, we risk breaking them. For module for any new fields we want to pass, or tool context, we would want an opt-in mechanism similar to the one we have for decorated tools.

A follow up issue will be created to discuss exposing ToolContext and what that means regarding backwards compatibility

Related Issues

#194

Documentation PR

Documentation PR to be raised before merging after naming is aligned.

Type of Change

New feature
Breaking change

Testing

How have you tested the change? Verify that the changes do not break functionality or introduce warnings in consuming repositories: agents-docs, agents-tools, agents-cli

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@dbschmigelski dbschmigelski marked this pull request as ready for review July 28, 2025 17:06
@dbschmigelski dbschmigelski requested a review from pgrayy July 31, 2025 16:29
@dbschmigelski
Copy link
Member Author

Related #580

After discussing offline that we want so synchronize the overall tool context population. So a revision will be made so the way/object we populate decorated, module, class tools will all try to leverage the same object.

@dbschmigelski
Copy link
Member Author

While inspecting how to expose this through a common pattern I came across

result = await self._tool_func(tool_use, **invocation_state)
. This seems like a bug where we are unpacking the invocation state instead of unpacking the kwargs.

Additionally, some of the unification concerns are not necessary since the tool definitions are not user defined when it comes to AgentTools - unlike with decorated tools. So we do not need to necessarily pass the object but want to have a mechanism where, unpacked or not, the same information is available in all tool patterns.

@dbschmigelski
Copy link
Member Author

dbschmigelski commented Aug 11, 2025

Earlier I stated

After discussing offline that we want so synchronize the overall tool context population. So a revision will be made so the way/object we populate decorated, module, class tools will all try to leverage the same object.

After reviewing the codebase a bit more I do not believe this is necessary. Reserved keys are an issue for decorated tools. This is because the user is the one who can freely add parameters. Rather than making an assumption about override behavior, and potential breaking changes in the event we want to pass along new information. this PR opts to reserve one new additional keyword, strands_context.

The issue above is NOT an issue for module or class based tools. This is because for both WE, as the SDK, define the inputs, not the user. So there is no risk of collision.

here is still more we can do to guide the user. So we introduce the InvocationState typed dict. This will help the user understand what is available to them. Right now they simple see that a dict is unpacked.

        if inspect.iscoroutinefunction(self._tool_func):
            result = await self._tool_func(tool_use, **invocation_state)
        else:
            result = await asyncio.to_thread(self._tool_func, tool_use, **invocation_state)

In the future we have the ability to pass along the StrandsContext. But for now it seems strange to pass along everything unpacked in addition to it packed. This is simply a constraint to avoid making breaking changes.


Note, TypedDict does not extend dict. Meaning if a user is referencing invocation_state as a dict[str, Any] externally, this will be breaking.

@dbschmigelski dbschmigelski changed the title feat: expose internals through tool decorator param StrandsContext feat: expose tool_use and agent through ToolContext to decorated tools Aug 12, 2025
Co-authored-by: Mackenzie Zastrow <3211021+zastrowm@users.noreply.github.com>
Co-authored-by: Mackenzie Zastrow <3211021+zastrowm@users.noreply.github.com>
@dbschmigelski dbschmigelski merged commit 606f657 into strands-agents:main Aug 14, 2025
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants