
Adk Style
- 1 installs
- 20.9k repo stars
- Updated July 28, 2026
- google/adk-python
adk-style skill documents ADK development style guide for routine nits - Python idioms, codebase conventions, imports, typing, Pydantic patterns, formatting, logging, async/concurrency, and file organization.
About
adk-style skill documents ADK development style guide for routine nits - Python idioms, codebase conventions, imports, typing, Pydantic patterns, formatting, logging, async/concurrency, and file organization. Use this skill whenever writing code, tests, or reviewing PRs for the ADK project to ensure compliance with styling a. name: adk-style description: ADK development style guide for routine nits - Python idioms, codebase conventions, imports, typing, Pydantic patterns, formatting, logging, async/concurrency, and file organization. Use this skill whenever writing code, tests, or reviewing PRs for the ADK project to ensure compliance with styling and coding conventions. Triggers on "code style", "how should I format",
- ADK development style guide for routine nits - Python idioms, codebase conventions, imports, typing, Pydantic patterns,
- Platform-specific setup patterns for adk-style.
- Evidence-backed steps from upstream SKILL.md.
- When-to-use criteria for adk-style versus alternatives.
Adk Style by the numbers
- 1 all-time installs (skills.sh)
- Ranked #2,003 of 2,742 Automation & Workflows skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
adk-style capabilities & compatibility
- Capabilities
- adk style quick start · adk style when to use guidance · adk style integration patterns
npx skills add https://github.com/google/adk-python --skill adk-styleAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 20.9k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 28, 2026 |
| Repository | google/adk-python ↗ |
How do I use adk-style correctly?
ADK development style guide for routine nits - Python idioms, codebase conventions, imports, typing, Pydantic patterns, formatting, logging, async/concurrency, and file organization. Use this skill wh
Who is it for?
Teams implementing adk-style workflows from the catalog.
Skip if: Skip when requirements clearly match a different specialized stack.
When should I use this skill?
User asks about adk-style, adk development style guide for routine nits - python idioms, codebase conventions, import.
What you get
Working adk-style setup with validated configuration and next steps.
Files
ADK Style Guide
Style Guide (references/)
- Visibility — naming conventions for module-private, internal, and package-private visibility.
- Imports — relative vs absolute imports,
TYPE_CHECKINGpatterns. - Typing — strong typing, avoiding Any, bare type names, keyword-only arguments,
Optionalvs| None, abstract parameter types, mutable default avoidance, runtime type discrimination. - Pydantic Patterns — Pydantic v2 usage,
Field()constraints,field_validator,model_validator, private attributes, deprecation migration, post-init setup. - Formatting — indentation, line limits, and running pre-commit hooks.
- Documentation — comments and docstrings.
- Logging — lazy evaluation and log levels.
- Async and Concurrency — async I/O requirements, avoiding blocking the event loop.
- File Organization — file headers and class organization.
Testing
references/testing.md — core principles, 9 rules for writing ADK tests, test structure template
Async and Concurrency Style Guide
- All I/O operations must be in async functions: Any operation that
performs I/O (network calls, file system access, database queries, etc.) must be defined in an async def function.
- Do not block the event loop: Avoid calling blocking synchronous
functions directly from async code.
- Wrap synchronous I/O: If you must use a synchronous library for I/O
(e.g., standard open(), pathlib file operations, or synchronous clients), wrap the blocking call in asyncio.to_thread to run it in a separate thread and prevent blocking the main event loop.
Example:
async def save_data(path: Path, data: bytes) -> None:
# Wrap blocking file write in asyncio.to_thread
await asyncio.to_thread(path.write_bytes, data)Documentation and Comments
Public API Documentation
- Clear Usage: For public interfaces, explain the intended usage clearly, with concise examples.
- Public Classes: Explain all public attributes.
- Public Methods/Functions: Explain all arguments, return values, and raised exceptions.
Internal Implementation Comments
- Explain Why, Not What: For internal code and private methods, explain why, not what — the code itself should be self-documenting.
- Stale References: Don't reference RFCs or design docs in source code (they become stale).
File Organization
- One class per file in
workflow/. - Private modules prefixed with
_(e.g.,_base_node.py). - Public API exported through
__init__.py. - Unit tests must be placed in the same folder hierarchy under
tests/unittests/as the original file insrc/. - If a single source file has multiple test files (e.g. testing different classes or behaviors separately), use the source file name (without leading underscores or extension) as the prefix for the test file names.
- Example:
src/google/adk/tools/environment/_tools.py->tests/unittests/tools/environment/test_tools_edit_file.py
File Headers
Every source file must have: 1. Apache 2.0 license header. 2. from __future__ import annotations. 3. Standard library imports, then third-party, then relative.
Formatting Style Guide
- 2-space indentation (never tabs).
- 80-character line limit.
pyinkformatter (Google-style).isortwith Google profile for import sorting.- Enforced automatically by pre-commit hooks (
isort,pyink,addlicense,mdformat). Use theadk-setupskill to install and configure these tools.
Running Formatter Manually
# Format only staged files (runs automatically on commit)
pre-commit run
# Format all changed files (staged + unstaged)
pre-commit run --files $(git diff --name-only HEAD)
# Format all files in the repo
pre-commit run --all-filesImports Style Guide
General Rules
- Source code (
src/): Use relative imports.
from ..agents.llm_agent import LlmAgent
- Tests (
tests/): Use absolute imports.
from google.adk.agents.llm_agent import LlmAgent
- Import from module: Import from the module file, not from
__init__.py.
from ..agents.llm_agent import LlmAgent (not from ..agents import LlmAgent)
- CLI package (
cli/): - Treat as an external package.
- Use relative imports for files within the
cli/package. - Use absolute imports for files outside of the
cli/package. - Dependency Direction: Only
cli/can import from the rest of the codebase. The other codebase must STRICTLY NOT import fromcli/.
TYPE_CHECKING Imports
Use TYPE_CHECKING for imports needed only by type hints to avoid circular imports at runtime:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..agents.invocation_context import InvocationContextThis works because from __future__ import annotations makes all annotations strings (deferred evaluation), so the import is never needed at runtime.
Logging Style Guide
General Rules
- Lazy Evaluation: Use lazy-evaluated
%-based templates for logging to avoid overhead when the log level is not enabled. - Good:
logging.info("Processing item %s", item_id) - Bad:
logging.info(f"Processing item {item_id}") - Contextual Logging: Leverage structured logging and trace IDs when available to correlate logs across operations.
- No Secrets: Never log sensitive information (API keys, user credentials, or PII).
Log Levels
- DEBUG: Detailed information for diagnosing problems. Use generously in internal implementation but avoid cluttering production logs.
- INFO: Confirmation that things are working as expected (e.g., workflow started, node completed).
- WARNING: Indication that something unexpected happened or a problem might occur soon (e.g., retry triggered).
- ERROR: A serious problem that prevented a function or operation from completing.
Pydantic Patterns
ADK models use Pydantic v2. This guide covers the key patterns used throughout the codebase.
Basic Model Structure
- Use
Field()for validation, defaults, and descriptions. - Use
PrivateAttr()for internal state that shouldn't be serialized. - Use
model_post_init()instead of__init__for setup logic. - Prefer
model_dump()overdict()(Pydantic v2).
On-Wire Models
For Pydantic models that cross network or system boundaries (e.g., API payloads, WebSocket messages, event persistence), inherit from SerializedBaseModel located in google.adk.utils._serialized_base_model.
This ensures:
- camelCase serialization by default (via
alias_generator=to_camel).
Docstrings as Field Descriptions
To keep code Pythonic and ensure that generated schemas stay in sync with documentation, it is strongly recommended to use docstrings as field descriptions for all Pydantic models in the ADK codebase.
To enable this, add use_attribute_docstrings=True to your model's ConfigDict:
from pydantic import BaseModel, ConfigDict
class MyModel(BaseModel):
model_config = ConfigDict(use_attribute_docstrings=True)
field_name: str
"""Description of the field."""Note: If you are inheriting from SerializedBaseModel, this is already enabled by default.
Summary of When to Use Each
| Need | Pattern |
|---|---|
| Simple numeric/string bounds | Field(ge=0, le=100) |
| Single-field business logic | @field_validator('field', mode='after') |
| Cross-field consistency | @model_validator(mode='after') |
| Field deprecation/migration | @model_validator(mode='before') |
| Internal mutable state | PrivateAttr(default_factory=...) |
| Post-construction setup | model_post_init() |
Field() with Constraints
Use Field() constraints for declarative validation directly on the field definition. This keeps validation close to the data declaration and avoids custom validator boilerplate.
field_validator — Single-Field Validation
Use @field_validator for validation logic that goes beyond simple constraints. This is heavily used in ADK (36+ instances). Always use mode='after' unless you need to intercept raw input before Pydantic coercion.
Rules:
- Decorate with
@field_validator(...). While@classmethodis automatically applied by Pydantic v2, adding it is recommended in ADK for explicit visibility. - Return the (possibly transformed) value.
- Raise
ValueErrorwith a descriptive message on failure. - Prefer
mode='after'(validates after Pydantic's own parsing/coercion).
model_validator — Cross-Field and Migration Validation
Use @model_validator when validation depends on multiple fields, or when handling deprecation/migration of field names.
mode='before' — Deprecation and Field Migration
mode='after' — Cross-Field Consistency
Rules:
mode='before': receives rawdata(usuallydict). Use for field renaming, deprecation, and input normalization. Must return the (modified) data.mode='after': receives the fully constructed model instance (self). Use for cross-field consistency checks. Must returnself.- Always guard
mode='before'validators withisinstance(data, dict)since data could also come as an existing model instance.
ADK Testing Style Guide
Core Principles
- Test through the public interface — call what users call, assert what users see.
- Test behavior, not implementation — verify outcomes (outputs, side effects, errors), not internal mechanics.
- Refactor-proof — if an internal refactor preserves the same behavior, all tests should still pass.
Rules
1. Test names describe the behavior, not the mechanism
# Good — describes what the caller observes
def test_empty_queue_returns_none():
def test_retry_stops_after_max_attempts():
def test_missing_key_raises_key_error():
# Bad — describes implementation details
def test_deque_popleft_called():
def test_retry_counter_incremented():
def test_dict_getitem_raises():2. Docstring: one-line summary, then setup/act/assert
The first line describes the expected behavior from the caller's perspective. For complex tests (multi-step, multi-invocation), follow with a structured breakdown of Setup, Act, and Assert.
# Good — simple test, one-liner is enough
"""Getting from an empty cache returns the default value."""
# Good — complex test with structured breakdown
"""Partial FR re-runs nested Workflow, resolved child completes
while unresolved stays interrupted.
Setup: outer_wf → inner_wf → (child_a, child_b) → join.
Both children interrupt on first run.
Act:
- Run 2: resolve only child_a's FR.
- Run 3: resolve child_b's FR.
Assert:
- Run 2: child_a produces output, invocation still interrupted.
- Run 3: child_b produces output, join completes, no interrupts.
"""
# Bad — restates the implementation
"""LRUCache._store.get returns sentinel when key missing."""
"""ThreadPool._accept_tasks flag checked in submit()."""3. Each test covers one behavior
If a test checks multiple unrelated behaviors, split it. If you can't describe the test in one sentence, it's testing too much.
# Bad — tests capacity AND eviction AND default in one test
def test_cache_behavior():
assert cache.size == 0
assert cache.get('x') is None
cache.put('a', 1)
assert cache.size == 1
# Good — split into focused tests
def test_new_cache_is_empty():
"""A freshly created cache has no entries."""
def test_cache_evicts_oldest_when_full():
"""Adding to a full cache removes the least recently used entry."""4. Don't test internal state
# Bad — reaches into private attributes
assert pool._workers[0].is_alive
assert parser._state == 'HEADER'
assert isinstance(router._handler, _FastHandler)
# Good — tests through the public interface
assert pool.active_count == 1
assert parser.parse('data') == expected
assert router.route('/api') == handler5. Use real components, mock only boundaries
ADK tests should use real implementations as much as possible instead of mocking.
- Mock external dependencies: LLM APIs, cloud services, session stores
- Use real ADK components: BaseNode subclasses, Event, Context
- Mock InvocationContext when testing NodeRunner (it's a boundary)
6. Test fixtures should be minimal
Define the simplest possible setup that triggers the behavior:
# Good — minimal fixture, one purpose
def make_user(role='viewer'):
return User(name='test', email='t@t.com', role=role)
# Bad — kitchen-sink fixture with unrelated setup
def make_full_test_env():
db = create_database()
user = create_user_with_billing()
setup_notifications()
...7. Keep arrange logic close to the test
When a helper class or fixture is used by only one test, define it inline inside the test function. This keeps the setup visible at the point of use and avoids scrolling to distant module-level definitions. Extract to module level only when 3+ tests share the same helper.
# Good — helper defined inline, right next to the test
@pytest.mark.asyncio
async def test_state_delta_bundled_with_output():
"""State set before yield is flushed onto the output event."""
class _Node(BaseNode):
async def _run_impl(self, *, ctx, node_input):
ctx.state['color'] = 'blue'
yield 'result'
ctx, events = _make_ctx()
await NodeRunner(node=_Node(name='n'), parent_ctx=ctx).run()
assert events[0].output == 'result'
assert events[0].actions.state_delta['color'] == 'blue'
# Bad — helper defined 300 lines above, reader must scroll
class _StateThenOutputNode(BaseNode):
async def _run_impl(self, *, ctx, node_input):
ctx.state['color'] = 'blue'
yield 'result'
# ... 300 lines later ...
async def test_state_delta_bundled_with_output():
node = _StateThenOutputNode(name='n')
...8. Assertions tell a story
# Good — reads like a specification
assert queue.size == 0
assert config.get('timeout') == 30
assert response.status_code == 404
# Bad — overly defensive, tests framework behavior
assert isinstance(queue, Queue)
assert hasattr(config, 'get')
assert len(response.headers) > 09. Structure tests as arrange, act, assert
Every test has three distinct steps:
- Arrange — set up the external state specific to the scenario.
General setup shared by many tests belongs in fixtures.
- Act — call the system under test. Usually a single call.
- Assert — verify return values or visible state changes. No
further calls to the system under test here.
Keep steps distinct. Separate with blank lines. In simple tests where each step is a single statement, blank lines can be omitted. In complex tests, use descriptive comments like "Given [situation]", "When [action]", "Then [expectation]" — avoid bare labels that add no information.
# Good — clear visual separation
def test_cache_returns_stored_value():
cache = Cache()
cache.put('key', 'value')
result = cache.get('key')
assert result == 'value'
# Good — simple test, blank lines omitted
def test_new_cache_is_empty():
assert Cache().size == 0
# Bad — steps interleaved
def test_cache_behavior():
cache = Cache()
cache.put('key', 'value')
result = cache.get('key')
assert result == 'value'
cache.put('key2', 'value2') # more setup after assert
assert cache.size == 2Test Structure Template
"""Tests for <ComponentName>.
Verifies that <component> correctly <high-level behavior>.
"""
# --- Fixtures (minimal, one purpose each) ---
def _make_service():
...
# --- Tests (one behavior per test) ---
def test_<behavior_description>():
"""<One sentence: what the system does from the outside.>"""
# Given a service with default config
service = _make_service()
input_data = 'hello'
# When the operation is performed
result = service.do_something(input_data)
# Then the result matches expectations
assert result == expectedType Hints and Strong Typing
General Rules
- Prefer Strong Typing: Use type hints for all function arguments and return types. Avoid leaving types unspecified.
- Minimize `Any`: Use specific types or
Genericwhenever possible. AvoidAnyas it bypasses type checking. - No double-quoted type hints: When
from __future__ import annotationsis present, use bare type names (e.g.,list[str]instead of"list[str]"). - Always include `from __future__ import annotations`: Every source file must include this immediately after the license header, before any other imports. This enables forward-referencing classes without quotes (PEP 563).
Optional[X] vs X | None
The codebase uses both styles. Follow this convention:
- New code (especially in
workflow/): PreferX | None— it is more concise and modern. - Existing files: Match the style already used in the file for consistency.
- Both are acceptable — do not refactor one to the other without reason.
Abstract Types for Function Parameters
Use abstract types from collections.abc for function parameter annotations. This accepts the widest range of inputs while remaining type-safe. Use concrete types for return annotations to give callers the most useful information.
Keyword-Only Arguments
Use * to force keyword-only arguments on functions with multiple parameters of the same type, or where argument order is error-prone. This is a widely used pattern in ADK (16+ files).
*When to use ``:**
- Constructors (
__init__) with 2+ non-self parameters - Any function where swapping arguments would silently produce wrong results
- Methods with multiple
strorintparameters
Mutable Default Arguments
Never use mutable default arguments. Use None as a sentinel and initialize in the function body. This is a well-followed pattern throughout ADK.
This applies to list, dict, set, and any other mutable type.
Runtime Type Discrimination with isinstance()
Use isinstance() for runtime type discrimination when handling polymorphic inputs. This is pervasive in ADK (700+ usages). Prefer exhaustive if/elif chains with a clear fallback.
Guidelines:
- Always include an
elsebranch that raisesTypeErroror handles the unknown case. - Prefer
isinstance(x, SomeType)overtype(x) is SomeType— it handles subclasses correctly. - For checking multiple types:
isinstance(x, (TypeA, TypeB)).
Visibility Style Guide
Python does not have native access modifiers (like public, private, or package-private). ADK relies on naming conventions and module structure to define visibility boundaries.
Conventions
1. Module-Private / Internal Files
- Private by Default: All new
.pymodule files undersrc/google/adk/must be private by default (prefixed with_). This is enforced by a pre-commit hook (check-new-py-prefix). - Even if a file contains symbols intended for the public API, the file itself must have a leading underscore. The symbols are then exposed via the package's
__init__.py. - Files intended for internal use within a package or subsystem must also be prefixed with a leading underscore (e.g.,
_task_models.py). - These files should never be imported directly by code outside of the ADK framework.
2. Class and Function Visibility
- Public: No leading underscore. Intended for use by consumers of the module or package.
- Internal/Private: Leading underscore (e.g.,
_private_method()). Intended only for use within the defining class or module.
3. Package-Private (Subsystem Visibility)
Since Python lacks true package-private access, we simulate it by:
- Not exporting the symbol in the package's
__init__.py. - Using
_-prefixed modules for internal implementation details. - Code within the same package can import from these
_modules, but code outside should not. - Direct Imports Required: Within the ADK framework, importing from
__init__.pyis not allowed. You must import from the specific module directly. This helps keep__init__.pyminimal and keeps packages as self-contained as possible.
4. Public API Export
- The public API of a package must be explicitly exported in
__init__.py. - Use `__all__`: The
__init__.pyfile should define__all__to explicitly list the symbols that are part of the public API. - Only public names (symbols intended for use outside the package) should be imported into
__init__.pyand listed in__all__. - Users should be able to import public symbols directly from the package level, rather than digging into internal modules.
Examples
Exposing a Public Interface
# In src/google/adk/agents/llm/task/_task_agent.py (File is private by default)
class TaskAgent: # Public symbol
...
# In src/google/adk/agents/llm/task/__init__.py
from ._task_agent import TaskAgent
__all__ = [
'TaskAgent',
]Keeping Implementation Details Private
# In src/google/adk/agents/llm/task/_task_models.py (Internal file)
class TaskRequest(BaseModel): # Public within the module, but module is private
...
# In src/google/adk/agents/llm/task/__init__.py
# We DO NOT export TaskRequest here if it is only for internal use within the task package.Related skills
FAQ
What does adk-style do?
adk-style skill documents ADK development style guide for routine nits - Python idioms, codebase conventions, imports, typing, Pydantic patterns, formatting, logging, async/concurrency, and file organization.
When should I use adk-style?
User asks about adk-style, adk development style guide for routine nits - python idioms, codebase conventions, import.
Is this skill safe to install?
Review the Security Audits panel on this page before installing in production.