Skip to content

docs(plugins): fix broken examples, correct plugin callback docs - #2015

Open
GWeale wants to merge 3 commits into
google:mainfrom
GWeale:docs-audit-plugins
Open

GWeale wants to merge 3 commits into
google:mainfrom
GWeale:docs-audit-plugins

Conversation

@GWeale

@GWeale GWeale commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

The plugins and skills pages: examples that can't run as written, plus callback behavior and Python registration guidance that had drifted from the source.

Changes

  • docs/plugins/index.md — the headline CountInvocationPlugin example had its whole class body unindented (docstring, __init__ and both callbacks sat at module level), and the registration example defined root_agent inside the hello_world tool function, so it was never importable; both are now indented correctly. Beyond that: notes that Python's Runner/InMemoryRunner plugins= is deprecated in favor of App(plugins=...) and that passing both raises ValueError; documents on_agent_error_callback, on_run_error_callback and close() (bounded by plugin_close_timeout, five seconds by default); corrects after_run_callback's return annotation from Optional[None] to None; describes the on-event return as a merge onto the original event rather than a wholesale replace, with id, invocation_id and timestamp always taken from the original; adds the built-in Debug Logging plugin to the list; and repairs two garbled "is only supported by the Plugins feature works as follows" sentences.
  • docs/skills/index.md — the inline-skills example passed get_weather_tool to SkillToolset without ever defining it, so it could not run; the function is now shown. The skill directory is renamed weather_skillweather-skill to match the frontmatter name, and the page now states that a filesystem skill's directory name must match that name or loading fails.

How this was produced

Part of a page-by-page audit of the Python docs against the google/adk-python v2.5.0 source: every import resolved against a real 2.5.0 install, every constructor kwarg checked against model_fields / inspect.signature, every documented default read off the field. A second independent pass re-derived each claim from source rather than trusting the finding, and a third conformed the new wording to the surrounding pages. mkdocs build --strict is clean.

Only Python tabs and language-neutral prose were touched — this audit had no ground truth for the Go / Java / TypeScript SDKs.

Split out of a larger audit branch so each area can be reviewed on its own.

@netlify

netlify Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploy Preview for adk-docs-preview ready!

Name Link
🔨 Latest commit 1c5cf71
🔍 Latest deploy log https://app.netlify.com/projects/adk-docs-preview/deploys/6a7641070ec6a50008eec516
😎 Deploy Preview https://deploy-preview-2015--adk-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@GWeale
GWeale force-pushed the docs-audit-plugins branch 2 times, most recently from d7640f3 to 1c5cf71 Compare August 7, 2026 20:33
@GWeale
GWeale force-pushed the docs-audit-plugins branch from 1c5cf71 to 68407bb Compare August 31, 2026 20:15
@zyantw

zyantw commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Here are my comments: @joefernandez

Comprehensive Technical Review

  • Reference Implementation: google/adk-python (main / commit 8db82ba and v2.5.0)
  • Overall Assessment: CONDITIONAL PASS (19 Passed, 1 Failed)

