Tool Hooks
Use pre and post hooks to modify tool behavior.
You can use tool hooks to perform validation, logging, or any other logic before or after a tool is called.
A tool hook is a function that takes a function name, function call, and arguments. Optionally, you can access the Agent or Team object as well. Inside the tool hook, you have to call the function call and return the result.
It is important to use exact parameter names when defining a tool hook. agent, team, run_context, function_name, function_call, and arguments are available parameters.
For example:
1def logger_hook(2 function_name: str, function_call: Callable, arguments: Dict[str, Any]3):4 """Log the duration of the function call"""5 start_time = time.time()67 # Call the function8 result = function_call(**arguments)910 end_time = time.time()11 duration = end_time - start_time1213 logger.info(f"Function {function_name} took {duration:.2f} seconds to execute")1415 # Return the result16 return resultor
1def confirmation_hook(2 function_name: str, function_call: Callable, arguments: Dict[str, Any]3):4 """Confirm the function call"""5 if function_name != "get_top_hackernews_stories":6 raise ValueError("This tool is not allowed to be called")7 return function_call(**arguments)You can assign tool hooks on agents and teams. The tool hooks will be applied to all tool calls made by the agent or team.
For example:
1agent = Agent(2 model=OpenAIResponses(id="gpt-5.2"),3 tools=[HackerNewsTools()],4 tool_hooks=[logger_hook],5)You can also get access to the RunContext object in the tool hook. Inside the run context, you will find the session state, dependencies, and metadata.
1from kern.run import RunContext23def grab_customer_profile_hook(4 run_context: RunContext, function_name: str, function_call: Callable, arguments: Dict[str, Any]5):6 if not run_context.session_state:7 run_context.session_state = {}89 cust_id = arguments.get("customer")10 if cust_id not in run_context.session_state["customer_profiles"]:11 raise ValueError(f"Customer profile for {cust_id} not found")12 customer_profile = run_context.session_state["customer_profiles"][cust_id]1314 # Replace the customer with the customer_profile for the function call15 arguments["customer"] = json.dumps(customer_profile)16 # Call the function with the updated arguments17 result = function_call(**arguments)1819 return resultMultiple Tool Hooks
You can also assign multiple tool hooks at once. They will be applied in the order they are assigned.
1agent = Agent(2 model=OpenAIResponses(id="gpt-5.2"),3 tools=[HackerNewsTools()],4 tool_hooks=[logger_hook, confirmation_hook], # The logger_hook will run on the outer layer, and the confirmation_hook will run on the inner layer5)You can also assign tool hooks to specific custom tools.
1@tool(tool_hooks=[logger_hook, confirmation_hook])2def get_top_hackernews_stories(num_stories: int) -> Iterator[str]:3 """Fetch top stories from Hacker News.45 Args:6 num_stories (int): Number of stories to retrieve7 """8 # Fetch top story IDs9 response = httpx.get("https://hacker-news.firebaseio.com/v0/topstories.json")10 story_ids = response.json()1112 # Yield story details13 final_stories = []14 for story_id in story_ids[:num_stories]:15 story_response = httpx.get(16 f"https://hacker-news.firebaseio.com/v0/item/{story_id}.json"17 )18 story = story_response.json()19 if "text" in story:20 story.pop("text", None)21 final_stories.append(story)2223 return json.dumps(final_stories)2425agent = Agent(26 model=OpenAIResponses(id="gpt-5.2"),27 tools=[get_top_hackernews_stories],28)Pre and Post Hooks
Pre and post hooks let's you modify what happens before and after a tool is called. It is an alternative to tool hooks.
Set the pre_hook in the @tool decorator to run a function before the tool call.
Set the post_hook in the @tool decorator to run a function after the tool call.
Here's a demo example of using a pre_hook, post_hook along with Agent Context.
1import json2from typing import Iterator34import httpx5from kern.agent import Agent6from kern.tools import FunctionCall, tool789def pre_hook(fc: FunctionCall):10 print(f"Pre-hook: {fc.function.name}")11 print(f"Arguments: {fc.arguments}")12 print(f"Result: {fc.result}")131415def post_hook(fc: FunctionCall):16 print(f"Post-hook: {fc.function.name}")17 print(f"Arguments: {fc.arguments}")18 print(f"Result: {fc.result}")192021@tool(pre_hook=pre_hook, post_hook=post_hook)22def get_top_hackernews_stories(agent: Agent) -> Iterator[str]:23 num_stories = agent.context.get("num_stories", 5) if agent.context else 52425 # Fetch top story IDs26 response = httpx.get("https://hacker-news.firebaseio.com/v0/topstories.json")27 story_ids = response.json()2829 # Yield story details30 for story_id in story_ids[:num_stories]:31 story_response = httpx.get(32 f"https://hacker-news.firebaseio.com/v0/item/{story_id}.json"33 )34 story = story_response.json()35 if "text" in story:36 story.pop("text", None)37 yield json.dumps(story)383940agent = Agent(41 dependencies={42 "num_stories": 2,43 },44 tools=[get_top_hackernews_stories],45 markdown=True,46)47agent.print_response("What are the top hackernews stories?", stream=True)