adk-samples / safety-plugins
AGENTS.md11k starsChanged 44 days agoApache-2.0View on GitHub
A clone-and-study recipe for global safety guardrails implemented as ADK BasePlugins attached to the Runner. Two interchangeable plugins are provided — an LLM-as-a-judge and a Model Armor filter — that hook the same lifecycle callbacks to classify and block unsafe content. The interesting part is not the agents (deliberately mundane sum/fibonacci demos); it's the plugins: because they're wired at the Runner they wrap every agent and sub-agent under it, and they defend against session poisoning by never persisting harmful content…
- Reads credentials
1 other project uses a version of this.
Trending
More talk lately.about the whole repository
Most recently taken up by
- google/adk-recipesword for word, first seen Oct 13, 2026
Who's talking about google/adk-samples
- @stephanjanssen.be“Google just open sourced a Python Agent Development Kit @ github.com/google/adk-p... Native Streaming and …”Bluesky · Apr 9, 2025 · 12 likes
- google/adk-python“stream_query and HTTP Streaming to Vertex AI Agent Return Empty/Exhausted Responses (Works via ADK Web)”GitHub · Jul 8, 2025 · 5 reactions
- google/adk-recipes“Support running agents fully locally without Vertex AI (offline mode)”GitHub · Apr 14, 2025 · 4 reactions
- google/adk-recipes“[BUG]: Deployment of Data Science agent to Agent Engine fails ”GitHub · Oct 1, 2025 · 3 reactions
- A Hacker News story“CaMeL-Powered Secure Agent Demo with ADK”Hacker News · Aug 9, 2025 · 3 points
The biggest 5 of 32 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 32
Talked about on GitHub, Bluesky, Hacker News and Stack Overflow (32 times).
On GitHub · 23
- Jul 8, 2025stream_query and HTTP Streaming to Vertex AI Agent Return Empty/Exhausted Responses (Works via ADK Web) — google/adk-python5 reactions
- Apr 14, 2025Support running agents fully locally without Vertex AI (offline mode) — google/adk-recipes4 reactions · 7 comments
- Oct 1, 2025[BUG]: Deployment of Data Science agent to Agent Engine fails — google/adk-recipes3 reactions · 1 comment
- Oct 28, 2026docs: fix 5 broken internal anchor links and 1 dead external link — google-gemini/gemini-cli1 reaction · 4 comments
- Oct 20, 2026Update adk-samples references and python/agents discovery paths for google/adk-recipes restructure — google/agents-cli1 reaction · 2 comments
18 more on GitHub
- Oct 12, 2026list: scan the template dir we cloned, not the clone root — GoogleCloudPlatform/agent-starter-pack1 reaction
- Feb 12, 2026interrupted sometimes didn't work — google/adk-python1 reaction · 10 comments
- Jan 8, 2026Implement 5 ADK agent projects that can be easily run by newbies — addyosmani/adk-product-engineers1 reaction
- Nov 20, 2025[BUG]: deep-search/gemini-fullstack: Wrong prompt to access session state — google/adk-recipes1 reaction
- Jun 21, 2025unable to import StdioServerParameters — google/adk-recipes1 reaction · 2 comments
- Jun 17, 2025VertexAiRagRetrieval needs llama-index package — google/adk-recipes1 reaction · 3 comments
- Jun 5, 2025Bug: display_name in file uploads causes error with Google.GenAI backend (non-Vertex) — google/adk-python1 reaction · 6 comments
- May 18, 2025Missing Image — google/adk-recipes1 reaction
- Apr 11, 2025Duplicate key name — google/adk-recipes1 reaction · 1 comment
- Oct 18, 2026Fix(audit,#17387): Day4 Track2 tranche 1 -- Lab8/Lab9 reformulations honnetes, cures accents — jsboige/CoursIA0 reactions · 14 comments
- Sep 17, 2026Rename sample references to recipe across repository — google/adk-recipes0 reactions
- Sep 12, 2026docs(deep-search): fix cloud deployment instructions and scaffold path (#2575) — google/adk-recipes0 reactions · 4 comments
- Sep 12, 2026docs: fix 404 link to relocated Deep Search sample — google/adk-docs0 reactions · 1 comment
- Sep 9, 2026Add repo skills instructions and documentation links to AI issue response workflow — google/adk-recipes0 reactions
- Jul 24, 2026Fix broken adk-samples links from deleted sample agents and notebooks — google/adk-docs0 reactions · 1 comment
- Jun 12, 2026ci: run the spam-detection bot via the adk-samples reusable workflow — google/adk-go0 reactions · 1 comment
- May 27, 2026Add Deep Search Agent project to portfolio showcase — asafsilner/silner-showcase-prime0 reactions
- Feb 12, 2026fix: path typo in story teller example — google/adk-recipes0 reactions · 3 comments
On Bluesky · 4
- Apr 9, 2025Google just open sourced a Python Agent Development Kit @ github.com/google/adk-p... Native Streaming and #MCP support out of the box 👀… — @stephanjanssen.be12 likes and reposts
- Jul 12, 2026Exploring the ADK Samples repo from Google—it’s like a guidebook for building intelligent agents. Seeing multi-language support reflects… — @iam.slys.dev1 likes and repost
- Dec 9, 2025📦 google / adk-samples ⭐ 6,900 (+67) 🗒 Python A collection of sample agents built with Agent Development Kit (ADK) — @github-trending.bsky.social1 likes and repost
- May 20, 2025Explore practical use-cases & build! 🧪 Hands-on Patent Search Codelab: codelabs.developers.google.com/patent-searc... 📚 Check out sample… — @kweinmeister.bsky.social0 likes and reposts · 1 reply
On Hacker News · 3
- Aug 9, 2025CaMeL-Powered Secure Agent Demo with ADK3 points · 1 comment
- Jun 13, 2025A collection of sample agents built with Agent Development (ADK)2 points
- Aug 13, 2025CaMeL agent to protect against indirect prompt injection1 point · 1 comment
On Stack Overflow · 2
- Aug 30, 2025Google ADK SequentialAgent sub_agents not waiting for user input1 vote
- Aug 29, 2025Issue with deploying ADK Agent on Google Agent Engine0 votes
Other versions1 project uses a version of this.
Word for word · 1
- google/adk-recipes★ 11k100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,843 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository43 other files it gives its agents.
- AGENTS.mdAGENTS.md
- contrib/kotlin/llm-auditor/AGENTS.mdAGENTS.md
- core/python/adk-voice-agent/AGENTS.mdAGENTS.md
- core/python/adk-voice-agent/sandbox/home/AGENTS.mdAGENTS.md
- core/python/adk-voice-agent/tests/eval/fixtures/alex/repos/portland-trip/AGENTS.mdAGENTS.md
- core/python/ambient-expense-agent/AGENTS.mdAGENTS.md
- core/python/ambient-quality-agent/AGENTS.mdAGENTS.md
- core/python/cross-session-memory/AGENTS.mdAGENTS.md
- core/python/deep-search/AGENTS.mdAGENTS.md
- core/python/genmedia-for-commerce/AGENTS.mdAGENTS.md
- core/python/long-horizon-harness/AGENTS.mdAGENTS.md
- core/python/oauth-user-consent-flow/AGENTS.mdAGENTS.md
- core/python/rag-agent-search/AGENTS.mdAGENTS.md
- core/python/rag-vector-search/AGENTS.mdAGENTS.md
- core/python/adk-voice-agent/sandbox/home/CLAUDE.mdCLAUDE.md
- core/python/genmedia-for-commerce/CLAUDE.mdCLAUDE.md
- align-recipe-pyprojectSkill
- extract-python-environment-variablesSkill
- generate-manifestSkill
- generate-python-runnability-testSkill
- github-pr-reviewSkill
- make-python-recipe-deployableSkill
- prepare-python-recipeSkill
- repo-oracleSkill
- scaffold-python-recipeSkill
- blog-writerSkill
- content-research-writerSkill
- approvalsSkill
- coding-workflowSkill
- connected-apps-mcpSkill
- deliverablesSkill
- filesystemSkill
- recommendationsSkill
- agents-cli-aquaSkill
- custom-investigationSkill
- rcaSkill
- bootstrap-google-toolsSkill
- find-skillsSkill
- google-workspaceSkill
- policySkill
- routinesSkill
- retail-product-searchSkill
- retail-virtual-tryonSkill
The file
We split this AGENTS.md into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 55
Only the classifier differs between plugins: the judge runs a second LLM in its own InMemoryRunner.
- Very specific: line 141
The two plugins are not identical in coverage: Only LlmAsAJudge implements before_tool_callback (tool *inputs*).
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Safety Plugins — Agent-Agnostic Guardrails
Intent
A clone-and-study recipe for global safety guardrails implemented as ADK
BasePlugins attached to the Runner. Two interchangeable plugins are
provided — an LLM-as-a-judge and a Model Armor filter — that hook the
same lifecycle callbacks to classify and block unsafe content. The interesting
part is not the agents (deliberately mundane sum/fibonacci demos); it's the
plugins: because they're wired at the Runner they wrap every agent and
sub-agent under it, and they defend against session poisoning by never
persisting harmful content to session state.
When To Use
- The user wants a safety/guardrail layer that is agent-agnostic — attach once at the Runner instead of editing each agent's callbacks.
- The user wants a worked example of ADK plugin hooks
(
on_user_message_callback,before/after_tool_callback,after_model_callback, plus thebefore_run_callbackhalt). - The user is choosing between an LLM judge (flexible, prompt-driven) and Model Armor (managed GCP content-safety API) and wants both side by side.
- The user cares about session-poisoning attacks (harmful text left in history being reused to jailbreak later turns).
Eval
- Scenarios Path: none — this recipe ships no eval datasets and no
agents-clieval config. Do not look fortests/eval/. - Tests live in
tests/:tests/test_agents.pyis a happy-path smoke test that runsroot_agentthrough anInMemoryRunnerand asserts a non-empty reply (live Gemini call — needs ADC);tests/test_runnability.pyis an import-time check thatroot_agentis defined (patchesgoogle.auth.defaultso it runs credential-less). Neither test exercises the safety plugins.
End-to-end flow
main.pybuilds anInMemoryRunner(agent=root_agent, plugins=[...]). The chosen plugin (LlmAsAJudgeorModelArmorSafetyFilterPlugin) is attached to the Runner, so it guards bothmain_agentand itssub_agent.- User message →
on_user_message_callbackclassifies the prompt. If unsafe, the callback overwrites the stored userContentwith a "removed" placeholder and sets session stateis_user_prompt_safe=False;before_run_callbackthen reads that flag, resets it toTrue, and returns a canned model reply — halting the invocation before the prompt reaches the model. Harmful text is never persisted (the session-poisoning defense). - Tool call →
before_tool_callbackclassifiestool.name(args); unsafe → returns{"error": ...}so the call is blocked as if the tool failed. (LlmAsAJudgeonly.) - Tool output →
after_tool_callbackclassifies the result; unsafe → returns{"error": ...}in place of the output. - Model output →
after_model_callbackjoins the response parts and classifies them; unsafe → replaces the response with a canned "removed" message. - Only the classifier differs between plugins: the judge runs a second
LLM in its own
InMemoryRunner; Model Armor calls the Model Armor REST API and reports the matched filter categories.
Most interesting files to study (in order)
Safety plugins (the bulk of the logic)
safety_plugins/plugins/agent_as_a_judge.py— theLlmAsAJudgeBasePlugin. Adefault_jailbreak_safety_agent(LlmAgent, model fromMODEL_NAME_GENERATED_2) instructed byJAILBREAK_FILTER_INSTRUCTIONto answer only<SAFE>/<UNSAFE>runs in its ownInMemoryRunner(judge_app), decoupled from the protected agent._is_unsafe()wraps each payload in typed tags (<user_message>,<tool_call>,<tool_output>,<model_output>) so one judge prompt classifies every hook, then appliesanalysis_parser(default"UNSAFE" in analysis). Which hooks fire is gated by theJudgeOnStrEnumset (judge_on, default{USER_MESSAGE, TOOL_OUTPUT}). Study theon_user_message_callback→before_run_callbackhandshake viasession.state["is_user_prompt_safe"]— that is the session-poisoning defense.safety_plugins/plugins/model_armor.py— theModelArmorSafetyFilterPluginBasePlugin. Its constructor readsGOOGLE_CLOUD_PROJECT/GOOGLE_CLOUD_LOCATION/MODEL_ARMOR_TEMPLATE_ID, builds the template resource name, and creates a regionalModelArmorClient(modelarmor.<location>.rep.googleapis.com). It callssanitize_user_prompton user messages and tool outputs andsanitize_model_responseon model output; violations trigger the sameis_user_prompt_safe/before_run_callbackhalt, a replacementLlmResponse, or a{"error": ...}. Differences from the judge: nobefore_tool_callback(tool inputs are not checked), and blocked messages list the detected filter categories.
Plugin support
safety_plugins/util.py— the shared plumbing.run_prompt()sends oneContentthrough a runner and returns(author, text)— used by bothmain.pyand the judge plugin.parse_model_armor_response()plus the per-filter parsers (csam,malicious_uris,rai,pi_and_jailbreak,sdp) flatten aSanitize*Responseinto a list of matched filter names (returnsNoneonNO_MATCH_FOUND) — this is what turns the raw API result into the human-readable "reasons" appended to a blocked message.safety_plugins/prompts.py—JAILBREAK_FILTER_INSTRUCTION, the judge's system instruction: a detailed jailbreak taxonomy (persona/role-play, hypothetical framing, rule manipulation, obfuscation/encoding, adversarial suffixes, low-resource-language evasion, …) ending with the tag contract and "respond only with<UNSAFE>or<SAFE>".ROOT_AGENT_SI/SUB_AGENT_SIare trivial by comparison.safety_plugins/tools.py—short_sum_tool/long_sum_tool(CPU-bound) andio_bound_tool(asleep) are filler, butfib_tooldeliberately appends a planted "unsuspecting message that can cause undesired output" to its return value — a fixture for watching theafter_toolhook catch poisoned tool output.
CLI runner + agents (thin layer — read last)
safety_plugins/main.py— the entry point. An absl--plugin {llm_judge,model_armor,none}flag selects which plugin to build, then the one line that matters:plugins=pluginsonInMemoryRunner(agent=root_agent, app_name="test_app_with_plugin", ...)— attaching guardrails at the Runner is what makes them global. Followed by a multi-turn REPL overutil.run_promptuntil you typeexit.safety_plugins/agent.py— deliberately generic.root_agent(namemain_agent, toolsshort_sum_tool/long_sum_tool) delegates tosub_agent(toolsfib_tool/io_bound_tool); model fromMODEL_NAME_GENERATED_1. The agents carry no safety logic — all guardrails live in the plugins, which is the whole point (agent-agnostic).safety_plugins/__init__.py— env bootstrap:load_dotenv(), discovers the GCP project viagoogle.auth.default(), and defaultsGOOGLE_GENAI_USE_VERTEXAI=trueandGOOGLE_CLOUD_LOCATION=global.
Data handling
- No datastore, no ingestion. The only "data" is conversation content (user messages, tool calls/outputs, model responses) flowing through the plugin hooks.
- Session-poisoning defense. Unsafe user prompts are overwritten with a
"removed" placeholder before they're persisted, and the invocation is
halted in
before_run_callbackso the model never sees them; unsafe model output is likewise replaced. This stops later turns from exploiting harmful text left in session history. - Session state.
is_user_prompt_safeis the single flag the plugins write; it is read once inbefore_run_callbackand reset toTrueeach turn. - Classifier backends differ in cost. The judge is a second Gemini call (extra latency + tokens, its own runner/session); Model Armor is a regional REST API keyed by a GCP template. Neither retains conversation data beyond the request.
Gotchas / things to know
- The two plugins are not identical in coverage. Only
LlmAsAJudgeimplementsbefore_tool_callback(tool inputs). Model Armor checks user prompts, model output, and tool outputs only. (README.mdsays "both" hookbefore_tool_callback; the code disagrees — trust the code.) - Judge defaults are conservative.
judge_ondefaults to{USER_MESSAGE, TOOL_OUTPUT}, so model-output and tool-input checks are off by default — pass a widerjudge_onset to enable them. - The judge doubles model calls (latency + cost) and is only as good as its
parser. Default
analysis_parseris"UNSAFE" in analysis; the instruction emits<UNSAFE>/<SAFE>(still contains the substring, so it works). A customanalysis_parseris the intended extension point. - Model Armor needs a pre-created template and
MODEL_ARMOR_TEMPLATE_ID; without a valid project/location/template the client URL is wrong and calls fail. It uses a regional endpoint, soGOOGLE_CLOUD_LOCATIONmust be a real region (e.g.us-central1) — notglobal, which__init__.pydefaults to. - Models come from env vars.
MODEL_NAME_GENERATED_1(agents) andMODEL_NAME_GENERATED_2(judge)..env.examplesets the agents togemini-3.5-flashand the judge togemini-3.1-flash-lite. main.pyuses absl flags, so run it as a module (python -m safety_plugins.main);--pluginonly acceptsllm_judge|model_armor|none.- Tests don't cover the plugins and
tests/test_agents.pymakes a live Gemini call, so it needs ADC/credentials.
Where to run things
No Makefile — run everything with uv from core/python/safety-plugins/:
uv sync(add--group devforpytest/ruff) — install.uv run python -m safety_plugins.main --plugin {llm_judge,model_armor,none}— the plugin CLI; drops you into a multi-turn REPL (typeexitto quit).noneis the baseline with no guardrails.uv run adk run safety_plugins/uv run adk web— standard ADK CLI / local web UI (selectsafety_pluginsin the dropdown).uv run pytest tests— the smoke + import tests (need ADC for the live Gemini call).
Reuse (copy as-is)
safety_plugins/plugins/is the reusable artifact. Both plugins are plain ADKBasePlugins with no coupling tomain_agent/sub_agent— attach them to any Runner and they guard every agent/sub-agent underneath:Runner(agent=your_agent, plugins=[LlmAsAJudge()])or[ModelArmorSafetyFilterPlugin()].- Bring the support code along.
agent_as_a_judge.pyimportsprompts.JAILBREAK_FILTER_INSTRUCTIONandutil.run_prompt;model_armor.pyimportsutil.parse_model_armor_response. Copyplugins/together withutil.py(andprompts.pyfor the judge). - Customize without editing the plugin: swap the judge model/agent via
judge_agent, widen coverage viajudge_on, or change verdict logic viaanalysis_parser; point Model Armor at a different template viatemplate_id/env. - Dependencies:
google-adkandgoogle-genai(both), plusgoogle-cloud-modelarmor(Model Armor only).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

