Tool Handlers (Default & Replace)
Applies to: 4.0.8.1+
This page explains how default handlers work and how to replace them.
1) Two critical handlers
Tool Loop has two main extension points:
plan_analysis_handler: decision stage (next_action+execution_commands)tool_execution_handler: execution stage (run commands and return records)
2) Default behavior
2.1 Default Plan Handler
Default logic:
- builds a standalone
ModelRequestfor planning - constrains output to
next_action + execution_commands[] - listens on
instant; short-circuits ifnext_action=response
Recommended decision shape:
python
{
"next_action": "execute" | "response",
"execution_commands": [
{
"purpose": str,
"tool_name": str,
"tool_kwargs": dict,
"todo_suggestion": str,
}
],
}2.2 Default Execution Handler
Default logic:
- runs tool calls with
tool.loop.concurrency - calls
async_call_tool(tool_name, tool_kwargs)per command - emits normalized records (
success/result/error)
3) Replace handlers at Agent level
python
from agently import Agently
agent = Agently.create_agent()
async def custom_plan_handler(
prompt,
settings,
tool_list,
done_plans,
last_round_records,
round_index,
max_rounds,
agent_name,
):
if round_index > 0:
return {"next_action": "response", "execution_commands": []}
return {
"next_action": "execute",
"execution_commands": [
{
"purpose": "calc",
"tool_name": "add",
"tool_kwargs": {"a": 1, "b": 2},
"todo_suggestion": "respond after this",
}
],
}
async def custom_execution_handler(
tool_commands,
settings,
async_call_tool,
done_plans,
round_index,
concurrency,
agent_name,
):
records = []
for command in tool_commands:
result = await async_call_tool(command["tool_name"], command.get("tool_kwargs", {}))
records.append(
{
"purpose": command.get("purpose", ""),
"tool_name": command.get("tool_name", ""),
"kwargs": command.get("tool_kwargs", {}),
"todo_suggestion": command.get("todo_suggestion", ""),
"success": True,
"result": result,
"error": "",
}
)
return records
agent.register_tool_plan_analysis_handler(custom_plan_handler)
agent.register_tool_execution_handler(custom_execution_handler)Reset to defaults:
python
agent.register_tool_plan_analysis_handler(None)
agent.register_tool_execution_handler(None)4) Replace at Core level (global)
python
from agently import Agently
Agently.tool.register_plan_analysis_handler(custom_plan_handler)
Agently.tool.register_tool_execution_handler(custom_execution_handler)5) Override per call
You can also pass handlers for one call:
Tool.async_plan_and_execute(..., plan_analysis_handler=..., tool_execution_handler=...)Tool.async_generate_tool_command(..., plan_analysis_handler=...)
6) Design recommendations
- keep plan handler decision-only
- keep execution handler execution-only
- always return auditable records (
success/result/error) - prefer
execution_commandsas canonical field