Python: Integrate tool approval into the harness (#6522)

* Integrate auto tool-approval feature into harness

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Rename disable_tool_approval to disable_tool_auto_approval

Addresses PR review feedback that the parameter name was unclear. The flag
toggles the auto/standing tool-approval middleware.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
westey
2026-06-16 16:21:31 +01:00
committed by GitHub
parent 571cae426c
commit 0db9305625
5 changed files with 154 additions and 9 deletions
@@ -18,6 +18,7 @@ from a chat client.
| MemoryContextProvider | File-based durable memory (when `memory_store` provided) |
| SkillsProvider | File-based skill discovery and progressive loading |
| Shell tool | Shell command execution + environment probing (when `shell_executor` provided) |
| Tool approval | "Don't ask again" standing rules + heuristic auto-approval (enabled by default) |
| OpenTelemetry | Built-in observability |
Each feature can be disabled or customized via keyword arguments.
@@ -182,6 +182,8 @@ class FallbackToolFormatter(ToolCallFormatter):
args_dict = json.loads(call.arguments)
except (json.JSONDecodeError, TypeError):
return None
if not isinstance(args_dict, dict):
return None
elif isinstance(call.arguments, dict):
args_dict = call.arguments
else:
@@ -84,23 +84,38 @@ class ToolApprovalObserver(ConsoleObserver):
tool_name = self._format_tool_name(request)
prompt = f"🔐 Tool approval: {tool_name}"
# TODO(westey-m): Add "Always approve" options when the framework supports
# CreateAlwaysApproveToolResponse / CreateAlwaysApproveToolWithArgumentsResponse.
choices = [
"Approve this call",
"Deny",
]
approve_once = "Approve this call"
always_tool = "Always approve this tool (any arguments)"
always_tool_args = "Always approve this tool with these arguments"
deny = "Deny"
choices = [approve_once, always_tool, always_tool_args, deny]
async def continuation(
selection: str,
ux: IUXStateDriver,
) -> Message | None:
from agent_framework import Message
from agent_framework import (
Message,
create_always_approve_tool_response,
create_always_approve_tool_with_arguments_response,
)
if selection == "Deny":
if selection == deny:
response_content = request.to_function_approval_response(approved=False)
action_label = "❌ Denied"
color = "red"
elif selection == always_tool:
response_content = create_always_approve_tool_response(
request, reason="User chose to always approve this tool"
)
action_label = "✅ Always approved (any args)"
color = "green"
elif selection == always_tool_args:
response_content = create_always_approve_tool_with_arguments_response(
request, reason="User chose to always approve this tool with these arguments"
)
action_label = "✅ Always approved (these args)"
color = "green"
else:
response_content = request.to_function_approval_response(approved=True)
action_label = "✅ Approved"