writing-skills
writing-skills
Use when creating new skills, editing existing skills, or verifying skills work before deployment
adk-dev-guideADK development lifecycle guardrails — 4-phase workflow (Understand → Build → Evaluate → Deploy), import precision rules, GOOGLE_CLOUD_LOCATION debugging, code preservation, and infinite loop breaking. Load BEFORE google-adk when starting a new ADK agent feature.
Independent GitHub snapshot · Automated classification · Original documentation governs
Development lifecycle guardrails for AI agents using Google ADK and Gemini models.
Every ADK agent feature follows this workflow. Skipping phases increases risk of eval failures, infinite loops, and deployment issues.
Before writing any code:
DESIGN_SPEC.md completely — this is the source of truthStop if:
Follow these patterns:
ToolContext for session state — never global variables# Fallback: comment to every tool functionInMemoryRunner for development and testsadk web — launch ADK playground for interactive testinguv run pytest tests/ -v — run unit testsuv run ruff check src/ && uv run ruff format src/ — lint and formatuv run mypy src/ — type checkStop if:
mypyruff check returns non-zero)Iterate 5–10 rounds per feature:
evalset.json with: 1 happy path, 1 error path, 1+ edge caseseval_config.json with appropriate eval criteriaadk eval ./app evalset.json --config_file_path=eval_config.json --print_detailed_resultsStop if:
Use adk-deploy-guide skill:
uv run pytest tests/evals/ locally before opening PREvery ADK agent project MUST have a DESIGN_SPEC.md in the agent service root before any implementation code is written.
# Agent Name — Design Spec
## Purpose
One-sentence mission statement.
## Example Use Cases
- User does X, agent responds with Y
- User does A, agent handles error B gracefully
## Required Tools
- tool_name: description and responsibility
- tool_name: description and responsibility
## Safety Constraints
- No [X] without [Y]
- Always [Z] before [A]
- Blocked: [B] under any condition
## Success Criteria
- Agent achieves 90% accuracy on test set
- Tool calls execute in <500ms
- Error recovery succeeds in <1 min
## Edge Cases & Fallbacks
- When tool A fails: [fallback behavior]
- When state is invalid: [reset or error response]
ADK tools must be imported from their specific submodule, not from the package level.
# ✅ CORRECT — import from specific submodule
from google.adk.tools.google_search import google_search
from google.adk.tools.load_web_page import load_web_page
from google.adk.tools.built_in_code_execution import built_in_code_execution
# ❌ WRONG — DO NOT import from package level
from google.adk.tools import google_search # ✗ Will fail at runtime
from google.adk.tools import load_web_page # ✗ Will fail at runtime
| Tool | Correct Import |
|---|---|
| Google Search | from google.adk.tools.google_search import google_search |
| Load Web Page | from google.adk.tools.load_web_page import load_web_page |
| Built-In Code Execution | from google.adk.tools.built_in_code_execution import built_in_code_execution |
Rule: When in doubt about import syntax, check the google-adk skill or the official ADK documentation — do not guess.
Model 404 errors are almost always a location issue, not a model name issue.
Check GOOGLE_CLOUD_LOCATION env var — must be set to a region that supports the model
Check GOOGLE_CLOUD_PROJECT env var — must be set to a valid GCP project ID
Only then check model name — but rarely the issue if steps 1–2 are correct
Error: 404 Model 'projects/my-project/locations/europe-west1/models/gemini-3.1-flash' not found
↓ Fix:
GOOGLE_CLOUD_LOCATION=us-central1 python app.py
Remember: Location is the #1 cause of 404 errors in ADK development. Check it first.
Once an agent is working, protect it from accidental breakage.
output_key values (downstream agents reference them by name)If the same error occurs 3+ times in a row: STOP immediately. Do not retry the same approach.
on_tool_error_callback to interrupt the retry cycleAfter 3 identical failures:
STOPPED after 3 identical failures:
- Attempt 1: [what you tried, what failed]
- Attempt 2: [what you tried, what failed]
- Attempt 3: [what you tried, what failed]
Options:
A) Different approach: [describe]
B) Escalate to human — need more context
Replace this section with your project's ADK constraints. Document: ADK version pin, model name(s), tool placement rules (R1/R2), eval thresholds, session backend choices, and any compliance requirements.
Example table:
| Constraint | Value | Notes |
|---|---|---|
| ADK version | google-adk==X.Y.Z exactly | Pin to specific version |
| Model | gemini-3.1-flash | Default — override per agent if needed |
| Runner (dev) | InMemoryRunner for tests | Never use real API in unit tests |
| Runner (prod) | Runner for production | Wire VertexAiSessionService |
| Every tool must have | # Fallback: comment | Defines degradation mode |
| Eval gates | Accuracy thresholds block merge | Define per-agent thresholds |
Run periodically to detect outdated skill files:
# Check if skills are outdated (>90 days)
npx skills check -g
# Update all skills to latest version
npx skills update -g
# Update specific skill
npx skills update adk-dev-guide -g
If a skill's last-reviewed date is >90 days old, flag it for review before using.
When you need to validate system behavior, measure performance, or run CI without real LLM calls:
Create a variant of your ADK agent that overrides only the LLM decision callback with deterministic heuristics. Same tool contracts, same output schema, same session state — zero model calls.
# Standard LLM-powered agent
class RunnerAgent(BaseAgent):
async def _run_async_impl(self, ctx: InvocationContext):
result = await self.llm.generate_content_async(build_prompt(ctx.session.state))
# ... handle result
# Shadow: same class hierarchy, no LLM
class AutopilotRunnerAgent(RunnerAgent):
async def _run_async_impl(self, ctx: InvocationContext):
state = ctx.session.state
decision = "slow" if state["energy"] < 0.3 else "steady"
ctx.session.state["decision"] = decision
yield Event(author=self.name, content=types.Content(parts=[types.Part(text=decision)]))
Switch via env var — no code-path changes at the call site:
def get_runner_agent() -> BaseAgent:
if os.getenv("RUNNER_MODE") == "autopilot":
return AutopilotRunnerAgent()
return RunnerAgent()
Use the autopilot variant in all InMemoryRunner unit tests. Zero API cost, deterministic assertions.
Record every event your ADK agent emits to a .ndjson file during a real run. Replay against the frontend or test harness without running agents:
# Record in agent's broadcast/after_agent layer
with open("recordings/sim-run.ndjson", "a") as f:
f.write(json.dumps({"ts": elapsed, "event": event.model_dump()}) + "\n")
Commit the recording — it becomes a regression baseline. CI replays it; no agents need to be running.
When to use either pattern: CI integration tests, load testing (run 1000s of autopilot agents cheaply), keynote demos, UI development iterations.
Full implementation detail for both patterns:
agentic-agent-variant-ladder.mdinagentic-ai-dev/reference/
adk-dev-lifecycle.md — Full detail on each phase, commands, common pitfallsgoogle-adk/SKILL.md — Agent patterns, tool API reference, ToolContext usageadk-eval-guide skill — Evaluation strategy and eval dataset structureadk-deploy-guide skill — Deployment to Cloud Run / Agent Engine, health checks, rollbackagentic-ai-dev/reference/agentic-agent-variant-ladder.md — Agent variant ladder, deterministic shadow agents, NDJSON replayLoad adk-dev-guide BEFORE google-adk when:
Load google-adk only when: