Google ADK Plugin
The watchlight-adk plugin brings Watchlight AI Agent Runtime Governance to any Google Agent Development Kit (ADK) application. Install it, register it on your Runner, and every tool call is policy-evaluated, every action is logged to the execution graph.
Quick Start
pip install watchlight-adk google-adk
from google.adk.agents import Agent
from google.adk.runners import Runner
from watchlight_adk import WatchlightPlugin
# Create your agent
agent = Agent(
name="my-agent",
model="gemini-2.5-flash",
tools=[get_data, run_analysis],
)
# Add Watchlight governance
plugin = WatchlightPlugin(apdp_url="http://localhost:8081")
runner = Runner(agent=agent, app_name="my-app", plugins=[plugin])
That's it. Every tool call is now policy-evaluated, every agent invocation is trust-checked, and every action is logged to the execution graph.
Configuration
WatchlightPlugin(
# Required: WL-APDP endpoint
apdp_url="http://localhost:8081",
# Action verb mapping (tool name → governance action)
action_map={
"get_market_data": "read",
"execute_trade": "execute",
"search_docs": "search",
},
# Optional: WL-Registry for tool discovery
registry_url="http://localhost:8080",
registry_api_key="agent-api-key",
# Behavior
fail_closed=True, # Deny if APDP unreachable (default: True)
log_decisions=True, # Log Allow/Deny to stdout (default: True)
tenant_id="acme-corp", # Multi-tenant scope
)
Action Verb Model
The plugin classifies every tool call with a governance action verb. This determines which policies apply and whether the action aligns with the agent's declared intent.
Convention-Based Defaults
When a tool is not in the explicit action_map, the plugin resolves the action from the tool name:
| Tool Name Prefix | Action | Rationale |
|---|---|---|
get_*, fetch_*, load_* | read | Data retrieval, no side effects |
list_*, show_* | list | Collection enumeration |
search_*, find_*, query_* | search | Filtered retrieval |
create_*, add_*, new_* | create | Resource creation |
update_*, set_*, modify_* | update | Resource mutation |
delete_*, remove_*, drop_* | delete | Resource destruction |
execute_*, run_*, trigger_* | execute | Side-effecting execution |
analyze_* | analyze | Data analysis |
summarize_* | summarize | Summarization |
generate_* | generate | Content generation |
approve_*, confirm_* | approve | Approval workflow |
review_* | review | Review workflow |
embed_* | embed | Vector embedding |
| (no match) | execute | Conservative default |
Override Precedence
1. Explicit action_map entry (highest priority)
2. Convention-based prefix matching
3. Default: "execute" (most restrictive)
The default execute is intentionally conservative — most intent categories (Research, DataAnalysis, SecurityCompliance) exclude it, so an unmapped tool won't accidentally get broader access.
LLM Access
LLM calls use Action::"chat" with Model:: resource type. This aligns with the authorization model where chat is a distinct action that can be independently governed.
Policy Examples
Resource-Group Policies (Recommended)
// All agents can read data tools
permit(principal, action == Action::"read", resource)
when {
(principal == Agent::"research-analyst" ||
principal == Agent::"portfolio-manager" ||
principal == Agent::"compliance-officer") &&
(resource == Tool::"get_market_data" ||
resource == Tool::"get_portfolio" ||
resource == Tool::"get_audit_log")
};
// Only portfolio-manager can execute trades
permit(
principal == Agent::"portfolio-manager",
action == Action::"execute",
resource == Tool::"execute_trade"
);
// All agents can chat with the LLM
permit(principal, action == Action::"chat", resource == Model::"gemini-chat")
when {
principal == Agent::"research-analyst" ||
principal == Agent::"portfolio-manager" ||
principal == Agent::"compliance-officer"
};
// Defense-in-depth: deny untrusted agents
forbid(principal, action, resource)
when {
principal has trust_state && principal.trust_state != "trusted"
};
What Happens on Denial
When a tool call is denied by policy:
- The plugin returns a successful response (not an error) to the agent
- The message clearly states the denial is a permanent policy restriction
- The agent is instructed not to retry and to inform the user
- The denial is logged in the execution graph with the determining policy name
Agent response: "This action was denied by Watchlight AI Agent Runtime
Governance. The research-analyst role is not authorized to execute
execute_trade."
How Events Flow
Plugin WL-APDP Execution Graph
│ │ │
├─ POST /authorize ────────────▶│ │
│ ├─ PolicyDecisionMade ────────▶│
│ │ (agent_id, action, │
│◀─ {decision, policy_name} ────┤ resource, decision) │
│ │ │
├─ POST /v1/events ────────────▶│ │
│ (execution_started, ├─ publish to NATS ───────────▶│
│ tool_call_observed, │ │
│ execution_completed) │ │
- Policy decisions are emitted by APDP when it evaluates
/authorize - Lifecycle events are batched by the plugin and flushed to APDP's
/v1/eventsendpoint at execution end - APDP publishes all events to NATS → execution graph materializes them
The plugin never connects to NATS directly. APDP is the single gateway.
Tool Registration
When registry_url is configured, the plugin automatically registers its tools in WL-Registry on agent startup:
plugin = WatchlightPlugin(
apdp_url="http://localhost:8081",
registry_url="http://localhost:8080",
registry_api_key="agent-key",
action_map={
"get_market_data": "read",
"execute_trade": "execute",
},
)
Each tool is registered with:
- Name — function name
- Action verb — governance classification (read, execute, etc.)
- Description — from function docstring
- Framework —
google_adk
Tools are visible in the WL-Registry dashboard and API.
ADK Callback Hooks
The plugin implements these ADK BasePlugin callbacks:
| Callback | Action |
|---|---|
before_agent_callback | Trust check, emit execution_started, register tools |
before_tool_callback | Policy evaluation (Allow/Deny) |
after_tool_callback | Emit tool_call_observed |
before_model_callback | LLM access policy check |
after_agent_callback | Emit execution_completed, flush events |
Agent Naming
ADK requires underscores in agent names (research_analyst). The plugin automatically converts to hyphens for the Cedar principal (research-analyst). Policies should use the hyphenated form:
permit(principal == Agent::"research-analyst", ...)
Fail-Closed Behavior
By default, the plugin denies all actions when APDP is unreachable:
# Default: fail closed (recommended for production)
plugin = WatchlightPlugin(apdp_url="...", fail_closed=True)
# Fail open (for development only)
plugin = WatchlightPlugin(apdp_url="...", fail_closed=False)
When fail_closed=False and APDP is unreachable, actions are allowed with a warning logged.