-
Notifications
You must be signed in to change notification settings - Fork 53
feat: add Agent Governance Toolkit sample — policy enforcement for ADK agents #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9a2d08b
108df9c
ff5d84d
3876da4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| # Agent Governance Toolkit — GovernancePlugin for Google ADK | ||
|
|
||
| A governance plugin for [Google ADK](https://github.com/google/adk-python) that enforces | ||
| policy-as-code rules before tool execution, verifies agent identity, and produces | ||
| tamper-evident audit trails. | ||
|
|
||
| Built on the [Agent Governance Toolkit](https://github.com/microsoft/agent-governance-toolkit) | ||
| (v3.2.0, 9,500+ tests, MIT licensed). | ||
|
|
||
| ## Features | ||
|
|
||
| - **Policy enforcement** — Evaluate YAML/OPA/Cedar policies before every tool call (<5ms) | ||
| - **Agent identity** — Zero-trust verification via Ed25519 + SPIFFE | ||
| - **Audit logging** — Merkle-chained tamper-evident action logs | ||
| - **Configurable** — Allow/deny/warn/require-approval actions per policy rules | ||
|
|
||
| ## Install | ||
|
|
||
| ```bash | ||
| pip install agentmesh-platform[server] | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| ```python | ||
| from google.adk.agents import Agent | ||
| from governance_plugin import GovernancePlugin | ||
|
|
||
| governance = GovernancePlugin( | ||
| policy_dir="./policies", | ||
| agent_did="did:mesh:my-agent", | ||
| ) | ||
|
|
||
| agent = Agent( | ||
| name="governed-agent", | ||
| model="gemini-2.0-flash", | ||
| tools=[my_tool], | ||
| plugins=[governance], | ||
| ) | ||
| ``` | ||
|
|
||
| ## Policy Example (policies/default.yaml) | ||
|
|
||
| ```yaml | ||
| apiVersion: governance.toolkit/v1 | ||
| name: adk-agent-policy | ||
| rules: | ||
| - name: block-dangerous-tools | ||
| condition: "action in ['shell_exec', 'file_delete']" | ||
| action: deny | ||
| - name: rate-limit-api-calls | ||
| condition: "action == 'api_call'" | ||
| action: allow | ||
| limit: "100/hour" | ||
| ``` | ||
|
|
||
| ## How It Works | ||
|
|
||
| The plugin hooks into ADK's `before_tool_call` lifecycle: | ||
|
|
||
| 1. **Before tool execution** — GovernancePlugin evaluates the tool name + arguments against loaded policies | ||
| 2. **Identity check** — Optionally verifies the calling agent's DID | ||
| 3. **Decision** — Allow, deny, warn, or require approval | ||
| 4. **Audit** — Logs the decision with Merkle hash chaining | ||
|
|
||
| ## Links | ||
|
|
||
| - [Agent Governance Toolkit](https://github.com/microsoft/agent-governance-toolkit) | ||
| - [Policy-as-Code Tutorial](https://github.com/microsoft/agent-governance-toolkit/tree/main/docs/tutorials/policy-as-code) | ||
| - [OWASP Agentic Compliance](https://github.com/microsoft/agent-governance-toolkit/blob/main/docs/OWASP-COMPLIANCE.md) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| # Copyright 2026 Microsoft Corporation | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why Microsoft? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with @DeanChensj. This sample is being contributed to a Google-owned repository, so it should ideally follow the project's licensing (Apache 2.0) and copyright standards. Also, the |
||
| # | ||
| # Licensed under the MIT License. | ||
| """ | ||
| GovernancePlugin for Google ADK. | ||
|
|
||
| Enforces policy-as-code rules before tool execution, verifies agent | ||
| identity, and produces tamper-evident audit trails using the Agent | ||
| Governance Toolkit (https://github.com/microsoft/agent-governance-toolkit). | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class GovernancePlugin: | ||
| """ADK plugin that enforces governance policies before tool execution. | ||
|
|
||
| Usage:: | ||
|
|
||
| from governance_plugin import GovernancePlugin | ||
|
|
||
| governance = GovernancePlugin(policy_dir="./policies") | ||
| agent = Agent(name="my-agent", plugins=[governance]) | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| policy_dir: str | Path = "./policies", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a relative path like |
||
| agent_did: str = "did:mesh:adk-agent", | ||
| sponsor_email: str = "adk-operator@example.com", | ||
| default_action: str = "allow", | ||
| ) -> None: | ||
| self._policy_dir = Path(policy_dir) | ||
| self._agent_did = agent_did | ||
| self._sponsor_email = sponsor_email | ||
| self._default_action = default_action | ||
| self._engine = None | ||
| self._audit = None | ||
| self._setup() | ||
|
|
||
| def _setup(self) -> None: | ||
| """Initialize AGT policy engine and audit service.""" | ||
| try: | ||
| from agentmesh.governance.policy import PolicyEngine | ||
| from agentmesh.services.audit import AuditService | ||
|
|
||
| self._engine = PolicyEngine() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This "fail-open" logic (returning |
||
| self._audit = AuditService() | ||
|
|
||
| if self._policy_dir.exists(): | ||
| for f in sorted(self._policy_dir.glob("*.yaml")): | ||
| try: | ||
| self._engine.load_yaml(f.read_text()) | ||
| logger.info("Loaded policy: %s", f.name) | ||
| except Exception as exc: | ||
| logger.warning("Skipped %s: %s", f.name, exc) | ||
|
|
||
| logger.info( | ||
| "GovernancePlugin initialized (agent=%s, policies=%s)", | ||
| self._agent_did, | ||
| self._policy_dir, | ||
| ) | ||
| except ImportError: | ||
| logger.warning( | ||
| "agentmesh-platform not installed. " | ||
| "Install with: pip install agentmesh-platform" | ||
| ) | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This install command should probably match the one in the README ( |
||
| def before_tool_call( | ||
| self, | ||
| tool_name: str, | ||
| args: dict[str, Any] | None = None, | ||
| **kwargs: Any, | ||
| ) -> dict[str, Any]: | ||
| """Evaluate governance policy before a tool call. | ||
|
|
||
| Returns: | ||
| Dict with 'allowed' (bool), 'decision', 'reason', and | ||
| 'audit_entry_id'. | ||
| """ | ||
| if self._engine is None: | ||
| return {"allowed": True, "decision": "allow", "reason": "AGT not installed"} | ||
|
|
||
| context = { | ||
| "action": tool_name, | ||
| "tool_args": args or {}, | ||
| **kwargs, | ||
| } | ||
|
|
||
| result = self._engine.evaluate( | ||
| agent_did=self._agent_did, | ||
| context=context, | ||
| ) | ||
|
|
||
| if self._audit: | ||
| entry = self._audit.log_policy_decision( | ||
| agent_did=self._agent_did, | ||
| action=tool_name, | ||
| decision=result.action, | ||
| policy_name=result.policy_name or "", | ||
| data={"tool_args": args or {}, "reason": result.reason}, | ||
| ) | ||
| audit_id = entry.entry_id | ||
| else: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's important to verify if |
||
| audit_id = None | ||
|
|
||
| return { | ||
| "allowed": result.allowed, | ||
| "decision": result.action, | ||
| "reason": result.reason, | ||
| "matched_rule": result.matched_rule, | ||
| "audit_entry_id": audit_id, | ||
| } | ||
|
|
||
| def get_audit_summary(self) -> dict[str, Any]: | ||
| """Return audit service summary.""" | ||
| if self._audit: | ||
| return self._audit.summary() | ||
| return {"error": "Audit service not initialized"} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # Copyright 2026 Microsoft Corporation | ||
| # | ||
| # Licensed under the MIT License. | ||
| """ | ||
| Example: Google ADK agent with Agent Governance Toolkit policy enforcement. | ||
|
|
||
| Demonstrates: | ||
| 1. Loading YAML governance policies | ||
| 2. Evaluating policies before tool calls | ||
| 3. Producing tamper-evident audit trails | ||
| """ | ||
|
|
||
| from pathlib import Path | ||
|
|
||
| from google.adk.agents import Agent | ||
| from governance_plugin import GovernancePlugin | ||
|
|
||
|
|
||
| def create_governed_agent() -> Agent: | ||
| """Create an ADK agent with governance controls.""" | ||
| governance = GovernancePlugin( | ||
| policy_dir=Path(__file__).parent / "policies", | ||
| agent_did="did:mesh:adk-demo-agent", | ||
| ) | ||
|
|
||
| # Example: check policy before a tool call | ||
| result = governance.before_tool_call( | ||
| tool_name="web_search", | ||
| args={"query": "latest AI safety research"}, | ||
| ) | ||
| print(f"Policy decision: {result['decision']} — {result['reason']}") | ||
|
|
||
| # Check audit trail | ||
| summary = governance.get_audit_summary() | ||
| print(f"Audit: {summary['total_entries']} entries, chain valid: {summary['chain_valid']}") | ||
|
|
||
| return Agent( | ||
| name="governed-research-agent", | ||
| model="gemini-2.0-flash", | ||
| instruction="You are a research assistant with governance controls.", | ||
| ) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| agent = create_governed_agent() | ||
| print(f"Agent '{agent.name}' created with governance enabled.") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| apiVersion: governance.toolkit/v1 | ||
| name: adk-demo-policy | ||
| description: Example governance policy for Google ADK agents | ||
| rules: | ||
| - name: block-shell-execution | ||
| condition: "action in ['shell_exec', 'code_exec', 'file_delete']" | ||
| action: deny | ||
| description: Block dangerous system-level tool calls | ||
| priority: 100 | ||
|
|
||
| - name: rate-limit-api-calls | ||
| condition: "action == 'api_call'" | ||
| action: allow | ||
| limit: "100/hour" | ||
| description: Rate limit external API calls | ||
| priority: 50 | ||
|
|
||
| - name: require-approval-for-payments | ||
| condition: "action == 'process_payment'" | ||
| action: require_approval | ||
| approvers: ["admin@example.com"] | ||
| description: Payment actions require human approval | ||
| priority: 90 | ||
|
|
||
| default_action: allow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can have a plugins/ folder under src/google/adk_community to host the plugin