General
Google ADK Callbacks Are a Policy Plane, Not Just Hooks
Raju Dandigam DEV Community 周榜
1 views
An agent proposes refund_order. The tool exists, its arguments are valid JSON, and the model sounds confident. None of that answers the question that matters: is this action allowed now?
Google's Agent Development Kit gives us interception points before and after agents, models, and tools. The tempting implementation is to add a log statement to each callback. The more useful design is to treat those callbacks as a small policy plane.
That does not mean placing every business rule in a callback. It means using the boundary to make four decisions explicit:
may this operation proceed?
should its inputs be normalized?
which budget or authorization applies?
what evidence should be recorded?
A callback can change control flow
ADK callbacks are not passive listeners. A beforeModelCallback can return a response and skip the model call. A beforeToolCallback can return a tool result and skip the tool. After-callbacks can replace results. That makes callback return values part of application behavior, not merely observability. The ADK callback documentation explains these interception semantics for TypeScript and the other supported SDKs.
Callback order is part of that behavior. ADK can run callback lists and plugin callbacks in sequence, with earlier callbacks able to modify inputs seen by later ones or short-circuit the remaining chain. Treat the registered order as reviewed configuration: authentication and tenant resolution should not accidentally run after a callback that can approve or execute an operation.
Start with a small decision vocabulary:
type PolicyDecision = {
outcome: "allow" | "block";
reasonCode: string;
policyVersion: string;
};
function authorizeTool(
toolName: string,
args: Record<string, unknown>,
): PolicyDecision {
if (toolName === "refund_order" && args.approved !== true) {
return {
outcome: "block",
reasonCode: "APPROVAL_REQUIRED",
policyVersion: "refund-policy-3",
};
}
return {
outcome: "allow",
reasonCode: "POLICY_SATISFIED",
policyVersion: "refund-policy-3",
};
}
This decision is deterministic and reviewable. It does not ask the model whether its own proposed action is safe.
Keep the callback thin
The callback should delegate to policy code rather than growing into an untestable collection of if statements. The following is deliberately schematic because exact tool callback types can change between ADK releases:
async function beforeTool({ tool, args }: {
tool: { name: string };
args: Record<string, unknown>;
}) {
const decision = authorizeTool(tool.name, args);
await policyEvidence.write({
tool: tool.name,
outcome: decision.outcome,
reasonCode: decision.reasonCode,
policyVersion: decision.policyVersion,
});
if (decision.outcome === "block") {
return {
error: "Tool execution blocked by policy",
reasonCode: decision.reasonCode,
};
}
return undefined; // continue with normal tool execution
}
Register that function through the agent's beforeToolCallback. Use beforeModelCallback for model-input limits or serving an approved cache entry, and afterToolCallback for result normalization and outcome evidence.
The boundary becomes easier to review when responsibilities stay distinct:
Boundary
Suitable responsibility
Before model
Input limits, budget checks, approved cache lookup
Before tool
Authorization, argument validation, freshness checks
After tool
Result normalization, outcome classification
After agent
Final evidence and response-level validation
Test absence, not only the error message
A guardrail test is incomplete if it checks only the returned text. The callback might return “blocked” after the real tool already ran.
it("does not execute a refund without approval", async () => {
const refund = vi.fn();
const decision = authorizeTool("refund_order", {
orderId: "synthetic-123",
approved: false,
});
if (decision.outcome === "allow") await refund();
expect(decision.reasonCode).toBe("APPROVAL_REQUIRED");
expect(refund).not.toHaveBeenCalled();
});
At integration level, preserve evidence that the policy callback ran and the protected tool did not. That is stronger than matching a model-generated refusal sentence.
Also test the installed callback chain, not only the pure policy function. A unit test can prove authorizeTool() works while one runner quietly omits the plugin. A useful integration assertion records the callback name, policy version, decision, proposal ID, and whether execution began. For a blocked proposal, tool.execution.started must be absent.
Callbacks are not the entire security architecture
A callback runs inside the application process. It can contain a bug, be omitted from another agent, or be registered in the wrong order. High-risk authorization should also be enforced at the tool or service boundary. The application still needs least-privilege credentials, idempotency, audit records, and tests.
Google explicitly recommends ADK Plugins for modular security guardrails instead of scattering individual callbacks across agents. That is the right graduation path when the same policy must apply consistently to several agents.
Plugins still run inside your application. They improve reuse and consistency; they do not replace authorization at the resource server. Think of the callback or plugin as the orchestration decision point and the downstream service as the final enforcement point.
Make policy visible
Callbacks are most valuable when they expose a control point that already exists conceptually. Give each decision a stable reason code and policy version. Record bounded facts, not raw prompts or hidden reasoning. Test both the allowed path and the absence of a forbidden side effect.
Used that way, an ADK callback is more than a hook. It is where an agent proposal becomes an application decision.
References
Google ADK callbacks
Google ADK safety and security
Read original: https://dev.to/raju_dandigam/google-adk-callbacks-are-a-policy-plane-not-just-hooks-551j
← Previous
How I Modelled My Power BI Data — Data Modelling, Relationships & Joins (Kenya Crops Dataset)
Next →
Separating Prompt Discovery from AI Generation in Next.js
Related
How I Modelled My Power BI Data — Data Modelling, Relationships & Joins (Kenya Crops Dataset)
General
1
DEV Community 周榜
Don't Panic! Decoding Your First Python SyntaxError Like a Pro
General
0
DEV Community 周榜
"Day three: the loop leaves a trace"
General
0
DEV Community 周榜
Building a Local AI Chatbot: What I Learned Running LLMs on My Own Laptop (No GPU, No Cloud API)
General
0
DEV Community 周榜
Comments0
No comments yet — be the first