One callback on the agent, one judgment before every tool
The ADK runs before_tool_callback before every tool an agent calls. Post the proposed call to Veridect there: return None on a greenlight and the tool runs as written; return a dict otherwise and the ADK skips the tool and hands that dict — verdict and record id — back to the model as the tool’s result, so the agent tells the user the action is waiting for a person instead of pretending it happened. Attach it to one agent, or as a plugin to every agent in the runner.
Fail-closed by construction: a network error, a timeout or a malformed response is a dict, never None, so the tool does not run. The Veridect key comes from Secret Manager, never from the agent’s instructions.
# the judgment before every tool call — one callback, attached to the agent (or a Plugin for the whole runner) import os, requests from typing import Any, Dict, Optional from google.adk.agents import LlmAgent from google.adk.tools.base_tool import BaseTool from google.adk.tools.tool_context import ToolContext AGENT_SCOPE = "may read orders and may approve replacement shipments for items reported damaged" # its configured authority, in words def veridect_before_tool(tool: BaseTool, args: Dict[str, Any], tool_context: ToolContext) -> Optional[Dict]: verdict, record_id = "unavailable", "" # closed until proven open try: r = requests.post("https://<your-veridect-host>/api/ai/consensus/pre-action", timeout=60, headers={"Authorization": "Bearer " + os.environ["VERIDECT_API_KEY"]}, # from Secret Manager json={"action": tool.name, "target": "adk:" + tool.name, "parameters": args, "callingAgent": tool_context.agent_name, "callingAgentScope": AGENT_SCOPE, "context": "session " + tool_context.session.id, "department": "customer_operations"}) d = r.json() if r.status_code == 200 else {} verdict, record_id = d.get("verdict", "unavailable"), d.get("auditBundleId", "") except Exception: pass # network, timeout, parse: all stay closed tool_context.state["veridect_record"] = record_id # the record id rides with the session if verdict == "greenlight": return None # the tool runs, as written return {"executed": False, "verdict": verdict, "record_id": record_id} # tool skipped; this becomes its result agent = LlmAgent(name="order_support_agent", model="gemini-2.5-pro", tools=[lookup_order, create_replacement_shipment], before_tool_callback=veridect_before_tool)