feat: Expose tool call id lifecyle hooks #1992
Open
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Tool Call ID Exposure in Lifecycle Hooks
Overview
This document describes the implementation of issue #1849: exposing
tool_call_idto lifecycle hooks (on_tool_startandon_tool_end).Problem Statement
Previously, lifecycle hooks received a
RunContextWrapperthat did not expose the tool call ID. This made it difficult to:Solution
The solution leverages the existing
ToolContextclass (a subclass ofRunContextWrapper) which already contains thetool_call_idfield. During tool execution, hooks now receive aToolContextinstance instead of a plainRunContextWrapper.Key Design Decisions
✅ What We Did
_run_impl.pyto createToolContextinstances when invoking hookslifecycle.pyto inform users thatcontextwill be aToolContextduring tool execution❌ What We Avoided
We initially attempted to add a
@property tool_call_idtoRunContextWrapperthat returnsNoneby default, expectingToolContextto override it. This approach failed because:ToolContextis a@dataclasswithtool_call_idas a fieldAttributeError: property 'tool_call_id' of 'ToolContext' object has no setterThe final solution is cleaner:
ToolContextalready has the field, and users can useisinstance()checks to access it safely.Implementation Details
1. Modified Files
src/agents/_run_impl.pyComputer Tool Execution (Line ~1278):
Local Shell Tool Execution (Line ~1408):
Note: Function tools already used
ToolContextwhen calling their functions, so hooks for function tools automatically had access totool_call_id.src/agents/lifecycle.pyUpdated docstrings for both
RunHooksBaseandAgentHooksBase:tests/test_tool_call_id_in_hooks.pyCreated a comprehensive test suite with 4 test cases:
test_tool_call_id_exposed_in_function_tool_hookstool_call_idis accessible in bothon_tool_startandon_tool_endtool_call_idappears in both hooks for the same calltest_tool_call_id_for_multiple_tool_callstool_call_idtest_tool_call_id_none_outside_tool_contextcontextis NOT aToolContextin non-tool hookson_llm_startandon_agent_starthookstest_tool_call_id_in_agent_scoped_hooksToolContextexamples/basic/lifecycle_example.pyUpdated to demonstrate proper usage with
isinstance()checks:2. How to Use
Basic Usage
Advanced Usage: Tracking Tool Calls
3. Type Safety
The
contextparameter in lifecycle hooks is typed asRunContextWrapper[TContext], which is the base class. During tool execution, it will actually be aToolContextinstance.Type-safe access:
4. Backward Compatibility
✅ Fully backward compatible
contextparameter type hasn't changed (RunContextWrapper)contextis now aToolContext(subclass) during tool executionTesting
Test Results
All 4 new tests pass:
Coverage
The implementation covers:
RunHooksBase)AgentHooksBase)ToolContext)Code Quality
Linting
$ uv run ruff check src/agents/ tests/test_tool_call_id_in_hooks.py All checks passed!Formatting
Type Checking
All modified files pass mypy type checking with no errors.
Benefits
For Users
tool_call_idon_tool_startandon_tool_endevents for the same callFor Developers
ToolContextclassMigration Guide
No Migration Needed
Existing code continues to work without changes. To use the new feature:
Future Enhancements
Potential improvements for future iterations:
@overloadto provide type-specific signatures for tool hooksToolContextfor common operationstool_call_idtool_call_idReferences
src/agents/_run_impl.pysrc/agents/lifecycle.pytests/test_tool_call_id_in_hooks.py(new)examples/basic/lifecycle_example.pyRunContextWrapper(src/agents/run_context.py)ToolContext(src/agents/tool_context.py)RunHooksBase&AgentHooksBase(src/agents/lifecycle.py)Conclusion
The implementation successfully exposes
tool_call_idto lifecycle hooks in a clean, type-safe, and backward-compatible manner. Users can now track individual tool invocations uniquely, enabling better observability, tracing, and debugging of agent systems.Status: ✅ Complete and tested
Date: October 25, 2025
Author: Implementation for issue #1849