diff --git a/contributing/samples/agent_governance/README.md b/contributing/samples/agent_governance/README.md new file mode 100644 index 00000000..e4f81d0d --- /dev/null +++ b/contributing/samples/agent_governance/README.md @@ -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) \ No newline at end of file diff --git a/contributing/samples/agent_governance/governance_plugin.py b/contributing/samples/agent_governance/governance_plugin.py new file mode 100644 index 00000000..7bb9afa8 --- /dev/null +++ b/contributing/samples/agent_governance/governance_plugin.py @@ -0,0 +1,125 @@ +# Copyright 2026 Microsoft Corporation +# +# 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", + 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() + 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" + ) + + 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: + 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"} \ No newline at end of file diff --git a/contributing/samples/agent_governance/main.py b/contributing/samples/agent_governance/main.py new file mode 100644 index 00000000..60a4f206 --- /dev/null +++ b/contributing/samples/agent_governance/main.py @@ -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.") \ No newline at end of file diff --git a/contributing/samples/agent_governance/policies/default.yaml b/contributing/samples/agent_governance/policies/default.yaml new file mode 100644 index 00000000..7b632fdb --- /dev/null +++ b/contributing/samples/agent_governance/policies/default.yaml @@ -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 \ No newline at end of file