Before merging, there is one technical discrepancy to address in docs/plugins/index.md:

  • Claim on before_run_callback early exit: The note claiming that run_async() ignores before_run_callback returning types.Content when the root agent is an LlmAgent or Workflow was a known bug (#6013) that was resolved in PR #6032 (commit dac1869). In main, _run_node_async() captures early_exit_result and terminates the invocation with that event. Please update this section to state that returning types.Content halts execution across all agents and workflows.
  • Style Guidelines:
    • Use sentence case for heading: ### Register plugin class (instead of ### Register Plugin class).
    • Use doc-relative linking: [App](../apps/index.md) (instead of /apps/) to prevent broken links on subpath deployments.

2. Phase 1: Claims Verification Table

# Claim Status Direct Quotation & PR Location Codebase Verification Link
1 DebugLoggingPlugin is a built-in plugin capturing debug info per invocation to a YAML file. PASS docs/plugins/index.md#L72-L73:
* [**Debug Logging**](https://github.com/google/adk-python/blob/main/src/google/adk/plugins/debug_logging_plugin.py):
    Captures complete debug information for each invocation to a YAML file.
adk-python/debug_logging_plugin.py#L235-L251
2 CountInvocationPlugin class body belongs indented inside the class block to fix Python IndentationError. PASS docs/plugins/index.md#L94-L121:
class CountInvocationPlugin(BasePlugin):
    """A custom plugin that counts agent and tool invocations."""
    def __init__(self) -> None:
Python AST validation (IndentationError resolved)
3 In Python, plugins are registered via an App object using plugins= rather than on Runner. PASS docs/plugins/index.md#L271-L274:
Integrate your Plugin class by registering it during your agent initialization as part of your Runner class, or in Python your App object, using the plugins parameter.
adk-python/runners.py#L265-L266
4 The plugins argument on Runner and InMemoryRunner is deprecated and raises a DeprecationWarning. PASS docs/plugins/index.md#L278-L280:
In Python, the plugins parameter of Runner and InMemoryRunner is deprecated and raises a DeprecationWarning.
adk-python/runners.py#L365-L369
5 Passing both plugins and app to runner instantiation raises a ValueError. PASS docs/plugins/index.md#L282-L283:
Passing both plugins and app raises a ValueError.
adk-python/runners.py#L360-L364
6 Recommended registration pattern is setting plugins on App and passing app to InMemoryRunner(app=app). PASS docs/plugins/index.md#L280-L282:
Set plugins on an App instead and pass that App to the runner as InMemoryRunner(app=app).
adk-python/apps/app.py#L81
7 root_agent must be scoped at module level rather than inside hello_world so it can be passed to App(root_agent=root_agent). PASS docs/plugins/index.md#L318-L342:
root_agent = Agent(...)
app = App(name='test_app_with_plugin', root_agent=root_agent, plugins=[CountInvocationPlugin()])
runner = InMemoryRunner(app=app)
adk-python/apps/app.py#L75
8 In Python, error callbacks run when an Agent raises an exception and when the run itself fails. PASS docs/plugins/index.md#L739-L742:
In Python, error callbacks also run when an Agent raises an exception and when the run itself fails.
adk-python/base_agent.py#L619-L641, adk-python/runners.py#L646-L651
9 run_async() in Python ignores before_run_callback return value when root agent is an LlmAgent or Workflow. FAILED docs/plugins/index.md#L831-L836:
In Python, run_async() honors this return value only when the root agent is a BaseAgent that is not an LlmAgent. When the root agent is an LlmAgent or a Workflow, the returned content is ignored and the run proceeds as if you had returned None; use before_agent_callback or before_model_callback to short-circuit those runs instead.
adk-python/runners.py#L673-L693 (Refuted by commit dac1869)
10 If an agent raises an exception, on_agent_error_callback runs instead of after_agent_callback, is observation-only, and re-raises the original exception. PASS docs/plugins/index.md#L882-L885:
In Python, if the agent's run raises an exception, on_agent_error_callback(*, agent, callback_context, error) runs instead of after_agent_callback. That callback only observes the failure: its return value is ignored and the original exception is still raised.
adk-python/base_agent.py#L620-L625
11 Model on-error callback works when an exception is raised during the model call. PASS docs/plugins/index.md#L914:
The on error callback for Model objects works as follows:
adk-python/base_plugin.py#L279-L284
12 Tool on-error callback works when an exception is raised during tool run execution. PASS docs/plugins/index.md#L996:
The on error callback for Tool objects works as follows:
adk-python/base_plugin.py#L356-L361
13 Returning an Event from on_event_callback merges fields onto original event, preserving id, invocation_id, and timestamp. PASS docs/plugins/index.md#L1068-L1070:
In Python, ADK merges your event onto the original: only the fields you set are applied, and id, invocation_id, and timestamp always come from the original event.
adk-python/runners.py#L1623-L1628
14 Return type annotation for after_run_callback is None. PASS docs/plugins/index.md#L1133:
async def after_run_callback(self, *, invocation_context: InvocationContext) -> None:
adk-python/base_plugin.py#L174-L176
15 on_run_error_callback runs instead of after_run_callback when the runner fails, is observation-only, and re-raises the exception. PASS docs/plugins/index.md#L1164-L1167:
on_run_error_callback(*, invocation_context, error): Runs instead of after_run_callback when the run fails with an unhandled exception. This callback only observes the failure: its return value is ignored and the original exception is still raised.
adk-python/base_plugin.py#L394-L410
16 close() executes once when closing the runner via await runner.close() and is bounded by plugin_close_timeout (5s default). PASS docs/plugins/index.md#L1168-L1173:
close(): Runs once per Plugin when you close the Runner with await runner.close(), not once per run. Use it to release resources the Plugin owns, such as an HTTP client or a metrics exporter. Each close() call is bounded by the runner's plugin_close_timeout, five seconds by default.
adk-python/runners.py#L248, adk-python/plugin_manager.py#L402-L405
17 Skill directory name in Python sample must be weather-skill to match name in frontmatter. PASS docs/skills/index.md#L39:
weather_skill = load_skill_from_dir(pathlib.Path(__file__).parent / "skills" / "weather-skill")
adk-python/skills/_utils.py#L191-L196
18 get_weather_tool must be defined before passing to SkillToolset(additional_tools=[get_weather_tool]). PASS docs/skills/index.md#L42-L47:
def get_weather_tool(city: str) -> dict:
    """Retrieves the current weather report for a specified city."""
    return {"status": "success", "report": f"The weather in {city} is sunny with a temperature of 25°C."}
Python AST validation (NameError resolved)
19 skills/ is resolved relative to the agent file in Python/TypeScript, but relative to cwd (os.DirFS("./skills")) in Go. PASS docs/skills/index.md#L123-L128:
The Python and TypeScript examples above resolve skills/ relative to the directory holding the agent source file, so place skills/ next to that file. The Go example uses os.DirFS("./skills"), which resolves relative to the current working directory instead. Either way, the skills/ directory must contain the sub-directories for the Skills you want to use in your agent.
Multi-language code sample audit
20 When loading a Skill from filesystem, directory name must match frontmatter name, or loading fails. PASS docs/skills/index.md#L180-L181:
When loading a Skill from the filesystem, the directory name must match the name in the frontmatter, or loading fails.
adk-python/skills/_utils.py#L191-L196

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants