Repository navigation
Description
** Please make sure you read the contribution guide and file the issues in the right place. **
Contribution guide.
🔴 Required Information
Is your feature request related to a specific problem?
ADK exposes rich lifecycle seams through BasePlugin, but there is no
standard, framework-neutral way to plug governance and policy enforcement
(policy engines, content filters, PII redaction, egress guards, rate
limiters) into those seams. Today each team hand-writes bespoke callback
logic per agent. That is inconsistent, easy to accidentally "fail open,"
hard to audit, and not portable across agent frameworks. There is no
uniform allow / deny / transform decision contract, no fail-closed
guarantee, and no built-in, auditable record of why an action was
allowed, blocked, or rewritten.
Describe the Solution You'd Like
Add AgentHooksPlugin: a BasePlugin that makes ADK a conformant host
for agent-hooks, a
framework-neutral control contract. You register one or more interceptors
once, and every governed ADK lifecycle point delegates its decision to the
agent-hooks emitter, which runs the interceptors, returns a Verdict
(allow / deny / transform), and records each decision as an auditable
InterceptionRecord.
API changes (all additive):
- New public export
google.adk.plugins.AgentHooksPlugin. - New optional extra:
pip install "google-adk[agent-hooks]"(backed by
agent-hooks-sdk, a compiled-core optional dependency). The import is
deferred to plugin construction, so importinggoogle.adk.pluginsnever
requires it. - Interceptor contract: any object with
intercept(AgentContext) -> Verdict
(sync or async). No ADK-specific base class required — the same
interceptor runs on other agent-hooks hosts (crewAI, ...).
Enforcement is fail-closed: deny blocks the guarded action,
transform rewrites the guarded value at $target, and any engine error,
malformed verdict, or interceptor timeout becomes a fail-closed deny — it
never fails open. mode="evaluate_only" records verdicts without acting.
Impact on your work
This gives us one consistent, auditable governance layer across ADK agents
instead of per-agent callback code, and lets teams
enforce the same interceptor across ADK and other frameworks. It is
foundational for governed/regulated deployments where every tool call,
model call, input, and output must be policy-checked and logged.
Willingness to contribute
Yes.
🟡 Recommended Information
Describe Alternatives You've Considered
- Framework-specific middleware. Locks policy to one framework;
agent-hooks is framework-neutral, so the same interceptor is portable. - Hand-written
BasePlugincallbacks per project. Works, but is
reimplemented everywhere, has no shared verdict model or audit trail, and
is easy to accidentally fail open. - Provider/model-side guardrails only. They don't uniformly cover tool
calls, tool results, user input, final output, and run lifecycle.
Proposed API / Implementation
from google.adk.apps.app import App
from google.adk.plugins import AgentHooksPlugin
from agent_hooks import AgentContext, Decision, Verdict
class ToolGovernance:
name = "tool_governance"
def intercept(self, ctx: AgentContext) -> Verdict:
if ctx["interception_point"] == "pre_tool_call":
if ctx["tool_call"]["name"] == "delete_account":
return Verdict.deny(reason="tool_denied",
message="disabled by policy")
return Verdict(decision=Decision.ALLOW)
records = []
plugin = AgentHooksPlugin(
interceptors=[ToolGovernance()],
mode="enforce", # or "evaluate_only" to observe without acting
record_sink=records.append, # one auditable InterceptionRecord per decision
)
app = App(name="demo", root_agent=root_agent, plugins=[plugin])Constructor (defaults shown): AgentHooksPlugin(interceptors, name="agent_hooks", mode="enforce", timeout=5.0, composition=None, identity_provider="jcs-sha256", record_sink=None, max_records=1000).
Additional Context
Interception-point mapping (ADK callback → agent-hooks point):
| ADK plugin callback | agent-hooks point |
|---|---|
before_run_callback |
agent_startup |
on_user_message_callback |
input |
before_model_callback |
pre_model_call |
after_model_callback |
post_model_call |
before_tool_callback |
pre_tool_call |
after_tool_callback |
post_tool_call |
on_event_callback (final) |
output |
after_run_callback |
agent_shutdown |
Notes:
pre_model_callsupportsallow/denyonly; atransformthere is
treated as a fail-closed deny, because rebuilding a provider-native
request from wire messages is not round-trip safe.- Trust model: agent-hooks is a cooperative control contract, not
a security boundary. Interceptors run in-process with full data access and
the interception points do not guarantee complete mediation.
Activity
sanketpatil06 commentedon Aug 6, 2026
Hi @prayagupa , Thanks for bringing this and taking time to raise a PR. Your request is currently being reviewed, and we will let you know if anything further is needed. We appreciate your contribution.
GWeale commentedon Sep 22, 2026
not taking this into adk core, same call as #6605, closed on 13 august. BasePlugin already has every hook in your mapping table, and a plugin outside adk can deny, rewrite and fail closed on them, so AgentHooksPlugin can ship as its own package and be listed through a pull request to adk-docs. if it needs a generic hook adk lacks, like the cancellation callback #6605 added, open a new issue for that hook.