{"owner":"google","repo":"adk-python","hasSkills":true,"totalSkillsCount":148,"totalTokensCount":201162,"categories":["anthropic-skill","root-instruction","plugin-manifest","subagent-persona"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/adk-agent-builder/SKILL.md",".agents/skills/adk-agent-builder/references/advanced-patterns.md",".agents/skills/adk-agent-builder/references/best-practices.md",".agents/skills/adk-agent-builder/references/callbacks-and-plugins.md",".agents/skills/adk-agent-builder/references/dynamic-nodes.md",".agents/skills/adk-agent-builder/references/function-nodes.md",".agents/skills/adk-agent-builder/references/getting-started.md",".agents/skills/adk-agent-builder/references/human-in-the-loop.md",".agents/skills/adk-agent-builder/references/import-paths.md",".agents/skills/adk-agent-builder/references/llm-agent-nodes.md",".agents/skills/adk-agent-builder/references/multi-agent.md",".agents/skills/adk-agent-builder/references/parallel-and-fanout.md",".agents/skills/adk-agent-builder/references/routing-and-conditions.md",".agents/skills/adk-agent-builder/references/session-and-state.md",".agents/skills/adk-agent-builder/references/state-and-events.md",".agents/skills/adk-agent-builder/references/task-mode.md",".agents/skills/adk-agent-builder/references/testing.md",".agents/skills/adk-agent-builder/references/tool-catalog.md",".agents/skills/adk-architecture/SKILL.md",".agents/skills/adk-architecture/references/api-principles.md",".agents/skills/adk-architecture/references/architecture-checkpoint-resume.md",".agents/skills/adk-architecture/references/architecture-context.md",".agents/skills/adk-architecture/references/architecture-llm-context-orchestration.md",".agents/skills/adk-architecture/references/architecture-node-runner.md",".agents/skills/adk-architecture/references/architecture-observability.md",".agents/skills/adk-architecture/references/architecture-runner-roles.md",".agents/skills/adk-architecture/references/architecture-workflow-resumability.md",".agents/skills/adk-architecture/references/interface-agent.md",".agents/skills/adk-architecture/references/interface-base-agent.md",".agents/skills/adk-architecture/references/interface-base-node.md",".agents/skills/adk-architecture/references/interface-event.md",".agents/skills/adk-architecture/references/interface-runner.md",".agents/skills/adk-architecture/references/interface-workflow.md",".agents/skills/adk-debug/SKILL.md",".agents/skills/adk-debug/references/cli-run.md",".agents/skills/adk-debug/references/event-flow.md",".agents/skills/adk-debug/references/failure-modes.md",".agents/skills/adk-debug/references/logs-and-traces.md",".agents/skills/adk-debug/references/web-api.md",".agents/skills/adk-git/SKILL.md",".agents/skills/adk-review/SKILL.md",".agents/skills/adk-sample-creator/SKILL.md",".agents/skills/adk-sample-creator/references/readme-template.md",".agents/skills/adk-setup/SKILL.md",".agents/skills/adk-style/SKILL.md",".agents/skills/adk-style/references/async.md",".agents/skills/adk-style/references/documentation.md",".agents/skills/adk-style/references/file-organization.md",".agents/skills/adk-style/references/formatting.md",".agents/skills/adk-style/references/imports.md",".agents/skills/adk-style/references/logging.md",".agents/skills/adk-style/references/pydantic.md",".agents/skills/adk-style/references/testing.md",".agents/skills/adk-style/references/typing.md",".agents/skills/adk-style/references/visibility.md",".agents/skills/adk-unit-design/SKILL.md",".agents/skills/adk-unit-design/references/design-template.md",".agents/skills/adk-unit-guide/SKILL.md",".agents/skills/adk-unit-guide/references/guide-template.md",".agents/skills/adk-verify-snippets/SKILL.md","AGENTS.md","contributing/samples/environment_and_skills/e2b_env_skill_toolset/skills/calc-skill/SKILL.md","contributing/samples/environment_and_skills/e2b_env_skill_toolset/skills/text-skill/SKILL.md","contributing/samples/environment_and_skills/local_env_skill_toolset/skills/calc-skill/SKILL.md","contributing/samples/environment_and_skills/local_env_skill_toolset/skills/text-skill/SKILL.md","contributing/samples/environment_and_skills/local_environment_skill/skills/weather-skill/SKILL.md","contributing/samples/environment_and_skills/local_environment_skill/skills/weather-skill/references/weather_info.md","contributing/samples/environment_and_skills/skills/README.md","contributing/samples/environment_and_skills/skills/skills/weather-skill/SKILL.md","contributing/samples/environment_and_skills/skills/skills/weather-skill/references/weather_info.md","contributing/samples/environment_and_skills/skills/tests/current_humidity.json","contributing/samples/environment_and_skills/skills/tests/current_weather.json","contributing/samples/environment_and_skills/skills/tests/support_hours.json","contributing/samples/environment_and_skills/skills/tests/wind_speed.json","contributing/samples/environment_and_skills/skills_agent/skills/weather-skill/SKILL.md","contributing/samples/environment_and_skills/skills_agent/skills/weather-skill/references/weather_info.md","contributing/samples/environment_and_skills/skills_inject_state/skills/code-review-skill/SKILL.md","contributing/samples/plugins/plugin_basic/README.md","contributing/samples/plugins/plugin_reflect_tool_retry/README.md","contributing/samples/tools/built_in_multi_tools/README.md","contributing/samples/tools/function_tools/README.md","contributing/samples/tools/long_running_functions/README.md","contributing/samples/tools/model_consult/README.md","contributing/samples/tools/output_schema_with_tools/README.md","contributing/samples/tools/parallel_functions/README.md","contributing/samples/tools/pydantic_argument/README.md","docs/guides/agents/base_agent/index.md","docs/guides/agents/config/index.md","docs/guides/agents/context/index.md","docs/guides/agents/invocation_context/index.md","docs/guides/agents/llm_agent/index.md","docs/guides/agents/llm_agent/single_turn.md","docs/guides/agents/llm_agent/task.md","docs/guides/agents/managed_agent/index.md","docs/guides/agents/remote_a2a_agent/task.md","docs/guides/agents/run_config/index.md","docs/guides/live/tools/index.md","docs/guides/plugins/reflect_retry_model_plugin/index.md","docs/guides/plugins/reflect_retry_tool_plugin/index.md","docs/guides/plugins/tool_call_integrity_plugin/index.md","docs/guides/skills/skill/index.md","docs/guides/skills/skill_registry/index.md","docs/guides/tools/agent_tool/index.md","docs/guides/tools/base_tool/index.md","docs/guides/tools/base_toolset/index.md","docs/guides/tools/function_tool/index.md","docs/guides/tools/long_running_tool/index.md","docs/guides/tools/mcp_tool/agent_to_mcp/index.md","docs/guides/tools/mcp_tool/mcp_toolset/index.md","docs/guides/tools/model_consult/force_escalation_plugin/index.md","docs/guides/tools/model_consult/model_consult_tool/index.md","docs/guides/tools/model_consult/observability_plugin/index.md","docs/guides/tools/node_tool/index.md","docs/guides/tools/skill_toolset/index.md","docs/guides/tools/tool_args_matcher/tool_args_matcher/index.md","docs/guides/tools/tool_confirmation/index.md","docs/guides/tools/tool_context/index.md","llms-full.txt","llms.txt","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/SKILL.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_agg.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_causal_effect.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_classify.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_detect_anomalies.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_evaluate.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_forecast.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate_embedding.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate_table.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_if.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_key_drivers.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_score.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_search.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_similarity.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_contribution_analysis.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_correlation.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_detect_change_points.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_seasonality.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_trend.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/remote_models.md","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/vector_search.md","src/google/adk/integrations/bigquery/skills/bigquery-graph/SKILL.md","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/best_practices.md","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/ddl_reference.md","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/feature_parity.md","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/graph_schema_ddl_advisor.md","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/semantic_queries.md","src/google/adk/skills/README.md"],"skills":{".agents/skills/adk-agent-builder/SKILL.md":"---\nname: adk-agent-builder\ndescription: >-\n  Builds ADK (Agent Development Kit) Python agents: LLM agents with tools,\n  graph workflows of function and agent nodes, conditional routing, fan-out and\n  join, schema-validated delegation between agents, human-in-the-loop pauses,\n  and pytest coverage for all of it. Use when asked to create an agent or a\n  workflow, add a tool to one, branch or loop between nodes, run steps in\n  parallel, pause for user approval, or test an agent. Don't use for explaining\n  how ADK works internally or designing its core components (use\n  `adk-architecture`), for an agent that already runs but misbehaves (use\n  `adk-debug`), for authoring a sample under `contributing/` (use\n  `adk-sample-creator`), or for naming, typing, and formatting conventions (use\n  `adk-style`).\n---\n\n# ADK Agent Builder\n\nRead only the reference that matches the task. Loading the whole tree costs\ncontext and buries the part that matters.\n\nEvery API below was checked against `google-adk` 2.6.2. If a symbol is missing\nat runtime, read the source under `src/google/adk/` rather than guessing a\nneighbouring name.\n\n## Start here\n\n| Task | Reference |\n|---|---|\n| First agent, environment, `adk` CLI | [getting-started.md](references/getting-started.md) |\n| Which import path is the canonical one | [import-paths.md](references/import-paths.md) |\n| The rules that cause most runtime failures | [best-practices.md](references/best-practices.md) |\n\n## Building blocks\n\n- [tool-catalog.md](references/tool-catalog.md) — function tools, MCP, OpenAPI,\n  Google API toolsets, built-in tools, custom `BaseTool` and `BaseToolset`.\n- [function-nodes.md](references/function-nodes.md) — plain functions as nodes:\n  parameter resolution, generators, `node_input` typing rules.\n- [llm-agent-nodes.md](references/llm-agent-nodes.md) — an `LlmAgent` used as a\n  workflow node: output types, instruction templates, `output_schema`,\n  auto-wrapping behavior.\n- [task-mode.md](references/task-mode.md) — `mode='task'` and\n  `mode='single_turn'` delegation with schema-validated input and output.\n\n## Graph orchestration\n\n- [routing-and-conditions.md](references/routing-and-conditions.md) — routed\n  edges, dict routing maps, default routes, self-loops, revision loops.\n- [parallel-and-fanout.md](references/parallel-and-fanout.md) — fan-out edges,\n  `JoinNode` fan-in, `parallel_worker=True` list processing.\n- [dynamic-nodes.md](references/dynamic-nodes.md) — scheduling nodes at runtime\n  with `ctx.run_node()` and imperative workflow construction.\n- [human-in-the-loop.md](references/human-in-the-loop.md) — `RequestInput`,\n  resume behavior, resumable vs replayed sessions.\n- [advanced-patterns.md](references/advanced-patterns.md) — nested workflows,\n  retries, custom `BaseNode` subclasses, graph validation rules.\n- [multi-agent.md](references/multi-agent.md) — chat-transfer hierarchies, and\n  the deprecated `SequentialAgent` / `LoopAgent` / `ParallelAgent` shells that\n  `Workflow` replaces.\n\n## Runtime and verification\n\n- [state-and-events.md](references/state-and-events.md) — the `Context` object,\n  `Event` fields, and how state flows between nodes.\n- [session-and-state.md](references/session-and-state.md) — session services,\n  artifacts, memory, and state key scoping.\n- [callbacks-and-plugins.md](references/callbacks-and-plugins.md) — the six\n  agent callbacks and app-level plugins.\n- [testing.md](references/testing.md) — `pytest` with `InMemoryRunner`, faking a\n  model, asserting on node output.\n",".agents/skills/adk-agent-builder/references/advanced-patterns.md":"# Advanced Workflow Patterns\n\nNested workflows, retries, timeouts, custom node classes, and the graph\nvalidation rules that reject a malformed graph at construction time.\n\n```python\nfrom google.adk import Context, Event, Workflow\nfrom google.adk.workflow import BaseNode, Edge, FunctionNode, RetryConfig, START\n```\n\n## Nested workflows\n\nA `Workflow` is both an agent and a node, so one can sit inside another. The\ninner workflow takes the predecessor's output as its `START` input, and its\nterminal output flows on to the next node outside.\n\n```python\ninner = Workflow(name='inner_pipeline', edges=[('START', step_a, step_b)])\n\nouter = Workflow(\n    name='outer_pipeline',\n    edges=[('START', pre_process, inner, post_process)],\n)\n```\n\n## Retries\n\nEvery `RetryConfig` field defaults to `None`, which means \"use the built-in\nfallback\" rather than \"disabled\":\n\n| Field | Fallback | Meaning |\n|---|---|---|\n| `max_attempts` | 5 | Total attempts including the first; 0 or 1 disables retrying |\n| `initial_delay` | 1.0 | Seconds before the first retry |\n| `max_delay` | 60.0 | Ceiling on the computed delay |\n| `backoff_factor` | 2.0 | Multiplier applied per attempt |\n| `jitter` | 1.0 | Randomness factor; 0.0 removes it |\n| `exceptions` | all | Exception classes or class-name strings to retry on |\n\n```python\napi_node = FunctionNode(\n    func=flaky_api_call,\n    name='api_call',\n    retry_config=RetryConfig(max_attempts=5, exceptions=[TimeoutError]),\n)\n```\n\nDelay for attempt *n* is\n`min(initial_delay * backoff_factor ** n, max_delay) * (1 + random(0, jitter))`.\n\nRead the current try from the context — it is 1 on the first attempt:\n\n```python\ndef my_node(ctx: Context, node_input: str) -> str:\n  if ctx.attempt_count > 1:\n    logger.warning('retry %d', ctx.attempt_count)\n  return 'result'\n```\n\n## Timeouts\n\n`timeout` is a per-node wall-clock limit in seconds. Exceeding it raises\n`NodeTimeoutError` (importable from `google.adk.workflow`), which the retry\nmachinery treats like any other exception.\n\n## Execution cancellation\n\nPass an `asyncio.Event` as `abort_signal` to `runner.run_async` to halt an\nin-flight workflow or agent cleanly. Setting the signal cancels active tasks,\nemits a terminal `Event(error_code='INVOCATION_ABORTED')`, seals any dangling\n`FunctionCall` events with a synthetic `FunctionResponse`, and still runs\n`after_run` plugin callbacks.\n\n```python\nimport asyncio\n\nabort_signal = asyncio.Event()\n\nasync for event in runner.run_async(\n    user_id='u1',\n    session_id='s1',\n    new_message=user_content,\n    abort_signal=abort_signal,\n):\n  if should_stop(event):\n    abort_signal.set()\n```\n\nInside a node or tool, `ctx.is_aborted` reflects whether the signal has been\nset.\n\n## Custom node classes\n\n`BaseNode` is a Pydantic model. Declare fields as fields, and override\n`_run_impl` — **not** `run`, which is `@final` and does the normalization of\nyielded values into events.\n\n```python\nfrom typing import Any, AsyncGenerator\n\nfrom typing_extensions import override\n\n\nclass BatchProcessorNode(BaseNode):\n  \"\"\"Processes a list of items in fixed-size batches.\"\"\"\n\n  batch_size: int = 10\n\n  @override\n  async def _run_impl(\n      self, *, ctx: Context, node_input: Any\n  ) -> AsyncGenerator[Any, None]:\n    items = node_input if isinstance(node_input, list) else [node_input]\n    results = []\n    for i in range(0, len(items), self.batch_size):\n      results.extend(await process_batch(items[i:i + self.batch_size]))\n    yield Event(output=results)\n\n\nbatcher = BatchProcessorNode(name='batch_processor', batch_size=25)\n```\n\n`_run_impl` may yield an `Event`, a `RequestInput`, a bare value (wrapped as\n`Event(output=...)`), or `None` (skipped). There is no `get_name()` to override\n— the node's name is the `name` field.\n\n### `BaseNode` fields\n\n| Field | Default | Purpose |\n|---|---|---|\n| `name` | required | Node identity within the graph; must be unique |\n| `description` | `''` | Human-readable label |\n| `rerun_on_resume` | `False` | Re-run after an interrupt instead of taking the answer as output |\n| `wait_for_output` | `False` | Finishing without an output event leaves the node WAITING, not COMPLETED |\n| `retry_config` | `None` | Retry policy |\n| `timeout` | `None` | Seconds before `NodeTimeoutError` |\n| `input_schema` | `None` | Validates and coerces `node_input` |\n| `output_schema` | `None` | Validates and coerces `event.output` |\n| `state_schema` | `None` | Validates `ctx.state` writes; `app:`, `user:`, `temp:` keys bypass it |\n\n### `wait_for_output`\n\nWith `wait_for_output=True`, a node that completes without emitting an output\nevent moves to WAITING rather than COMPLETED, and no downstream node fires. An\nupstream predecessor can trigger it again later — useful for a node that\naccumulates across several triggers before producing one answer.\n\n```python\nclass CollectorNode(BaseNode):\n  wait_for_output: bool = True\n\n  @override\n  async def _run_impl(self, *, ctx, node_input):\n    collected = ctx.state.get('collected', []) + [node_input]\n    yield Event(state={'collected': collected})\n    if len(collected) >= 3:\n      yield Event(output=collected)  # now COMPLETED, downstream fires\n```\n\n`JoinNode` reaches a similar result by a different mechanism — it sets\n`_requires_all_predecessors`, so the orchestrator holds it until every\npredecessor has run and then hands it all their outputs at once.\n\n## Wrapping a tool as a node\n\n`_ToolNode` is private and keyword-only. Its input must be a dict of tool\narguments, or `None`.\n\n```python\nfrom google.adk.tools import FunctionTool\nfrom google.adk.workflow._tool_node import _ToolNode\n\n\ndef search(query: str) -> str:\n  \"\"\"Search for information.\"\"\"\n  return f'Results for: {query}'\n\n\ntool_node = _ToolNode(tool=FunctionTool(search), name='search_node')\n\nagent = Workflow(\n    name='with_tool',\n    edges=[('START', prepare_query, tool_node, process_results)],\n)\n```\n\n## Graph validation\n\n`Workflow` validates the graph when it is constructed, in this order. Each check\nraises `ValueError` naming the offending node or edge.\n\n1. No duplicate node names.\n2. A `START` node exists.\n3. No edge leaving `START` carries a route.\n4. Every node is reachable from `START`, and `START` has no incoming edges.\n5. No two edges share both a source and a target — routes are not part of edge\n   identity.\n6.  A `route` list on one edge never mixes `__DEFAULT__` with named routes.\n7. No unconditional cycle — a cycle needs at least one routed edge.\n8. Where a source declares `output_schema` and its target declares\n   `input_schema`, the two must be the same schema.\n9. No edge into a `mode='chat'` `LlmAgent` from anything but `START`, because a\n   chat agent reads conversation history rather than a node input.\n\nNodes with no outgoing edges are the graph's terminals; their outputs become the\nworkflow's own output.\n\n## Ways to declare edges\n\n```python\nedges = [\n    ('START', node_a),                 # simple\n    (node_a, node_b, 'route'),         # routed\n    (node_a, (node_b, node_c)),        # fan-out\n    ((node_b, node_c), join_node),     # fan-in\n    ('START', node_a, node_b, node_c), # chain of three edges\n    (classifier, {'ok': handler_a, 'err': handler_b}),  # routing map\n]\n```\n\n`Edge` objects are the explicit form. It is a Pydantic model, so its fields are\nkeyword-only:\n\n```python\nedges = [\n    Edge(from_node=START, to_node=node_a),\n    Edge(from_node=node_a, to_node=node_b, route='success'),\n]\n```\n\nTo build the graph yourself, pass `graph=` instead of `edges=`:\n\n```python\nfrom google.adk.workflow._graph import Graph\n\ngraph = Graph.from_edge_items([('START', node_a), (node_a, node_b)])\nagent = Workflow(name='my_workflow', graph=graph)\n```\n\n## Where the code lives\n\n| Component | File |\n|---|---|\n| `Workflow` | `src/google/adk/workflow/_workflow.py` |\n| `Graph`, `Edge`, `DEFAULT_ROUTE` | `src/google/adk/workflow/_graph.py` |\n| graph validation rules | `src/google/adk/workflow/utils/_graph_validation.py` |\n| `BaseNode`, `START` | `src/google/adk/workflow/_base_node.py` |\n| `FunctionNode` | `src/google/adk/workflow/_function_node.py` |\n| `@node`, `Node` | `src/google/adk/workflow/_node.py` |\n| `JoinNode` | `src/google/adk/workflow/_join_node.py` |\n| `_ParallelWorker` | `src/google/adk/workflow/_parallel_worker.py` |\n| `_ToolNode` | `src/google/adk/workflow/_tool_node.py` |\n| `RetryConfig` | `src/google/adk/workflow/_retry_config.py` |\n| running an `LlmAgent` as a node | `src/google/adk/workflow/_llm_agent_wrapper.py` |\n| dynamic node scheduling | `src/google/adk/workflow/_dynamic_node_scheduler.py` |\n| `Context` | `src/google/adk/agents/context.py` |\n| `Event` | `src/google/adk/events/event.py` |\n| `RequestInput` | `src/google/adk/events/request_input.py` |\n",".agents/skills/adk-agent-builder/references/best-practices.md":"# ADK Workflow Rules That Bite\n\nThe failure modes that account for most broken ADK workflows. Each one is a\nsilent or confusing failure, not a clear error — that is why they are collected\nhere rather than left to be discovered.\n\n## Type everything with Pydantic models\n\nUse a `BaseModel` for node inputs, node outputs, LLM `output_schema`,\n`RequestInput.response_schema`, and structured state values. `dict[str, Any]`\ngives up validation, IDE completion, and the automatic dict-to-model conversion\nthat `FunctionNode` performs from type hints.\n\n```python\n# Loses validation and downstream typing\ndef lookup_flights(node_input: dict[str, Any]) -> dict[str, Any]:\n  return {'flight_cost': 500, 'details': 'Economy'}\n\n\n# Validated on the way in and on the way out\nclass FlightInfo(BaseModel):\n  flight_cost: int\n  details: str\n\n\ndef lookup_flights(node_input: Itinerary) -> FlightInfo:\n  return FlightInfo(flight_cost=500, details='Economy')\n```\n\n## `event.output` is plumbing; `event.content` is the UI\n\nThe web UI renders `event.content` only. A node that produces something a human\nshould read must emit it as a message, in addition to the output the next node\nconsumes.\n\n```python\ndef final_output(node_input: str):\n  yield Event(message=node_input)  # rendered in the web UI\n  yield Event(output=node_input)   # passed to the downstream node\n```\n\n`Event(message=...)` is a constructor convenience that writes `event.content`.\nStream partial text with `Event(message='chunk', partial=True)`. LLM agents emit\ntheir content events automatically; function nodes do not.\n\n## Write state through `Event(state=...)`, not `ctx.state[key] = ...`\n\nState passed to the `Event` constructor lands in `event.actions.state_delta`, so\nit is part of event history and survives the replay that non-resumable\nhuman-in-the-loop performs. A direct `ctx.state` mutation is a side effect that\nreplay does not reproduce.\n\n```python\n# Recorded in event history\ndef save(node_input: str):\n  return Event(output=node_input, state={'user_request': node_input})\n\n\n# Lost on replay\ndef save(ctx: Context, node_input: str) -> str:\n  ctx.state['user_request'] = node_input\n  return node_input\n```\n\nReading is always `ctx.state[...]`.\n\n## At most one output event per node execution\n\nA node may yield many events, but only one of them may carry `output`. Two\noutput events are merged into a list, which silently changes the downstream\nnode's `node_input` type from `str` to `list[str]`. The same limit applies to\n`route`, except there a second routed event raises `ValueError`.\n\n```python\ndef my_node(node_input: str):\n  yield Event(message='Processing...')   # display only\n  yield Event(state={'status': 'done'})  # state only\n  yield Event(output='final result')     # the one output\n```\n\nThis holds for function nodes, LLM agent nodes, and nested workflows alike.\n\n## A function either yields or returns — never both\n\nPython turns any function containing `yield` into a generator and discards its\n`return value`. Mixing the two loses the output with no error.\n\n```python\n# Generator: every event is yielded\ndef my_node(node_input: str):\n  yield Event(state={'key': 'value'})\n  yield Event(output='result')\n\n\n# Plain function: one return, which may be an Event or a bare value\ndef my_node(node_input: str):\n  return Event(output='result', state={'key': 'value'})\n\n\n# Broken: the return is silently ignored\ndef my_node(node_input: str):\n  yield Event(state={'key': 'value'})\n  return Event(output='result')\n```\n\n## `{var}` in an instruction reads state, never `node_input`\n\nInstruction placeholders resolve against `ctx.state` only. `node_input` is\ndelivered to the model as the user message, so `{node_input}` is just a missing\nstate key and raises `KeyError` at call time.\n\n```python\n# KeyError: 'node_input' is not a state key\nAgent(name='summarizer', instruction='Summarize this: {node_input}')\n\n# The predecessor's output is already the user message\nAgent(name='summarizer', instruction='Summarize the text in one sentence.')\n\n# Anything else the instruction needs must be in state first\nAgent(name='writer', instruction='Write about \"{topic}\". Feedback: {feedback?}')\n```\n\n`{var?}` substitutes an empty string when the key is absent; `{var}` raises.\n\n## A chat-mode agent can only follow `START`\n\nGraph validation rejects an edge into an `LlmAgent` with `mode='chat'` from any\nnode other than `START`, because a chat agent reads conversational history\nrather than a node input. Set `mode='single_turn'` (the default for an\nauto-wrapped agent) or `mode='task'` on agents used mid-graph.\n\n## Everything in `event.output` must be JSON-serializable\n\n- `FunctionNode` converts a returned `BaseModel` with `model_dump()`, so\n  returning a model is safe. A `types.Content` or any other non-serializable\n  object is not.\n- An LLM agent with `output_schema` validates then dumps, so\n  `ctx.state[output_key]` is a plain `dict`, never a model instance. Read it\n  with `data['field']`, or rebuild the model with `MyModel(**data)`.\n- This bites hardest at a `JoinNode`, which parks partial inputs in session\n  state while waiting. If a predecessor is an LLM agent without `output_schema`,\n  the parked value is a `types.Content` and `DatabaseSessionService` raises\n  `TypeError` on write.\n\n## Give every loop iteration its own `interrupt_id`\n\nA node that yields `RequestInput` inside a loop must vary the `interrupt_id` per\niteration. Reusing one id makes event-based state reconstruction match an\nearlier iteration's answer to the current interrupt, which restarts the loop\nforever.\n\n```python\nreview_count = ctx.state.get('review_count', 0)\nyield RequestInput(\n    interrupt_id=f'review_{review_count}',\n    message='Approve?',\n)\n```\n\nIncrement the counter through `Event(state={'review_count': review_count + 1})`\nwhen the response arrives.\n",".agents/skills/adk-agent-builder/references/callbacks-and-plugins.md":"# Callbacks and Plugins\n\nCallbacks hook one agent; plugins hook every agent under an `App`. Both follow\nthe same contract: **return `None` to let the normal thing happen, return a\nvalue to replace it.**\n\n```python\nfrom google.adk.agents.callback_context import CallbackContext\nfrom google.adk.models.llm_request import LlmRequest\nfrom google.adk.models.llm_response import LlmResponse\nfrom google.adk.tools import BaseTool, ToolContext\n```\n\n`CallbackContext` and `ToolContext` are both aliases for `Context`.\n\n## The eight agent callbacks\n\n| Field | Arguments | Return to override |\n|---|---|---|\n| `before_agent_callback` | `(CallbackContext)` | `types.Content` — skips the agent entirely |\n| `after_agent_callback` | `(CallbackContext)` | `types.Content` — replaces the agent's output |\n| `before_model_callback` | `(CallbackContext, LlmRequest)` | `LlmResponse` — skips the model call |\n| `after_model_callback` | `(CallbackContext, LlmResponse)` | `LlmResponse` — replaces the response |\n| `on_model_error_callback` | `(CallbackContext, LlmRequest, Exception)` | `LlmResponse` — suppresses the error |\n| `before_tool_callback` | `(BaseTool, dict, ToolContext)` | `dict` — skips the tool call |\n| `after_tool_callback` | `(BaseTool, dict, ToolContext, dict)` | `dict` — replaces the tool result |\n| `on_tool_error_callback` | `(BaseTool, dict, ToolContext, Exception)` | `dict` — suppresses the error |\n\nEvery one may be sync or async, and every one accepts either a single callable\nor a list. A list runs in order and stops at the first callback that returns\nsomething other than `None`.\n\n## Examples\n\nBlocking a request before it reaches the model:\n\n```python\ndef guard(\n    callback_context: CallbackContext, llm_request: LlmRequest\n) -> LlmResponse | None:\n  for content in llm_request.contents:\n    for part in content.parts or []:\n      if part.text and 'unsafe' in part.text:\n        return LlmResponse(content=types.ModelContent('I cannot process that.'))\n  return None\n\n\nagent = LlmAgent(\n    name='guarded', model='gemini-3.5-flash', before_model_callback=guard\n)\n```\n\nObserving without changing anything — note the explicit `return None`:\n\n```python\ndef log_response(\n    callback_context: CallbackContext, llm_response: LlmResponse\n) -> LlmResponse | None:\n  logger.info('model said: %s', llm_response.content)\n  return None\n```\n\nAuditing and repairing tool calls:\n\n```python\ndef audit(tool: BaseTool, args: dict, tool_context: ToolContext) -> dict | None:\n  logger.info('calling %s with %s', tool.name, args)\n  return None\n\n\ndef repair(\n    tool: BaseTool, args: dict, tool_context: ToolContext, tool_response: dict\n) -> dict | None:\n  if 'error' in tool_response:\n    return {'result': 'Tool execution failed, please try again.'}\n  return None\n\n\nagent = LlmAgent(\n    name='audited',\n    model='gemini-3.5-flash',\n    tools=[my_tool],\n    before_tool_callback=audit,\n    after_tool_callback=repair,\n)\n```\n\nDegrading gracefully on failure:\n\n```python\ndef handle_model_error(\n    callback_context: CallbackContext,\n    llm_request: LlmRequest,\n    error: Exception,\n) -> LlmResponse | None:\n  return LlmResponse(content=types.ModelContent('Service unavailable.'))\n\n\nagent = LlmAgent(\n    name='resilient',\n    model='gemini-3.5-flash',\n    on_model_error_callback=handle_model_error,\n)\n```\n\n## Plugins\n\nA plugin is the same set of hooks applied to every agent, tool, and model call\nin an app, plus a few that only make sense at app scope. All hooks are async and\nkeyword-only.\n\n```python\nfrom google.adk.plugins.base_plugin import BasePlugin\n\n\nclass MyPlugin(BasePlugin):\n\n  def __init__(self):\n    super().__init__(name='my_plugin')\n\n  async def before_agent_callback(self, *, agent, callback_context):\n    return None\n\n  async def before_model_callback(self, *, callback_context, llm_request):\n    return None\n```\n\nBeyond the eight agent-level hooks, `BasePlugin` adds\n`on_user_message_callback`, `before_run_callback`, `on_event_callback`,\n`after_run_callback`, `on_agent_error_callback`, and `on_run_error_callback`.\n\nRegister plugins on the `App`:\n\n```python\nfrom google.adk.apps import App\nfrom google.adk.plugins.context_filter_plugin import ContextFilterPlugin\n\napp = App(\n    name='my_app',\n    root_agent=root_agent,\n    plugins=[ContextFilterPlugin(num_invocations_to_keep=3)],\n)\n```\n\n## Built-in plugins\n\n| Plugin | Module under `google.adk.plugins` | Purpose |\n|---|---|---|\n| `ContextFilterPlugin` | `context_filter_plugin` | Trims history to the last N invocations |\n| `SaveFilesAsArtifactsPlugin` | `save_files_as_artifacts_plugin` | Stores file outputs as session artifacts |\n| `GlobalInstructionPlugin` | `global_instruction_plugin` | Prepends an instruction to every agent |\n| `LoggingPlugin` | `logging_plugin` | Logs the invocation lifecycle |\n| `DebugLoggingPlugin` | `debug_logging_plugin` | Verbose request and response logging |\n| `ReflectAndRetryToolPlugin` | `reflect_retry_tool_plugin` | Retries a failed tool call after letting the model reflect |\n| `MultimodalToolResultsPlugin` | `multimodal_tool_results_plugin` | Routes non-text tool results into content |\n| `AutoTracingPlugin` | `auto_tracing_plugin` | Emits tracing spans automatically |\n| `BigQueryAgentAnalyticsPlugin` | `bigquery_agent_analytics_plugin` | Exports invocation analytics to BigQuery |\n",".agents/skills/adk-agent-builder/references/dynamic-nodes.md":"# Dynamic Node Scheduling\n\n`await ctx.run_node(...)` runs another node from inside a node and returns its\noutput. It turns graph control flow into ordinary Python: loops, conditionals,\nand early exits, written as loops, conditionals, and early exits.\n\n```python\nfrom google.adk import Agent, Context, Event, Workflow\nfrom google.adk.workflow import FunctionNode, node\n```\n\n## Example\n\n```python\nclass Feedback(BaseModel):\n  grade: str\n\n\ngenerate_headline = Agent(\n    name='generate_headline',\n    instruction='Write a headline about the topic \"{topic}\".',\n)\n\nevaluate_headline = Agent(\n    name='evaluate_headline',\n    mode='single_turn',\n    instruction='Grade whether the headline is tech-related.',\n    output_schema=Feedback,\n)\n\n\n@node(rerun_on_resume=True)\nasync def orchestrate(ctx: Context, node_input: str) -> str:\n  yield Event(state={'topic': node_input})\n  while True:\n    headline = await ctx.run_node(generate_headline)\n    feedback = Feedback.model_validate(\n        await ctx.run_node(evaluate_headline, node_input=headline)\n    )\n    if feedback.grade == 'tech-related':\n      yield headline\n      break\n\n\nroot_agent = Workflow(name='root_agent', edges=[('START', orchestrate)])\n```\n\n## `ctx.run_node` arguments\n\n```python\nawait ctx.run_node(\n    node,                     # a function, Agent, BaseTool, or BaseNode\n    node_input=None,\n    *,\n    use_as_output=False,\n    run_id=None,\n    use_sub_branch=False,\n    override_branch=None,\n)\n```\n\n| Argument | Effect |\n|---|---|\n| `use_as_output` | The child's output becomes the parent's output; the parent's own output events are suppressed |\n| `run_id` | Names this execution instead of auto-numbering it |\n| `use_sub_branch` | Appends `node_name@run_id` to the branch, isolating events from sibling runs |\n| `override_branch` | Uses a specific branch instead of the parent's |\n\n## Rules the framework enforces\n\n**The calling node needs `rerun_on_resume=True`.** Calling `run_node` without it\nraises immediately. The reason is resumption: a dynamically scheduled child may\ninterrupt for user input, and the only way the parent can receive the answer is\nto be re-run from the top.\n\n**An explicit `run_id` must contain a non-digit.** Auto-generated ids are plain\nnumbers (`\"1\"`, `\"2\"`, ...), so an all-digit custom id would collide with one.\n`ValueError` names the offending id.\n\n**`use_as_output=True` at most once per parent execution.** A second call raises\n`Node {path} already has a use_as_output delegate.` (A `Workflow` calling\n`run_node` is exempt.)\n\n**`await` the call directly.** Wrapping it in `asyncio.create_task()` leaves the\nchild unsupervised: its errors are swallowed and it is not cancelled when the\nparent is interrupted.\n\n## Imperative workflows\n\nStandard Python replaces routed edges entirely:\n\n```python\nasync def orchestrator(ctx: Context, node_input: str):\n  res_a = await ctx.run_node(step_a, node_input=node_input)\n  if 'success' in res_a:\n    return await ctx.run_node(step_b, node_input=res_a)\n  return await ctx.run_node(step_c, node_input=res_a)\n```\n\n### Three traps in this style\n\n**A raw function's parameters bind from state, not from `node_input`.** Node\nparameter binding defaults to `'state'`, so a value passed as\n`run_node(fn, node_input=x)` reaches the function only through a parameter\nliterally named `node_input`.\n\n```python\ndef my_worker(node_input: str):  # this name, or the value never arrives\n  return f'Done: {node_input}'\n```\n\n**A child that itself calls `run_node` is a parent too**, so it also needs\n`rerun_on_resume=True`. Raw functions default to `False`, so wrap it:\n\n```python\ninner = FunctionNode(func=inner_orchestrator, rerun_on_resume=True)\n```\n\n**A generator cannot `return` a value.** In a node that uses `yield`, produce\nthe result with `yield Event(output=...)`; `return value` is a syntax error in\nan async generator and silently ignored in a sync one.\n",".agents/skills/adk-agent-builder/references/function-nodes.md":"# Function Nodes\n\nAny Python function can be a workflow node. Put the callable straight into\n`edges` and the framework wraps it in a `FunctionNode`.\n\n```python\nfrom google.adk import Context, Event\nfrom google.adk.workflow import FunctionNode, RetryConfig, node\n```\n\n## Parameter resolution\n\n`FunctionNode` inspects the signature and fills each parameter by name:\n\n| Parameter name | Bound to |\n|---|---|\n| `ctx` | the workflow `Context` |\n| `node_input` | the predecessor node's output |\n| anything else | `ctx.state[param_name]`, falling back to the default |\n\n```python\ndef with_context(ctx: Context, node_input: str) -> str:\n  return f'Session {ctx.session.id}: {node_input}'\n\n\ndef input_only(node_input: str) -> str:\n  return node_input.upper()\n\n\ndef from_state(node_input: str, user_name: str) -> str:\n  # user_name comes from ctx.state['user_name']\n  return f'{user_name}: {node_input}'\n\n\ndef no_params() -> str:\n  return 'hello'\n```\n\nPass `parameter_binding='node_input'` to `FunctionNode` or `@node` to bind every\nparameter from a `node_input` dict instead of from state. That mode also infers\n`input_schema` and `output_schema` from the signature, and is what the framework\nuses when a node is exposed as an agent's tool.\n\n## Return values\n\nA plain return is wrapped in `Event(output=...)`. Returning `None` emits no\nevent, so no downstream node fires.\n\n```python\ndef process(node_input: str) -> str:\n  return f'Processed: {node_input}'\n\n\nasync def fetch_data(node_input: str) -> dict:\n  return {'data': await some_api_call(node_input)}\n\n\ndef maybe_output(node_input: str) -> str | None:\n  if not node_input:\n    return None  # downstream stays idle\n  return f'Got: {node_input}'\n```\n\nGenerators may yield `Event` objects or bare values — a bare yield is wrapped\nthe same way a return is.\n\n```python\nasync def multi(ctx: Context):\n  yield Event(message='working...')\n  yield 'output value'  # becomes Event(output='output value')\n```\n\n## Input coercion\n\n`FunctionNode` runs each argument through a Pydantic `TypeAdapter` built from\nthe annotation, so the annotation is both documentation and a coercion rule:\n\n| Annotation | Incoming value | Result |\n|---|---|---|\n| a `BaseModel` subclass | `dict` | validated model instance |\n| `list[Model]`, `dict[K, Model]` | nested dicts | recursively converted |\n| `str` (including `str \\| None`) | `types.Content` | concatenated text of the text parts |\n| anything else | any | validated by `TypeAdapter`, `TypeError` on mismatch |\n\nThe `types.Content` to `str` rule drops non-text parts (inline data, file data,\nexecutable code) and logs a warning when it does.\n\n```python\nclass Order(BaseModel):\n  item: str\n  quantity: int\n\n\ndef process_order(node_input: Order) -> str:\n  # {'item': 'widget', 'quantity': 3} arrives as Order(item='widget', quantity=3)\n  return f'Order: {node_input.quantity}x {node_input.item}'\n```\n\nA union annotation (`list | dict`) accepts anything — the adapter is satisfied\nby any member, so wrong types reach the body. Use `isinstance` checks inside the\nfunction when a union is unavoidable.\n\n## What `node_input` will actually be\n\n| Predecessor | `node_input` |\n|---|---|\n| function returning `str` / `dict` | that value |\n| function returning `Event(output=X)` | `X` |\n| `LlmAgent` without `output_schema` | `str` — the model's concatenated text |\n| `LlmAgent` with `output_schema` | `dict` — the validated model, dumped |\n| `JoinNode` | `dict[str, Any]` keyed by predecessor node name |\n| a `parallel_worker=True` node | `list` of per-item results |\n| `START` without `input_schema` | `types.Content` (the user's message) |\n| `START` with `input_schema` | the parsed schema type |\n\n## Explicit `FunctionNode`\n\nConstruct one directly when you need to override its properties. Every argument\nis keyword-only, including `func`.\n\n```python\napi_node = FunctionNode(\n    func=flaky_api_call,\n    name='api_call',             # defaults to func.__name__\n    rerun_on_resume=True,        # re-run after a human-in-the-loop interrupt\n    retry_config=RetryConfig(max_attempts=3, initial_delay=1.0),\n    timeout=30.0,\n)\n```\n\n## The `@node` decorator\n\n`@node` is the same thing with less ceremony, and it also accepts an already-made\nnode, an agent, or a tool.\n\n```python\n@node\ndef plain(node_input: str) -> str:\n  return node_input\n\n\n@node(name='custom_name', rerun_on_resume=True)\nasync def renamed(node_input: str) -> str:\n  return node_input\n\n\n# Called as a function on an existing callable\nmy_node = node(some_func, name='renamed')\n\n# Fan a list out across parallel workers\nworker = node(some_func, parallel_worker=True)\n```\n\nAccepted keywords: `name`, `rerun_on_resume`, `retry_config`, `timeout`,\n`parallel_worker`, `max_parallel_workers`, `auth_config`,\n`require_confirmation`, `parameter_binding`.\n\n## Routing and state from a node\n\n```python\ndef classify(node_input: str):\n  route = 'urgent' if 'urgent' in node_input else 'normal'\n  return Event(output=node_input, route=route)\n\n\ndef update_counter(node_input: str):\n  return Event(output=node_input, state={'last_input': node_input})\n```\n\n## Requiring confirmation before a node runs\n\n`require_confirmation` pauses the workflow with an `adk_request_input`\ninterrupt (`wf_tool_confirmation:{node_path}`) before the function runs. It\naccepts a `bool` or a sync/async predicate callable receiving the function's\nbound arguments (plus `ctx` or `node_input` if declared). `@node` automatically\ndefaults `rerun_on_resume=True` when `require_confirmation` is set.\n\n```python\n@node(require_confirmation=True)\ndef delete_file(path: str) -> str:\n  return f'Deleted {path}'\n\n\n@node(require_confirmation=lambda amount: amount > 1000)\ndef transfer_funds(amount: int) -> str:\n  return f'Transferred {amount}'\n```\n\n## Requiring authentication before a node runs\n\n`auth_config` makes the framework request user credentials before the node's\nfirst execution. It requires `rerun_on_resume=True`, because the node runs again\nonce the credential arrives.\n\n```python\nsecured = FunctionNode(\n    func=call_private_api,\n    auth_config=my_auth_config,\n    rerun_on_resume=True,\n)\n```\n\nInside the node, read the credential with\n`AuthHandler(auth_config).get_auth_response(ctx.state)`\n(`google.adk.auth.auth_handler.AuthHandler`).\n",".agents/skills/adk-agent-builder/references/getting-started.md":"# Getting Started: Creating ADK Agents\n\nEnvironment, the agent directory convention, a first LLM agent, and the jump to\ngraph workflows.\n\n## CLI commands\n\n| Command | What it does |\n|---|---|\n| `adk create {agent_name}` | Scaffolds an agent directory |\n| `adk run {agent_dir}` | Runs the agent in the terminal |\n| `adk web {agent_dir}` | Dev server on `http://localhost:8000` (development only) |\n| `adk api_server {agent_dir}` | HTTP API for the agent |\n\n## 1. Environment\n\n```bash\nuv venv --python python3.11 .venv\nsource .venv/bin/activate\nuv pip install google-adk\n```\n\n`pip install google-adk` in a `python -m venv` works too. ADK requires Python\n3.10 or newer.\n\n## 2. API keys\n\nPut a `.env` file in the **agent directory**, not its parent — the loader looks\nbeside `agent.py`. Do not commit it.\n\nGoogle AI Studio (get a key at https://aistudio.google.com/app/apikey):\n\n```bash\nGOOGLE_GENAI_USE_ENTERPRISE=FALSE\nGOOGLE_API_KEY=YOUR_API_KEY\n```\n\nVertex AI, after `gcloud auth application-default login`:\n\n```bash\nGOOGLE_GENAI_USE_ENTERPRISE=TRUE\nGOOGLE_CLOUD_PROJECT=your-project-id\nGOOGLE_CLOUD_LOCATION=us-central1\n```\n\nVertex AI express mode swaps the project/location pair for an API key:\n\n```bash\nGOOGLE_GENAI_USE_ENTERPRISE=TRUE\nGOOGLE_API_KEY=YOUR_EXPRESS_MODE_KEY\n```\n\n`GOOGLE_GENAI_USE_VERTEXAI` is the old name for the same switch. It still works\nbut emits a `DeprecationWarning`; `GOOGLE_GENAI_USE_ENTERPRISE` wins when both\nare set.\n\n## 3. Directory layout\n\nThe CLI discovers agents by convention:\n\n```text\nmy_agent/\n├── __init__.py    # from . import agent\n├── agent.py       # defines root_agent (and optionally app)\n└── .env\n```\n\n`__init__.py` must re-export the module, or the agent will not appear in\n`adk web`:\n\n```python\nfrom . import agent\n```\n\n## 4. A basic LLM agent\n\n`LlmAgent` (aliased as `Agent`) binds a model, an instruction, and tools.\n\n```python\nfrom google.adk import Agent\n\n\ndef get_weather(city: str) -> dict:\n  \"\"\"Returns the current weather for a specified city.\"\"\"\n  return {\n      'status': 'success',\n      'city': city,\n      'weather': 'sunny',\n      'temperature': '72F',\n  }\n\n\nroot_agent = Agent(\n    model='gemini-3.5-flash',\n    name='root_agent',\n    description='An assistant that reports the weather.',\n    instruction=(\n        'You are a helpful assistant. Use get_weather to look up the'\n        ' weather in any city. Be concise.'\n    ),\n    tools=[get_weather],\n)\n```\n\n| Field | Purpose |\n|---|---|\n| `model` | Model id, e.g. `'gemini-3.5-flash'`, `'gemini-3.1-pro-preview'` |\n| `instruction` | System prompt; `{var}` placeholders resolve from session state |\n| `tools` | Python callables; name, docstring, and type hints become the tool schema |\n| `description` | How a parent agent decides to route to this one |\n| `output_key` | Session-state key to store the agent's final text under |\n\nA tool function needs a docstring and type hints on every parameter — the LLM\nsees only those. Return a `dict` or a `str`.\n\n## 5. Running it programmatically\n\n```python\nimport asyncio\n\nfrom google.adk.runners import InMemoryRunner\nfrom google.genai import types\n\nfrom my_agent import agent\n\n\nasync def main():\n  runner = InMemoryRunner(app_name='my_app', agent=agent.root_agent)\n  session = await runner.session_service.create_session(\n      app_name='my_app', user_id='user1'\n  )\n  message = types.Content(\n      role='user',\n      parts=[types.Part.from_text(text=\"What's the weather in Paris?\")],\n  )\n  async for event in runner.run_async(\n      user_id='user1', session_id=session.id, new_message=message\n  ):\n    if event.content and event.content.parts:\n      text = event.content.parts[0].text\n      if text:\n        print(f'{event.author}: {text}')\n\n\nasyncio.run(main())\n```\n\n## 6. From one agent to a workflow\n\nA `Workflow` replaces \"one LLM decides everything\" with an explicit graph. The\nsmallest one has a single edge from `START`:\n\n```python\nfrom google.adk import Workflow\n\n\ndef greet(node_input: str) -> str:\n  return f'Hello! You said: {node_input}'\n\n\nroot_agent = Workflow(name='my_workflow', edges=[('START', greet)])\n```\n\n### Sequential pipeline of LLM agents\n\n> **Deprecated.** `SequentialAgent` is deprecated in favour of `Workflow`.\n> Prefer the explicit edge list above for new code; this form is documented\n> because existing agents still use it.\n\n`SequentialAgent` generates `START -> a -> b -> c` for you. Each agent's\n`output_key` publishes to session state, and the next agent reads it through an\ninstruction placeholder.\n\n```python\nfrom google.adk.agents import LlmAgent, SequentialAgent\n\nwriter = LlmAgent(\n    name='CodeWriterAgent',\n    model='gemini-3.5-flash',\n    instruction=(\n        'Write Python code that fulfills the user request. Output only the'\n        ' code block.'\n    ),\n    description='Writes initial Python code from a specification.',\n    output_key='generated_code',\n)\n\nreviewer = LlmAgent(\n    name='CodeReviewerAgent',\n    model='gemini-3.5-flash',\n    instruction=(\n        'Review this code and reply with a bulleted list of issues, or \"No'\n        ' major issues found.\" if it is clean:\\n\\n{generated_code}'\n    ),\n    description='Reviews code and provides feedback.',\n    output_key='review_comments',\n)\n\nrefactorer = LlmAgent(\n    name='CodeRefactorerAgent',\n    model='gemini-3.5-flash',\n    instruction=(\n        'Improve this code:\\n\\n{generated_code}\\n\\nAddressing these'\n        ' comments:\\n\\n{review_comments}\\n\\nOutput only the final code'\n        ' block.'\n    ),\n    description='Refactors code based on review comments.',\n    output_key='refactored_code',\n)\n\nroot_agent = SequentialAgent(\n    name='CodePipelineAgent',\n    sub_agents=[writer, reviewer, refactorer],\n    description='Writes, reviews, and refactors Python code.',\n)\n```\n\n### Graph with conditional routing\n\nA node returns `Event(route=...)` and the edge dict picks the branch.\n\n```python\nfrom google.adk import Event, Workflow\n\n\ndef parse_input(node_input: str) -> dict:\n  return {'text': node_input, 'word_count': len(node_input.split())}\n\n\ndef classify(node_input: dict):\n  route = 'long' if node_input['word_count'] > 10 else 'short'\n  return Event(output=node_input, route=route)\n\n\ndef handle_short(node_input: dict) -> str:\n  return f\"Short ({node_input['word_count']} words): {node_input['text']}\"\n\n\ndef handle_long(node_input: dict) -> str:\n  return f\"Long ({node_input['word_count']} words): {node_input['text'][:50]}...\"\n\n\nroot_agent = Workflow(\n    name='classifier_workflow',\n    input_schema=str,\n    edges=[\n        ('START', parse_input, classify),\n        (classify, {'short': handle_short, 'long': handle_long}),\n    ],\n)\n```\n\n### Parallel list processing\n\n`parallel_worker=True` makes a node run once per item of a list input and\nreturn a list of results.\n\n```python\nfrom google.adk import Workflow\nfrom google.adk.workflow import node\n\n\ndef split_input(node_input: str) -> list:\n  return [item.strip() for item in node_input.split(',')]\n\n\n@node(parallel_worker=True)\ndef process_item(node_input: str) -> dict:\n  return {'item': node_input, 'upper': node_input.upper()}\n\n\ndef format_results(node_input: list) -> str:\n  return '\\n'.join(f\"- {r['item']} -> {r['upper']}\" for r in node_input)\n\n\nroot_agent = Workflow(\n    name='parallel_processor',\n    input_schema=str,\n    edges=[('START', split_input, process_item, format_results)],\n)\n```\n\n### Mixing function nodes and an LLM agent\n\n```python\nfrom google.adk import Workflow\nfrom google.adk.agents import LlmAgent\n\n\ndef get_weather(city: str) -> dict:\n  \"\"\"Get the current weather for a city.\"\"\"\n  return {'city': city, 'temp': '72F', 'condition': 'sunny'}\n\n\ndef extract_city(node_input: str) -> str:\n  return node_input.strip()\n\n\nweather_agent = LlmAgent(\n    name='weather_reporter',\n    model='gemini-3.5-flash',\n    instruction='Use get_weather, then give a natural-language report.',\n    tools=[get_weather],\n)\n\n\ndef sign_off(node_input: str) -> str:\n  return f'{node_input}\\n\\nHave a great day!'\n\n\nroot_agent = Workflow(\n    name='weather_workflow',\n    input_schema=str,\n    edges=[('START', extract_city, weather_agent, sign_off)],\n)\n```\n\n## Troubleshooting\n\n| Symptom | Cause |\n|---|---|\n| `No module named 'google.adk'` | Virtual environment not activated, or `google-adk` not installed in it |\n| Agent missing from the `adk web` dropdown | `__init__.py` lacks `from . import agent`, or `agent.py` defines no `root_agent` |\n| API key errors | `.env` sits in the parent directory instead of the agent directory |\n| Model not found | Typo in the model id; non-Google models (Anthropic, LiteLLM) need extra dependencies |\n",".agents/skills/adk-agent-builder/references/human-in-the-loop.md":"# Human-in-the-Loop\n\nPause a workflow to ask the user something, then continue with their answer.\n\n```python\nfrom google.adk import Context, Event, Workflow\nfrom google.adk.apps import App, ResumabilityConfig\nfrom google.adk.events import RequestInput\nfrom google.adk.workflow import FunctionNode\n```\n\n## Asking\n\nYield or return a `RequestInput`. The node's output stream is normalized, so\neither works — a plain function can return one directly without becoming a\ngenerator.\n\n```python\nasync def approval_gate(ctx: Context, node_input: str):\n  yield RequestInput(message='Please approve this action:')\n\n\ndef evaluate_request(request: TimeOffRequest):\n  if request.days <= 1:\n    return TimeOffDecision(approved=True)  # no interrupt at all\n  return RequestInput(\n      interrupt_id='manager_approval',\n      message='Please review this time off request.',\n      payload=request,\n      response_schema=TimeOffDecision,\n  )\n```\n\nThe workflow emits an `adk_request_input` function call and stops. Responding to\nthat function call resumes it.\n\n| Field | Type | Notes |\n|---|---|---|\n| `interrupt_id` | `str` | Auto-generated UUID when omitted |\n| `message` | `str \\| None` | Shown to the user |\n| `payload` | `Any` | Arbitrary data carried along with the request |\n| `response_schema` | Pydantic class, Python type, or JSON-schema dict | Expected response shape |\n\n## Resuming: `rerun_on_resume`\n\nThe flag on the interrupted node decides what happens when the answer arrives.\n\n**`rerun_on_resume=False`** (the default for `FunctionNode`) — the node is *not*\nre-executed; the user's response becomes its output and flows downstream.\n\n```python\napproval_node = FunctionNode(func=ask_approval, rerun_on_resume=False)\n```\n\n**`rerun_on_resume=True`** (the default for an `LlmAgent` used as a node) — the\nnode runs again from the top, with answers available in `ctx.resume_inputs`,\nkeyed by `interrupt_id`.\n\n```python\nasync def interactive_node(ctx: Context, node_input: str):\n  if ctx.resume_inputs:\n    answer = list(ctx.resume_inputs.values())[0]\n    yield Event(output=f'User said: {answer}')\n  else:\n    yield RequestInput(message='What should I do?')\n```\n\n## Several questions from one node\n\nBecause a re-run node sees every answer so far, one node can walk a form:\n\n```python\nasync def multi_step_form(ctx: Context, node_input: str):\n  if not ctx.resume_inputs:\n    yield RequestInput(interrupt_id='ask_name', message='What is your name?')\n    return\n\n  if 'ask_email' not in ctx.resume_inputs:\n    yield RequestInput(interrupt_id='ask_email', message='What is your email?')\n    return\n\n  yield Event(output={\n      'name': ctx.resume_inputs['ask_name'],\n      'email': ctx.resume_inputs['ask_email'],\n  })\n```\n\n## Feeding the answer back into a loop\n\nA review-and-revise cycle turns the answer into a route. Vary the\n`interrupt_id` per iteration (see the best-practices reference for why) and read\n`ctx.resume_inputs` with the same id:\n\n```python\nasync def review(ctx: Context, node_input: Any):\n  review_count = ctx.state.get('review_count', 0)\n  interrupt_id = f'review_{review_count}'\n\n  response = ctx.resume_inputs.get(interrupt_id)\n  if response:\n    yield Event(\n        output=response,\n        route='approved' if response.get('approved') else 'rejected',\n        state={'review_count': review_count + 1},\n    )\n    return\n\n  yield RequestInput(\n      interrupt_id=interrupt_id,\n      message='Approve this plan?',\n      response_schema=ApprovalSchema,\n  )\n```\n\nWire the routes back with `(review, {'rejected': revise, 'approved': publish})`.\n\n## Resumable versus replayed\n\n**Replay (the default).** With no `App`, or with `is_resumable=False`, each\nresponse replays the workflow from `START`. Completed nodes are skipped and\nstate is rebuilt from event history, so only the interrupted node actually runs\nagain. Fine for a single interrupt; the replay cost grows with the graph.\n\n**Resumable.** Export an `App` with `is_resumable=True` and the workflow\ncheckpoints its progress into `event.actions.agent_state` and resumes at the\ninterrupted node instead of replaying.\n\n```python\nroot_agent = Workflow(name='my_workflow', edges=[...])\n\napp = App(\n    name='my_app',\n    root_agent=root_agent,\n    resumability_config=ResumabilityConfig(is_resumable=True),\n)\n```\n\nExport both `root_agent` and `app` from `agent.py` — the loader prefers `app`\nbut other tooling looks for `root_agent`. Use resumable mode for multi-step\ninterrupts, for `LongRunningFunctionTool`, and for any graph large enough that\nreplaying it is wasteful.\n\n## Human-in-the-loop from an LLM agent\n\nAn `LlmAgent` pauses through a `LongRunningFunctionTool` rather than\n`RequestInput`:\n\n```python\nfrom google.adk.tools import LongRunningFunctionTool\n\n\ndef approval_tool(request: str) -> str:\n  \"\"\"Request human approval for an action.\"\"\"\n  return f'Approved: {request}'\n\n\nllm_agent = LlmAgent(\n    name='agent_with_approval',\n    model='gemini-3.5-flash',\n    instruction='When you need approval, use approval_tool.',\n    tools=[LongRunningFunctionTool(func=approval_tool)],\n)\n```\n\nThe agent node already defaults to `rerun_on_resume=True`, so it picks the\nconversation back up on its own.\n\n## Answering from client code\n\n```python\nfrom google.genai import types\n\nfunction_call_id = interrupt_event.content.parts[0].function_call.id\n\nresponse = types.Content(\n    role='user',\n    parts=[types.Part(\n        function_response=types.FunctionResponse(\n            id=function_call_id,\n            name='adk_request_input',\n            response={'result': \"User's answer here\"},\n        )\n    )],\n)\n\nasync for event in runner.run_async(\n    user_id=user_id, session_id=session_id, new_message=response\n):\n  ...\n```\n\nA non-dict answer is carried under the `result` key as shown; a dict response\nmatching `response_schema` is passed through as-is.\n",".agents/skills/adk-agent-builder/references/import-paths.md":"# ADK Import Paths\n\nPrefer the canonical short import. The verbose module path is listed only where\nthere is no short form, or where the short form does not exist yet.\n\n## Canonical imports\n\nThese cover most agent code:\n\n```python\nfrom google.adk import Agent, Context, Event, Runner, Workflow\nfrom google.adk.events import Event, EventActions, RequestInput\nfrom google.adk.workflow import BaseNode, Edge, FunctionNode, JoinNode, node\nfrom google.adk.workflow import DEFAULT_ROUTE, RetryConfig, START\n```\n\n`google.adk` and `google.adk.workflow` load their members lazily, so importing\nfrom them does not pull in the whole package.\n\n## Agents\n\n| Component | Import |\n|---|---|\n| `Agent` (alias of `LlmAgent`) | `from google.adk import Agent` |\n| `LlmAgent` | `from google.adk.agents import LlmAgent` |\n| `BaseAgent` | `from google.adk.agents import BaseAgent` |\n| `SequentialAgent` (deprecated — use `Workflow`) | `from google.adk.agents import SequentialAgent` |\n| `ParallelAgent` (deprecated — use `Workflow`) | `from google.adk.agents import ParallelAgent` |\n| `LoopAgent` (deprecated — use `Workflow`) | `from google.adk.agents import LoopAgent` |\n| `RunConfig` | `from google.adk.agents import RunConfig` |\n\n## Workflow graph\n\n| Component | Import |\n|---|---|\n| `Workflow` | `from google.adk.workflow import Workflow` |\n| `Edge` | `from google.adk.workflow import Edge` |\n| `DEFAULT_ROUTE` (`\"__DEFAULT__\"`) | `from google.adk.workflow import DEFAULT_ROUTE` |\n| `START` | `from google.adk.workflow import START` |\n| `Graph` | `from google.adk.workflow._graph import Graph` |\n\n`Graph` is private; construct workflows from `edges=[...]` unless you need\n`Graph.from_edge_items(...)` directly.\n\n## Nodes\n\n| Component | Import |\n|---|---|\n| `BaseNode` | `from google.adk.workflow import BaseNode` |\n| `FunctionNode` | `from google.adk.workflow import FunctionNode` |\n| `Node` | `from google.adk.workflow import Node` |\n| `@node` decorator | `from google.adk.workflow import node` |\n| `JoinNode` | `from google.adk.workflow import JoinNode` |\n| `RetryConfig` | `from google.adk.workflow import RetryConfig` |\n| `NodeTimeoutError` | `from google.adk.workflow import NodeTimeoutError` |\n| `_ToolNode` (private) | `from google.adk.workflow._tool_node import _ToolNode` |\n\nParallel-worker behavior has no importable class. Set `parallel_worker=True` on\n`@node` or on an `LlmAgent`; the framework wraps it with an internal\n`_ParallelWorker`.\n\n## Events and context\n\n| Component | Import |\n|---|---|\n| `Event` | `from google.adk import Event` |\n| `EventActions` | `from google.adk.events import EventActions` |\n| `RequestInput` | `from google.adk.events import RequestInput` |\n| `Context` | `from google.adk import Context` |\n| `CallbackContext` (alias of `Context`) | `from google.adk.agents.callback_context import CallbackContext` |\n| `ReadonlyContext` | `from google.adk.agents.readonly_context import ReadonlyContext` |\n| `ToolContext` (alias of `Context`) | `from google.adk.tools import ToolContext` |\n\n## Task delegation\n\n| Component | Import |\n|---|---|\n| `FinishTaskTool` | `from google.adk.agents.llm.task._finish_task_tool import FinishTaskTool` |\n| `TaskRequest`, `TaskResult` | `from google.adk.agents.llm.task._task_models import TaskRequest, TaskResult` |\n\nAll three are private. Setting `mode='task'` attaches `FinishTaskTool`\nautomatically — there is no reason to import it in agent code.\n\n## Tools\n\n| Component | Import |\n|---|---|\n| `FunctionTool` | `from google.adk.tools import FunctionTool` |\n| `BaseTool` | `from google.adk.tools import BaseTool` |\n| `BaseToolset` | `from google.adk.tools.base_toolset import BaseToolset` |\n| `LongRunningFunctionTool` | `from google.adk.tools import LongRunningFunctionTool` |\n| `AgentTool` | `from google.adk.tools import AgentTool` |\n| `McpToolset` | `from google.adk.tools.mcp_tool import McpToolset` |\n| `StdioConnectionParams` | `from google.adk.tools.mcp_tool import StdioConnectionParams` |\n| `SseConnectionParams` | `from google.adk.tools.mcp_tool import SseConnectionParams` |\n| `StreamableHTTPConnectionParams` | `from google.adk.tools.mcp_tool import StreamableHTTPConnectionParams` |\n| `OpenAPIToolset`, `RestApiTool` | `from google.adk.tools.openapi_tool import OpenAPIToolset, RestApiTool` |\n\n`MCPToolset` (all caps) still resolves but raises a deprecation warning — use\n`McpToolset`.\n\n## Runner, sessions, app\n\n| Component | Import |\n|---|---|\n| `Runner` | `from google.adk import Runner` |\n| `InMemoryRunner` | `from google.adk.runners import InMemoryRunner` |\n| `InMemorySessionService` | `from google.adk.sessions import InMemorySessionService` |\n| `DatabaseSessionService` | `from google.adk.sessions import DatabaseSessionService` |\n| `VertexAiSessionService` | `from google.adk.sessions import VertexAiSessionService` |\n| `App`, `ResumabilityConfig` | `from google.adk.apps import App, ResumabilityConfig` |\n| `BasePlugin` | `from google.adk.plugins.base_plugin import BasePlugin` |\n\n`DatabaseSessionService` needs the `db` extra; importing it without\n`sqlalchemy` installed raises a \"missing extra\" error rather than\n`ImportError`.\n\n## Live streaming\n\n| Component | Import |\n|---|---|\n| `LiveRequestQueue` | `from google.adk.live import LiveRequestQueue` |\n| `LiveRequest` | `from google.adk.live import LiveRequest` |\n\n## Models\n\n| Component | Import |\n|---|---|\n| `BaseLlm` | `from google.adk.models.base_llm import BaseLlm` |\n| `LiteLlm` | `from google.adk.models.lite_llm import LiteLlm` |\n| `LlmRequest` | `from google.adk.models.llm_request import LlmRequest` |\n| `LlmResponse` | `from google.adk.models.llm_response import LlmResponse` |\n\n## Code executors\n\n| Component | Import |\n|---|---|\n| `BuiltInCodeExecutor` | `from google.adk.code_executors.built_in_code_executor import BuiltInCodeExecutor` |\n\n## google-genai types\n\n| Component | Import |\n|---|---|\n| `types` | `from google.genai import types` |\n| `Content`, `ModelContent`, `Part` | `from google.genai.types import Content, ModelContent, Part` |\n| `GenerateContentConfig` | `from google.genai.types import GenerateContentConfig` |\n",".agents/skills/adk-agent-builder/references/llm-agent-nodes.md":"# LLM Agents as Workflow Nodes\n\nPut an `LlmAgent` straight into `edges` and the framework runs it as a node,\nconverting the model's answer into the next node's `node_input`.\n\n```python\nfrom google.adk import Workflow\nfrom google.adk.agents import LlmAgent\n```\n\nThere is no wrapper class to import or subclass — the wrapping is internal.\n\n## Basic usage\n\n```python\nwriter = LlmAgent(\n    name='writer',\n    model='gemini-3.5-flash',\n    instruction=\"Write a short story based on the user's prompt.\",\n)\n\nreviewer = LlmAgent(\n    name='reviewer',\n    model='gemini-3.5-flash',\n    instruction='Review the following story and provide feedback.',\n)\n\nagent = Workflow(name='story_pipeline', edges=[('START', writer, reviewer)])\n```\n\n## What the next node receives\n\n| Agent config | `node_input` for the next node |\n|---|---|\n| no `output_schema` | `str` — the model's text parts, concatenated, thoughts excluded |\n| `output_schema=MyModel` | `dict` — the validated model, `model_dump(exclude_none=True)` |\n\n```python\nclass CodeOutput(BaseModel):\n  code: str\n  language: str\n\n\nwriter = LlmAgent(\n    name='writer',\n    model='gemini-3.5-flash',\n    instruction=\"Write code. Return JSON with 'code' and 'language'.\",\n    output_schema=CodeOutput,\n)\n\n\ndef process_code(node_input: dict) -> str:\n  return node_input['code']\n```\n\nSet `output_schema` whenever the downstream node needs fields rather than prose,\nand always when the agent feeds a `JoinNode` — the join parks partial results in\nsession state, and a raw `types.Content` there breaks a database-backed session\nservice.\n\n### Do not assert on `event.output` for an LLM agent's own event\n\nThe wrapper sets `event.output` internally, but the runner clears it on a copy\nbefore the event reaches your loop, so the same text is not rendered twice.\n`event.output` is therefore `None` when you read the agent's own event out of\n`runner.run_async(...)`. Assert on the downstream node's output, on\n`session.state[output_key]`, or on `event.content.parts[*].text` instead.\n\n## Auto-wrapping defaults\n\nAn `LlmAgent` placed in a workflow gets `mode='single_turn'` if `mode` is unset,\n`rerun_on_resume=True`, and its own content branch so parallel agents do not see\neach other's turns. Change the behavior on the agent, not on the wrapper:\n\n```python\n# single_turn (the default here): isolated, no session history\nclassifier = LlmAgent(\n    name='classifier',\n    model='gemini-3.5-flash',\n    instruction='Classify the input as positive, negative, or neutral.',\n    output_schema=ClassificationResult,\n)\n\n# task: multi-turn within the delegated task, supports human-in-the-loop\ntask_agent = LlmAgent(\n    name='task_agent',\n    model='gemini-3.5-flash',\n    mode='task',\n    instruction='Process the request.',\n)\n```\n\n`mode='chat'` is only legal directly after `START` — see the graph validation\nrules in the advanced-patterns reference.\n\n## Instruction as a function\n\nFor an instruction that depends on more than placeholder substitution, pass a\ncallable taking a `ReadonlyContext`:\n\n```python\nfrom google.adk.agents.readonly_context import ReadonlyContext\n\n\ndef build_instruction(ctx: ReadonlyContext) -> str:\n  agents = ctx.state.get('active_agents', [])\n  return f\"Coordinate these agents: {', '.join(agents)}\"\n\n\nagent = LlmAgent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    instruction=build_instruction,\n)\n```\n\n## Storing output in state\n\n`output_key` writes the agent's output into session state, where a later\ninstruction template or a state-bound function parameter can read it:\n\n```python\nagent = LlmAgent(\n    name='writer',\n    model='gemini-3.5-flash',\n    instruction='Write a draft.',\n    output_key='draft',  # lands in state['draft']\n)\n```\n\n## Controlling history\n\n`include_contents='none'` runs the agent without session history, which is what\nyou want for a classifier or extractor that should judge only the current input:\n\n```python\nagent = LlmAgent(\n    name='stateless',\n    model='gemini-3.5-flash',\n    instruction='Process this input independently.',\n    include_contents='none',\n)\n```\n\n## Tools\n\n```python\ndef search_database(query: str) -> str:\n  \"\"\"Search the database for relevant records.\"\"\"\n  return f'Results for: {query}'\n\n\nagent = LlmAgent(\n    name='assistant',\n    model='gemini-3.5-flash',\n    instruction='Help the user with their request.',\n    tools=[search_database],\n)\n```\n\n`tools` accepts plain callables (wrapped as `FunctionTool`), `BaseTool`\ninstances, and `BaseToolset` instances.\n\n## Generation config\n\nModel-level knobs go in `generate_content_config`. Instructions, tools, and\nresponse schema do **not** — set those as agent fields, or they are ignored.\n\n```python\nfrom google.genai import types\n\nagent = LlmAgent(\n    name='creative',\n    model='gemini-3.5-flash',\n    instruction='Write creative stories.',\n    generate_content_config=types.GenerateContentConfig(\n        temperature=0.9,\n        top_p=0.95,\n        max_output_tokens=2048,\n    ),\n)\n```\n\n## Transfer between agents\n\nAn `LlmAgent` with `sub_agents` can hand control to one of them by reasoning\nabout their `description` fields:\n\n```python\nspecialist = LlmAgent(\n    name='specialist',\n    model='gemini-3.5-flash',\n    description='Handles specialized requests.',\n    instruction='Answer specialized questions.',\n)\n\ncoordinator = LlmAgent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    instruction='Route requests to the specialist when needed.',\n    sub_agents=[specialist],\n)\n```\n\n`disallow_transfer_to_parent=True` and `disallow_transfer_to_peers=True` close\noff the return path and sideways moves respectively.\n",".agents/skills/adk-agent-builder/references/multi-agent.md":"# Multi-Agent Hierarchies\n\nComposing agents when the composition is a tree of agents rather than a graph of\nnodes.\n\n## Chat transfer\n\nGive a coordinator `sub_agents` and the model decides, from their `description`\nfields, when to hand over. Control passes to the sub-agent and comes back the\nsame way.\n\n```python\nfrom google.adk import Agent\n\nresearcher = Agent(\n    name='researcher',\n    description='Researches topics and reports findings.',\n    instruction='You research topics and provide findings.',\n    tools=[search_tool],\n)\n\nwriter = Agent(\n    name='writer',\n    description='Writes prose from research findings.',\n    instruction='You write content based on research.',\n)\n\nroot_agent = Agent(\n    model='gemini-3.5-flash',\n    name='coordinator',\n    instruction='Delegate research to the researcher and writing to the writer.',\n    sub_agents=[researcher, writer],\n)\n```\n\n- Only the root needs `model=`; a sub-agent without one resolves to the nearest\n  `LlmAgent` ancestor's model.\n- The `description` is the only thing the routing model sees, so make each one\n  say what the agent is *for* and how it differs from its peers. Ambiguous\n  descriptions are the usual cause of the wrong agent picking up a request.\n- `disallow_transfer_to_parent=True` blocks the way back;\n  `disallow_transfer_to_peers=True` blocks sideways moves. Both default to\n  `False`, so a sub-agent can normally return control on its own.\n\nFor schema-validated delegation rather than free-form transfer, set\n`mode='task'` or `mode='single_turn'` on the sub-agent — that is a different\nmechanism with its own tool and completion protocol.\n\n## Orchestration agents\n\n> **Deprecated.** `SequentialAgent`, `ParallelAgent`, and `LoopAgent` are all\n> deprecated in favour of `Workflow` and will be removed in a future version.\n> Build new orchestration as a `Workflow` graph instead — the getting-started\n> reference shows the equivalent edge lists. The one thing they still do that\n> `Workflow` cannot: a `Workflow` cannot yet be used as an `LlmAgent`\n> sub-agent, so reach for these only when you need model-driven transfer into\n> an orchestrated block.\n\nThese three run their `sub_agents` without asking a model what to do next.\n\n```python\nfrom google.adk.agents import LoopAgent, ParallelAgent, SequentialAgent\n\n# One after another\nroot_agent = SequentialAgent(\n    name='pipeline',\n    sub_agents=[step1_agent, step2_agent, step3_agent],\n)\n\n# All at once\nroot_agent = ParallelAgent(\n    name='fan_out',\n    sub_agents=[task_a, task_b, task_c],\n)\n```\n\n`LoopAgent` repeats its sub-agents until one calls `exit_loop` or escalates.\n`max_iterations` is optional; without it the only way out is `exit_loop`, so set\none unless a sub-agent reliably calls the tool.\n\n```python\nfrom google.adk.agents import LoopAgent\nfrom google.adk.tools import exit_loop\n\nchecker = Agent(\n    name='checker',\n    tools=[exit_loop],\n    instruction='Check the result and call exit_loop when it is good enough.',\n)\n\nroot_agent = LoopAgent(\n    name='retry_loop',\n    sub_agents=[worker_agent, checker],\n    max_iterations=5,\n)\n```\n\n## Models\n\nThe built-in default when no agent in the chain sets `model=` is\n`LlmAgent.DEFAULT_MODEL`, currently `'gemini-3.5-flash'`. Override the default\nprocess-wide with `LlmAgent.set_default_model('gemini-3.1-pro-preview')`.\n\nNon-Gemini models go through LiteLLM, with the provider as a prefix:\n\n```python\nfrom google.adk.models.lite_llm import LiteLlm\n\nroot_agent = Agent(model=LiteLlm(model='openai/gpt-4o'), ...)\n```\n\n## Common failures\n\n| Symptom | Cause |\n|---|---|\n| A sub-agent takes over and never gives control back | It has no path home; check `disallow_transfer_to_parent` and say in its instruction when to return |\n| The wrong agent answers | Two `description` fields overlap; sharpen the boundary between them |\n| `ImportError` on agent definitions | Circular imports between per-agent modules; define the tree in one `agent.py` or put shared sub-agents in their own module |\n",".agents/skills/adk-agent-builder/references/parallel-and-fanout.md":"# Parallel Execution, Fan-Out, and Fan-In\n\nTwo different things share the word \"parallel\": **fan-out** runs several\n*different* nodes on the same input, and a **parallel worker** runs one node\nacross every item of a list. Mixing them up is the most common failure here.\n\n```python\nfrom google.adk import Agent, Workflow\nfrom google.adk.workflow import JoinNode, node\n```\n\n## Fan-out: several nodes, same input\n\nA tuple of targets sends the same output to each of them concurrently:\n\n```python\nagent = Workflow(\n    name='fan_out',\n    edges=[('START', (analyze_text, translate_text, summarize_text))],\n)\n```\n\n## Fan-in: `JoinNode`\n\nA `JoinNode` waits for every predecessor, then fires once with all their outputs\nin a dict keyed by predecessor node name:\n\n```python\njoin = JoinNode(name='collect_results')\n\nagent = Workflow(\n    name='fan_out_fan_in',\n    edges=[\n        ('START', (analyze_text, translate_text, summarize_text)),\n        ((analyze_text, translate_text, summarize_text), join),\n        (join, final_processor),\n    ],\n)\n\n\ndef final_processor(node_input: dict) -> str:\n  # {'analyze_text': ..., 'translate_text': ..., 'summarize_text': ...}\n  return f\"Combined: {node_input['analyze_text']}\"\n```\n\nWhile waiting, the join holds partial inputs in session state. If a predecessor\nis an `LlmAgent` without `output_schema`, what it holds is a `types.Content`,\nwhich a database-backed session service cannot serialize — set `output_schema`\non every LLM agent feeding a join.\n\n## Multi-trigger: fan-out into one shared successor\n\nPoint several branches at one node with no join and that node runs once per\nbranch:\n\n```python\nagent = Workflow(\n    name='root_agent',\n    input_schema=str,\n    edges=[(\n        'START',\n        (make_uppercase, count_characters, reverse_string),\n        send_message,\n    )],\n)\n```\n\n`send_message` fires three times here. A `JoinNode` in the same position would\nfire once with a merged dict. Pick by whether the downstream work is per-branch\nor needs all branches at once.\n\n## Parallel workers: one node, every item of a list\n\n`parallel_worker=True` wraps a node so it receives a list, runs an instance per\nitem concurrently, and emits a list of results in input order.\n\n```python\n@node(parallel_worker=True)\ndef process_item(node_input: int) -> int:\n  return node_input * 2\n\n\ndef produce_list(node_input: str) -> list:\n  return [1, 2, 3, 4, 5]\n\n\nagent = Workflow(\n    name='parallel_processing',\n    edges=[('START', produce_list, process_item)],\n)\n# process_item emits [2, 4, 6, 8, 10]\n```\n\nThere is no `ParallelWorker` class to import; the wrapper is internal.\n\nBehavior worth knowing:\n\n- A non-list input is wrapped in a one-element list rather than rejected.\n- An empty list produces an empty list, and downstream still fires.\n- `rerun_on_resume` is forced to `True`.\n- `max_parallel_workers=N` caps concurrency; unset means unbounded. It must be\n  at least 1.\n\n### On an agent\n\nSet the flag on the `LlmAgent` itself — each item is handled by a clone:\n\n```python\nexplain_topic = Agent(\n    name='explain_topic',\n    instruction='Explain how this topic relates to \"{topic}\".',\n    output_schema=TopicExplanation,\n    parallel_worker=True,\n)\n\nagent = Workflow(\n    name='parallel_analysis',\n    edges=[('START', process_input, find_related_topics, explain_topic, aggregate)],\n)\n```\n\n### Do not put `parallel_worker=True` on a fan-out branch\n\nFan-out edges already run their targets concurrently. Adding the flag makes the\nbranch expect a list and iterate it; handed a single value it iterates once, and\nhanded `None` it emits nothing — so a downstream join waits forever.\n\n## Diamond\n\n```python\njoin = JoinNode(name='merge')\n\n\ndef combiner(node_input: dict) -> str:\n  return f\"{node_input['branch_a']} + {node_input['branch_b']}\"\n\n\nagent = Workflow(\n    name='diamond',\n    edges=[\n        ('START', splitter),\n        (splitter, (branch_a, branch_b)),\n        ((branch_a, branch_b), join),\n        (join, combiner),\n    ],\n)\n```\n\n## `SequentialAgent` and `ParallelAgent`\n\n> **Deprecated.** Both are deprecated in favour of `Workflow` and will be\n> removed in a future version. Use the edge forms above for new code.\n\nShorthands for the two commonest graphs, when you have agents rather than\nfunctions:\n\n```python\nfrom google.adk.agents import ParallelAgent, SequentialAgent\n\n# START -> writer -> reviewer -> editor\npipeline = SequentialAgent(\n    name='pipeline',\n    sub_agents=[writer_agent, reviewer_agent, editor_agent],\n)\n\n# START -> (analyzer, translator, summarizer)\nparallel = ParallelAgent(\n    name='concurrent',\n    sub_agents=[analyzer_agent, translator_agent, summarizer_agent],\n)\n```\n",".agents/skills/adk-agent-builder/references/routing-and-conditions.md":"# Routing and Conditional Branching\n\nA node emits `Event(route=...)`; the edges leaving that node decide which\nsuccessors fire.\n\n## Dict routing — the default form\n\nMap route values to targets in one edge tuple:\n\n```python\nfrom google.adk import Event, Workflow\n\n\ndef classify(node_input: str):\n  route = 'error' if 'error' in node_input else 'success'\n  return Event(output=node_input, route=route)\n\n\nagent = Workflow(\n    name='router',\n    edges=[\n        ('START', classify),\n        (classify, {'success': handle_success, 'error': handle_error}),\n    ],\n)\n```\n\nThe three-tuple form `(classify, handle_success, 'success')` does the same thing\none target at a time. Reach for it only when a single edge must match several\nroutes (see below), since the dict form maps one route to one target.\n\n## Sequence shorthand\n\nA tuple of more than two elements becomes a chain:\n\n```python\nedges = [('START', step_a, step_b, step_c)]\n# equivalent to [('START', step_a), (step_a, step_b), (step_b, step_c)]\n```\n\nChains and dict routing compose:\n\n```python\nedges = [\n    ('START', process_input, classify),\n    (classify, {'approved': send, 'rejected': discard}),\n]\n```\n\n## Route values\n\nA route is a `str`, `bool`, or `int`, or a list of those.\n\n```python\n(decision, {'approve': path_a, 'reject': path_b})   # strings\n(decision, {True: yes_path, False: no_path})        # booleans\n(decision, {0: path_0, 1: path_1})                  # integers\n```\n\n## Default route\n\n`'__DEFAULT__'` (exported as `DEFAULT_ROUTE`) fires when no other route on that\nnode matches:\n\n```python\nedges = [\n    ('START', classify),\n    (classify, {\n        'success': handler_a,\n        'error': handler_b,\n        '__DEFAULT__': fallback_handler,\n    }),\n]\n```\n\nThe default fans out like any other route, so `'__DEFAULT__': (a, b)` triggers\nboth. `'__DEFAULT__'` may not appear inside a list of routes on one edge — give\nit its own edge.\n\n## One edge, several routes\n\nPassing a list matches any value in it:\n\n```python\nedges = [\n    ('START', classifier),\n    (classifier, {'route_z': handler_b}),\n    (classifier, handler_a, ['route_x', 'route_y']),\n]\n```\n\n## Several routes from one node\n\nA node can emit a list of routes to fire multiple branches at once:\n\n```python\ndef fan_out_router(node_input: str):\n  return Event(output=node_input, route=['path_a', 'path_b'])\n\n\nagent = Workflow(\n    name='multi_route',\n    edges=[\n        ('START', fan_out_router),\n        (fan_out_router, {'path_a': branch_a, 'path_b': branch_b}),\n    ],\n)\n```\n\n## Self-loop\n\n```python\ndef guess_number(target_number: int):\n  guess = random.randint(0, 10)\n  yield Event(message=f'Guessing {guess}...')\n  if guess == target_number:\n    yield Event(message='Correct!')\n  else:\n    yield Event(route='guessed_wrong')\n\n\nagent = Workflow(\n    name='root_agent',\n    edges=[\n        ('START', validate_input, guess_number),\n        (guess_number, {'guessed_wrong': guess_number}),\n    ],\n)\n```\n\n## Revision loop\n\n```python\nedges = [\n    ('START', process_input, draft_email, human_review),\n    (human_review, {\n        'revise': draft_email,\n        'approved': send,\n        'rejected': discard,\n    }),\n]\n```\n\n## Constraints the graph validator enforces\n\n- **A cycle needs at least one routed edge.** An entirely unconditional cycle is\n  rejected at construction time, because nothing could ever break out of it.\n- **Edges leaving `START` may not carry a route.** `START` never runs, so it\n  never emits one.\n- **No two edges may share a source and a target**, even with different routes.\n  To reach one destination from both a named route and `__DEFAULT__`, point the\n  default at a thin wrapper function.\n\n## Unrouted edges always fire\n\nAn edge with no route fires on every output event from its source, whatever\nroute that event carries. So if a node routes at all, give *every* one of its\noutgoing edges a route — otherwise the unrouted one fires alongside the branch\nyou selected.\n\n```python\nedges = [\n    ('START', node_a),  # unconditional\n    (node_a, node_b),   # fires on every node_a output\n]\n```\n",".agents/skills/adk-agent-builder/references/session-and-state.md":"# Sessions, Artifacts, and Memory\n\nWhere data lives beyond a single node: the session store, binary artifacts, and\nlong-term memory.\n\n## State key scopes\n\nA prefix on the key decides how far the value travels and how long it lives.\n\n| Key form | Scope |\n|---|---|\n| `key` | This session |\n| `app:key` | The whole app — shared across every session and user |\n| `user:key` | This user, across their sessions |\n| `temp:key` | This invocation only; never persisted |\n\nA `state_schema` on a node validates writes, but prefixed keys bypass that\nvalidation.\n\nMutate, do not rebind — `state['key'] = value` records a delta, whereas\n`state = {'key': value}` just rebinds a local name and is lost.\n\n```python\ndef my_tool(tool_context: ToolContext):\n  tool_context.state['user_name'] = 'Alice'\n  tool_context.state['app:feature_flag'] = True\n```\n\nIn parallel branches, two nodes writing the same key race. Give each branch its\nown key, or write to a shared `app:`-scoped key deliberately.\n\n## Session services\n\n| Service | Use for | Import |\n|---|---|---|\n| `InMemorySessionService` | local development and tests | `from google.adk.sessions import InMemorySessionService` |\n| `DatabaseSessionService` | production on SQLite or PostgreSQL | `from google.adk.sessions import DatabaseSessionService` |\n| `VertexAiSessionService` | Vertex AI Agent Engine | `from google.adk.sessions import VertexAiSessionService` |\n\n```python\nfrom google.adk import Runner\nfrom google.adk.sessions import InMemorySessionService\n\nrunner = Runner(\n    agent=root_agent,\n    app_name='my_app',\n    session_service=InMemorySessionService(),\n)\n```\n\n`DatabaseSessionService` needs the `db` extra (`pip install \"google-adk[db]\"`).\nIt also serializes everything you put in state and in a `JoinNode`'s parked\ninputs, so a non-JSON-serializable value that works in memory fails here.\n\n## Artifacts\n\nArtifacts hold bytes — images, files, generated documents — keyed by filename\nand versioned per session.\n\n```python\nfrom google.genai import types\n\n\nasync def save_chart(tool_context: ToolContext):\n  part = types.Part.from_bytes(data=generate_chart(), mime_type='image/png')\n  version = await tool_context.save_artifact('chart.png', part)\n\n\nasync def get_chart(tool_context: ToolContext):\n  part = await tool_context.load_artifact('chart.png')\n  return part.inline_data.data\n```\n\n## Memory\n\nMemory is recall across sessions, as opposed to state, which is recall within\none.\n\n```python\nfrom google.adk.memory.in_memory_memory_service import InMemoryMemoryService\n\nrunner = Runner(\n    agent=root_agent,\n    app_name='my_app',\n    session_service=InMemorySessionService(),\n    memory_service=InMemoryMemoryService(),\n)\n```\n\nAgents reach it through the `load_memory` and `preload_memory` tools, or\ndirectly with `await ctx.search_memory(query)`.\n",".agents/skills/adk-agent-builder/references/state-and-events.md":"# Context and Events\n\nThe two objects a node touches: `Context`, which is how it reads its\nsurroundings, and `Event`, which is how it says anything.\n\n```python\nfrom google.adk import Context, Event\n```\n\n## Getting a `Context`\n\nDeclare a parameter named `ctx`. Nodes that do not need it can omit it.\n\n```python\ndef my_node(ctx: Context, node_input: str) -> str:\n  value = ctx.state.get('key', 'default')\n  return f'{ctx.session.id}: {value}'\n```\n\n## Context properties\n\nAvailable everywhere (a `Context` is also what a callback and a tool receive —\n`CallbackContext` and `ToolContext` are aliases for this same class):\n\n| Property | Type | Notes |\n|---|---|---|\n| `state` | `State` | Delta-aware session state; reads and writes like a dict |\n| `session` | `Session` | Current session, with the workflow's local events merged in |\n| `invocation_id` | `str` | This invocation |\n| `user_id` | `str` | Read-only |\n| `user_content` | `types.Content \\| None` | The message that started the invocation |\n| `agent_name` | `str` | Agent currently running |\n| `run_config` | `RunConfig \\| None` | Read-only |\n| `actions` | `EventActions` | State and artifact deltas being accumulated |\n| `branch` | `str \\| None` | Event-isolation branch |\n| `function_call_id` | `str \\| None` | Set when running as a tool |\n\nMeaningful only inside a workflow node:\n\n| Property | Type | Notes |\n|---|---|---|\n| `node` | `BaseNode \\| None` | The node being executed |\n| `node_path` | `str` | Full path, e.g. `'WorkflowA/node1'` |\n| `run_id` | `str` | This node-run, e.g. `'1'`, `'2'` |\n| `attempt_count` | `int` | 1 on the first try, higher on a retry |\n| `resume_inputs` | `dict[str, Any]` | Human-in-the-loop answers, keyed by `interrupt_id` |\n| `error`, `error_node_path` | `Exception \\| None`, `str` | Set after a node fails |\n\n## Context methods\n\n| Method | Purpose |\n|---|---|\n| `await run_node(node, node_input=None, *, use_as_output=False, run_id=None, use_sub_branch=False, override_branch=None)` | Run a node dynamically; the caller needs `rerun_on_resume=True` |\n| `await save_artifact(filename, part)` / `await load_artifact(filename)` | Session artifacts |\n| `await search_memory(query)` | Long-term memory lookup |\n| `get_auth_response(auth_config)` / `request_credential(auth_config)` | Credentials |\n| `get_invocation_context()` | Escape hatch to the underlying `InvocationContext` |\n\n## Parameters resolved from state\n\nAny function-node parameter that is not `ctx` or `node_input` is looked up in\n`ctx.state` by name, and coerced to its annotation. If the key is absent, the\nparameter's default is used.\n\n```python\n# With state {'user_name': 'Alice', 'threshold': 0.5}\ndef my_node(node_input: str, user_name: str, threshold: float) -> str:\n  return f'{user_name}: {node_input} (threshold={threshold})'\n```\n\nResolution order: `ctx` → `node_input` → `ctx.state[name]` → default.\n\n## Event fields\n\n`Event` extends `LlmResponse`. Three of its constructor arguments are\nconveniences that write somewhere else:\n\n| Constructor argument | Where it lands |\n|---|---|\n| `output=` | `event.output` — data for the next node |\n| `message=` | `event.content` — what the UI renders |\n| `state=` | `event.actions.state_delta` |\n| `route=` | `event.actions.route` |\n\n`message` and `content` are mutually exclusive; passing both raises. `message`\naccepts a string, a `types.Part`, a list of parts, or a `types.Content`.\n\nOther fields you will read: `author`, `content`, `partial`, `branch`,\n`node_info` (with `node_info.path` identifying the emitting node), and\n`is_final_response()`.\n\n## Emitting user-visible messages\n\n```python\nyield Event(message='Processing step 1...')\n\n# Multimodal\nfrom google.genai import types\n\nyield Event(message=[\n    types.Part.from_text(text='Here is the result:'),\n    types.Part.from_bytes(data=image_bytes, mime_type='image/png'),\n])\n\n# Streaming chunks, then the real output\nasync def verbose_node(ctx: Context, node_input: str):\n  yield Event(message='Processing step 1...', partial=True)\n  yield Event(message='Processing step 2...', partial=True)\n  yield Event(output='final result')\n```\n\n## What the workflow itself outputs\n\nAfter every node settles, the workflow emits one more event of its own. Its\nvalue comes from the terminal nodes — those with no outgoing edges. That event\nis authored by the workflow, with `node_info.path` set to the workflow's own\npath, which is why filtering test assertions on `event.author` picks up the\nwrapper rather than the node you meant.\n",".agents/skills/adk-agent-builder/references/task-mode.md":"# Task Delegation (`mode='task'` / `mode='single_turn'`)\n\nHand a sub-agent a schema-validated job and get a schema-validated answer back,\ninstead of transferring the whole conversation to it.\n\n## The three modes\n\n`LlmAgent.mode` is `'chat'`, `'task'`, `'single_turn'`, or unset. Unset means\n`'chat'` when the agent is a sub-agent, `'single_turn'` when it is a workflow\nnode.\n\n| Mode | How the parent reaches it | User interaction | How it finishes |\n|---|---|---|---|\n| `chat` | the `transfer_to_agent` tool | full conversation | transfers back |\n| `task` | a tool named after the sub-agent | can ask the user for clarification | calls `finish_task` |\n| `single_turn` | a tool named after the sub-agent | none — told no reply is coming | calls `finish_task` |\n\nThe delegation tool takes the sub-agent's **`name`** verbatim. An agent called\n`researcher` is exposed to the coordinator as a tool called `researcher`, and\nits `description` becomes the tool description, so write the description for a\nmodel deciding whether to call it.\n\n## Task mode\n\n```python\nfrom google.adk import Agent\nfrom pydantic import BaseModel\n\n\nclass ResearchInput(BaseModel):\n  topic: str\n  depth: str = 'standard'\n\n\nclass ResearchOutput(BaseModel):\n  summary: str\n  key_findings: str\n  confidence: str\n\n\ndef search_web(query: str) -> str:\n  \"\"\"Search the web for information.\"\"\"\n  return f'Results for \"{query}\": ...'\n\n\nresearcher = Agent(\n    name='researcher',\n    mode='task',\n    input_schema=ResearchInput,\n    output_schema=ResearchOutput,\n    description='Researches topics using web search and analysis.',\n    instruction=(\n        'Research the given topic with search_web. If the user asks for'\n        ' changes, adjust. When done, call finish_task with summary,'\n        ' key_findings, and confidence.'\n    ),\n    tools=[search_web],\n)\n\nroot_agent = Agent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    sub_agents=[researcher],\n    instruction=(\n        'When the user asks for research, call the researcher tool. Summarize'\n        ' its result for the user.'\n    ),\n)\n```\n\nSequence: the coordinator calls the `researcher` tool with structured input; the\nresearcher works, possibly talking to the user; the researcher calls\n`finish_task` with structured output; the coordinator gets the result.\n\n## Single-turn mode\n\nSame shape, no conversation. The framework appends a nudge to the sub-agent's\ninput telling it no further user replies will arrive, so it must finish from the\ninput alone.\n\n```python\nclass SummaryOutput(BaseModel):\n  summary: str\n  word_count: int\n\n\nsummarizer = Agent(\n    name='summarizer',\n    mode='single_turn',\n    output_schema=SummaryOutput,\n    description='Summarizes documents autonomously.',\n    instruction='Summarize the document with extract_text, then finish_task.',\n    tools=[extract_text],\n)\n\nroot_agent = Agent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    sub_agents=[summarizer],\n    instruction='Delegate summarization to the summarizer tool.',\n)\n```\n\n## Schemas\n\n`input_schema` types the delegation tool's parameters; `output_schema` types\n`finish_task`'s parameters. Both are optional.\n\n```python\nagent = Agent(\n    name='worker',\n    mode='task',\n    input_schema=TaskInput,    # validates the delegation call\n    output_schema=TaskOutput,  # validates the finish_task call\n    ...\n)\n```\n\nWithout them the defaults are a single string each:\n\n```python\n# delegation tool parameters\n{'request': str}   # \"Detailed instructions or context for the task sub-agent.\"\n\n# finish_task parameters\n{'result': str}\n```\n\nA schema violation is not fatal — `finish_task` returns a validation-error\nmessage and the model gets to retry.\n\n## `finish_task`\n\n`mode='task'` attaches a tool called `finish_task` to the sub-agent\nautomatically, and injects an instruction telling the model to complete the work\nbefore calling it. Its parameters come from `output_schema`, or `{'result':\nstr}` when there is none. There is nothing to import or register.\n\n## Mixed modes under one coordinator\n\n```python\nflight_searcher = Agent(\n    name='flight_searcher',\n    mode='task',                # interactive: can discuss options\n    input_schema=FlightSearchInput,\n    output_schema=FlightSearchOutput,\n    description='Searches and books flights interactively.',\n    instruction='Search flights, discuss with the user, then finish_task.',\n    tools=[search_flights, book_flight],\n)\n\nweather_checker = Agent(\n    name='weather_checker',\n    mode='single_turn',         # autonomous\n    output_schema=WeatherOutput,\n    description='Checks weather for a destination.',\n    instruction='Check the weather and call finish_task.',\n    tools=[get_weather],\n)\n\nroot_agent = Agent(\n    name='travel_planner',\n    model='gemini-3.5-flash',\n    sub_agents=[flight_searcher, weather_checker],\n    instruction=(\n        'Plan trips. Use weather_checker for weather and flight_searcher for'\n        ' booking.'\n    ),\n)\n```\n\n## Rules worth knowing\n\n- Only the coordinator needs `model=`; sub-agents inherit it from the nearest\n  `LlmAgent` ancestor.\n- Every delegating sub-agent needs a `description` — it is the entire tool\n  description the coordinator's model sees.\n- The delegation tool is marked as deferring its response and its description\n  tells the model **not** to call it in parallel with other tools. Do not build\n  a prompt that asks for several delegations in one turn.\n- A `mode='chat'` sub-agent gets neither a delegation tool nor `finish_task`; it\n  stays a `transfer_to_agent` target.\n\n## Task mode versus chat transfer\n\n| | chat (`transfer_to_agent`) | task / single_turn |\n|---|---|---|\n| input | free-form conversation | schema-validated |\n| output | free-form conversation | schema-validated |\n| control returns when | the agent transfers back | the agent calls `finish_task` |\n| user interaction | full chat | `task`: multi-turn, `single_turn`: none |\n\n## Where the code lives\n\n| Component | File |\n|---|---|\n| `mode`, `input_schema`, `output_schema` | `src/google/adk/agents/llm_agent.py` |\n| delegation tools, default input schema | `src/google/adk/tools/agent_tool.py` |\n| `finish_task` | `src/google/adk/agents/llm/task/_finish_task_tool.py` |\n| `TaskRequest`, `TaskResult` | `src/google/adk/agents/llm/task/_task_models.py` |\n",".agents/skills/adk-agent-builder/references/testing.md":"# Testing Workflow Agents\n\n`pytest` plus `InMemoryRunner`. Everything below uses the published\n`google-adk` package — no test-internal helpers.\n\n## Setup\n\n```bash\nuv add \"google-adk>=2.0\" pytest pytest-asyncio\n```\n\n```toml\n[tool.pytest.ini_options]\nasyncio_mode = \"auto\"\n```\n\n`asyncio_mode = \"auto\"` saves marking every test `@pytest.mark.asyncio`. Omit it\nif you prefer explicit marks.\n\n## Imports\n\n```python\nimport pytest\nfrom google.adk import Workflow\nfrom google.adk.agents import LlmAgent\nfrom google.adk.apps import App, ResumabilityConfig\nfrom google.adk.events import Event, RequestInput\nfrom google.adk.runners import InMemoryRunner\nfrom google.genai import types\n```\n\n## Two helpers worth having\n\n```python\nasync def run(agent, text='hi', app_name='test_app'):\n    runner = InMemoryRunner(agent=agent, app_name=app_name)\n    session = await runner.session_service.create_session(\n        app_name=app_name, user_id='u1'\n    )\n    msg = types.Content(role='user', parts=[types.Part(text=text)])\n    events = []\n    async for event in runner.run_async(\n        user_id='u1', session_id=session.id, new_message=msg\n    ):\n        events.append(event)\n    return runner, session, events\n\n\ndef node_name(event):\n    \"\"\"'workflow@1/step@1' -> 'step'.\"\"\"\n    if not event.node_info:\n        return None\n    return event.node_info.path.split('/')[-1].split('@')[0]\n```\n\n`event.author` is the *enclosing workflow's* name, not the node's, so filtering\non it silently matches the wrong events. `event.node_info.path` is the one that\nidentifies the node.\n\n## A workflow\n\n```python\nasync def test_simple_workflow():\n    def step_one(node_input: str) -> str:\n        return 'step 1 done'\n\n    def step_two(node_input: str) -> str:\n        return 'step 2 done'\n\n    agent = Workflow(\n        name='test_workflow', edges=[('START', step_one, step_two)]\n    )\n\n    _, _, events = await run(agent)\n    final = [e for e in events if node_name(e) == 'step_two' and e.output][-1]\n    assert final.output == 'step 2 done'\n```\n\n## Routing\n\n```python\nasync def test_routing():\n    def router(node_input: str):\n        route = 'error' if 'error' in node_input else 'success'\n        return Event(output=node_input, route=route)\n\n    agent = Workflow(\n        name='routing_test',\n        edges=[\n            ('START', router),\n            (router, {'success': success_handler, 'error': error_handler}),\n        ],\n    )\n\n    _, _, ok = await run(agent, text='all good')\n    assert any(node_name(e) == 'success_handler' for e in ok)\n\n    _, _, err = await run(agent, text='error case')\n    assert any(node_name(e) == 'error_handler' for e in err)\n```\n\n## Pause and resume\n\n```python\nasync def test_hitl_workflow():\n    async def ask_user(ctx, node_input: str):\n        yield RequestInput(message='Approve?', interrupt_id='ask')\n\n    def after_approval(node_input) -> str:\n        return f'Approved: {node_input}'\n\n    agent = Workflow(\n        name='hitl_test', edges=[('START', ask_user, after_approval)]\n    )\n    app = App(\n        name='hitl_test_app',\n        root_agent=agent,\n        resumability_config=ResumabilityConfig(is_resumable=True),\n    )\n    runner = InMemoryRunner(app=app)\n    session = await runner.session_service.create_session(\n        app_name='hitl_test_app', user_id='u1'\n    )\n\n    msg = types.Content(role='user', parts=[types.Part(text='start')])\n    paused = [\n        e\n        async for e in runner.run_async(\n            user_id='u1', session_id=session.id, new_message=msg\n        )\n    ]\n    fc_events = [e for e in paused if e.get_function_calls()]\n    assert fc_events, 'expected an interrupt function call'\n    fc = fc_events[-1].get_function_calls()[0]\n\n    response = types.Content(\n        role='user',\n        parts=[types.Part(function_response=types.FunctionResponse(\n            id=fc.id, name=fc.name, response={'result': 'yes'},\n        ))],\n    )\n    resumed = [\n        e\n        async for e in runner.run_async(\n            user_id='u1', session_id=session.id, new_message=response\n        )\n    ]\n    final = [e for e in resumed if node_name(e) == 'after_approval'][-1]\n    assert final.output == 'Approved: yes'\n```\n\n## State\n\nPrefer reading the session back after the run over inspecting state mid-flight.\n\n```python\nasync def test_state_management():\n    def writer(node_input: str):\n        return Event(output=node_input, state={'counter': 1})\n\n    def reader(ctx, node_input):\n        return f\"counter={ctx.state['counter']}\"\n\n    agent = Workflow(name='state_test', edges=[('START', writer, reader)])\n\n    runner, session, events = await run(agent)\n    final = [e for e in events if node_name(e) == 'reader' and e.output][-1]\n    assert final.output == 'counter=1'\n\n    after = await runner.session_service.get_session(\n        app_name='test_app', user_id='u1', session_id=session.id\n    )\n    assert after.state['counter'] == 1\n```\n\n## Parallel workers\n\n```python\nfrom google.adk.workflow import node\n\n\nasync def test_parallel_worker():\n    def produce(node_input: str) -> list:\n        return [1, 2, 3]\n\n    @node(parallel_worker=True)\n    def double(node_input: int) -> int:\n        return node_input * 2\n\n    def collect(node_input: list) -> str:\n        return f'results: {node_input}'\n\n    agent = Workflow(\n        name='parallel_test', edges=[('START', produce, double, collect)]\n    )\n\n    _, _, events = await run(agent)\n    final = [e for e in events if node_name(e) == 'collect' and e.output][-1]\n    assert final.output == 'results: [2, 4, 6]'\n```\n\n## Faking the model\n\n`BaseLlm` has exactly one abstract method, so a fake is short:\n\n```python\nfrom google.adk.models.base_llm import BaseLlm\nfrom google.adk.models.llm_response import LlmResponse\n\n\nclass FakeLlm(BaseLlm):\n\n    def __init__(self, *, responses: list[str]):\n        super().__init__(model='fake')\n        self._responses = list(responses)\n\n    async def generate_content_async(self, llm_request, stream=False):\n        yield LlmResponse(content=types.Content(\n            role='model', parts=[types.Part(text=self._responses.pop(0))],\n        ))\n\n\nasync def test_llm_agent_with_fake():\n    agent = LlmAgent(name='x', model=FakeLlm(responses=['ok']), instruction='Help.')\n    _, _, events = await run(agent, text='hi')\n    assert events[-1].content.parts[0].text == 'ok'\n```\n\nTo assert on the request shape instead, `monkeypatch` the agent's\n`canonical_model.generate_content_async`.\n\nDo **not** assert on `event.output` for an LLM agent's own event — the runner\nclears it before you see it. Assert on the downstream node's output, on\n`session.state[output_key]`, or on `event.content.parts[*].text`.\n\n## Tests that hit a real model\n\n```python\nimport os\n\nimport pytest\n\n\n@pytest.fixture(scope='session', autouse=True)\ndef adk_env():\n    if 'GOOGLE_API_KEY' not in os.environ:\n        pytest.skip('GOOGLE_API_KEY not set')\n    os.environ.setdefault('GOOGLE_GENAI_USE_ENTERPRISE', 'FALSE')\n\n\n@pytest.mark.integration\nasync def test_real_model():\n    ...\n```\n\n`pytest -m integration` runs them; `pytest -m \"not integration\"` skips them.\n\n## Habits that avoid flakes\n\n- One `InMemoryRunner` and one session per test — runners carry state.\n- A unique `app_name` per test (`request.node.name` works) so parallel pytest\n  workers do not collide.\n- `event.is_final_response()` filters for \"the agent's last word\".\n- Any LLM agent feeding a `JoinNode` needs `output_schema=`, or the join buffer\n  fails to serialize under `DatabaseSessionService`.\n- `pytest -xvs` while iterating: stop at the first failure, verbose, show prints.\n",".agents/skills/adk-agent-builder/references/tool-catalog.md":"# Tool Catalog\n\nEvery way to give an agent a capability, from a plain Python function to a whole\nremote API.\n\n## Python functions\n\nPass callables straight to `tools=`. The name, docstring, and type hints become\nthe schema the model sees, so all three are load-bearing — an undocumented or\nuntyped parameter is invisible to the model.\n\n```python\ndef get_weather(city: str, unit: str = 'celsius') -> str:\n  \"\"\"Get the current weather for a city.\n\n  Args:\n    city: The city name to look up.\n    unit: Temperature unit, 'celsius' or 'fahrenheit'.\n\n  Returns:\n    A string with the weather information.\n  \"\"\"\n  return f'Sunny, 22 degrees {unit} in {city}'\n\n\nroot_agent = Agent(tools=[get_weather], ...)\n```\n\nSync and async both work.\n\n### Getting the context inside a tool\n\nAdd a parameter annotated with `ToolContext` (or `Context` / `CallbackContext` —\nthey are all the same class). It is matched **by annotation**, not by name, and\nexcluded from the schema the model sees. A parameter literally named\n`tool_context` is used as a fallback when no annotation matches.\n\n```python\nfrom google.adk.tools import ToolContext\n\n\nasync def my_tool(query: str, tool_context: ToolContext) -> str:\n  tool_context.state['key'] = 'value'\n  await tool_context.save_artifact('f.txt', part)\n  results = await tool_context.search_memory('q')\n  return 'done'\n```\n\nA parameter named `input_stream` is also excluded, for streaming tools.\n\n## Built-in tools\n\n| Tool | Import from `google.adk.tools` |\n|---|---|\n| `google_search` | Google Search grounding |\n| `url_context` | Fetch and ground on URLs in the prompt |\n| `load_artifacts` | Pull session artifacts into context |\n| `load_memory` / `preload_memory` | Query long-term memory |\n| `exit_loop` | Break out of a `LoopAgent` |\n| `transfer_to_agent` | Hand control to another agent |\n| `get_user_choice` | Ask the user to pick an option |\n| `google_maps_grounding`, `enterprise_web_search` | Other grounding sources |\n\n## Long-running tools\n\n`LongRunningFunctionTool` returns its result asynchronously against the original\n`function_call_id`, which is how an agent pauses for a human.\n\n```python\nfrom google.adk.tools import LongRunningFunctionTool\n\n\ndef approve_expense(amount: float) -> dict:\n  \"\"\"Submit an expense for approval.\"\"\"\n  return {'status': 'pending', 'id': 'exp-123'}\n\n\nroot_agent = Agent(tools=[LongRunningFunctionTool(approve_expense)], ...)\n```\n\n## Workflows and `@node` functions as tools\n\nPass a `Workflow` (with `input_schema` set to a Pydantic `BaseModel`) or a\n`@node`-decorated function straight into `tools=`. The agent runs it in an\nisolated sub-branch (`{tool_name}@{function_call_id}`) and receives the\nterminal output.\n\nSet `ctx.actions.skip_summarization = True` inside the node when its output\nshould be emitted directly as the final user-visible text response without a\nfollow-up LLM summarization turn:\n\n```python\nfrom google.adk import Agent, Context\nfrom google.adk.workflow import node\n\n\n@node\ndef generate_report(project: str, ctx: Context) -> str:\n  \"\"\"Generate a status report for a project.\"\"\"\n  ctx.actions.skip_summarization = True\n  return f'Report for {project}: Ready'\n\n\nroot_agent = Agent(tools=[generate_report], ...)\n```\n\n## MCP servers\n\n```python\nfrom google.adk.tools.mcp_tool import McpToolset, StdioConnectionParams\nfrom mcp import StdioServerParameters\n\nroot_agent = Agent(\n    tools=[\n        McpToolset(\n            connection_params=StdioConnectionParams(\n                server_params=StdioServerParameters(\n                    command='npx',\n                    args=['-y', '@modelcontextprotocol/server-filesystem', '/path'],\n                ),\n                timeout=5,\n            ),\n            tool_filter=['read_file', 'list_directory'],\n        )\n    ],\n    ...\n)\n```\n\nConnection classes: `StdioConnectionParams`, `SseConnectionParams`,\n`StreamableHTTPConnectionParams`.\n\nNeeds `pip install mcp`. `StdioServerParameters` comes from that package, not\nfrom ADK. Use `McpToolset`; the all-caps `MCPToolset` still resolves but warns.\n\n## OpenAPI specs\n\n```python\nfrom google.adk.tools.openapi_tool import OpenAPIToolset\n\ntoolset = OpenAPIToolset(spec_str=open('openapi.yaml').read(), spec_str_type='yaml')\nroot_agent = Agent(tools=[toolset], ...)\n```\n\n`spec_str_type` is `'json'` (the default) or `'yaml'`. Pass `spec_dict=` instead\nto skip parsing. `RestApiTool` from the same module wraps a single endpoint.\n\n## Google API toolsets\n\nGenerated from Google's API discovery documents. `BigQueryToolset`,\n`CalendarToolset`, and their siblings all take the same arguments.\n\n```python\nfrom google.adk.tools.google_api_tool.google_api_toolsets import BigQueryToolset\n\nbigquery = BigQueryToolset(\n    client_id='...',\n    client_secret='...',\n    tool_filter=['bigquery_datasets_list'],\n)\n```\n\nAlso accepted: `service_account=` instead of the OAuth pair, and\n`tool_name_prefix=` to namespace the generated tool names.\n\n## Code execution\n\nThe code executor is its own agent field, not a tool.\n\n```python\nfrom google.adk.code_executors.built_in_code_executor import BuiltInCodeExecutor\n\nroot_agent = Agent(code_executor=BuiltInCodeExecutor(), ...)\n```\n\n## Custom `BaseTool`\n\n```python\nfrom google.adk.tools import BaseTool\nfrom google.genai import types\n\n\nclass MyTool(BaseTool):\n\n  def __init__(self):\n    super().__init__(name='my_tool', description='Does something.')\n\n  def _get_declaration(self):\n    return types.FunctionDeclaration(\n        name=self.name,\n        description=self.description,\n        parameters_json_schema={\n            'type': 'object',\n            'properties': {'param': {'type': 'string'}},\n            'required': ['param'],\n        },\n    )\n\n  async def run_async(self, *, args, tool_context):\n    return {'result': args['param']}\n```\n\n## Custom `BaseToolset`\n\nA toolset supplies tools dynamically, so the set can depend on context.\n\n```python\nfrom google.adk.tools.base_toolset import BaseToolset\n\n\nclass MyToolset(BaseToolset):\n\n  def __init__(self):\n    super().__init__(tool_filter=None, tool_name_prefix='my')\n\n  async def get_tools(self, readonly_context=None):\n    return [ToolA(), ToolB()]\n\n  async def process_llm_request(self, *, tool_context, llm_request):\n    llm_request.append_instructions(['Custom instruction'])\n```\n\n`tool_filter` is a list of tool names or a `ToolPredicate` callable;\n`tool_name_prefix` renames every tool the toolset returns, which is how you keep\ntwo toolsets from colliding.\n",".agents/skills/adk-architecture/SKILL.md":"---\nname: adk-architecture\ndescription: >-\n  Explains how the ADK runtime fits together: the node and graph execution\n  model, Context and Event flow, checkpoint and resume, tracing, and the rules\n  governing the public API surface. Use when answering \"how does X work\" about\n  ADK internals, tracing where an event or a piece of state comes from,\n  deciding where a new capability belongs, reviewing a change to BaseNode,\n  Workflow, Runner, Agent, Event or Context, working out why a node re-ran or\n  stayed waiting after a resume, or judging whether a change breaks the public\n  API. Don't use for assembling an agent from existing pieces (use\n  adk-agent-builder), diagnosing one failing run or test (use adk-debug), or\n  formatting and naming conventions (use adk-style).\n---\n\n# ADK Architecture\n\nThe runtime is a graph of nodes. `BaseNode` is the unit of execution.\n`Workflow` is a node that schedules other nodes along declared edges.\n`NodeRunner` executes exactly one node. `Runner` owns the invocation and the\nsession. Agents are nodes too — `BaseAgent` extends `BaseNode`.\n\nA node communicates with its parent through a per-execution `Context`, and\nwith the session through `Event`s it yields. Those are two separate channels:\n`ctx` carries the result upward, events carry persistence and streaming.\n\nRead the source before relying on any signature here. These notes drift; the\ncode does not. Paths below are relative to `src/google/adk/`.\n\n## Pick a reference\n\n| Question | Reference |\n|---|---|\n| What must a node implement? What may it yield? Which config fields exist? | [BaseNode](references/interface-base-node.md) |\n| How does the graph schedule nodes, dedup dynamic children, propagate interrupts? | [Workflow](references/interface-workflow.md) |\n| How does a caller start an invocation? | [Runner](references/interface-runner.md) |\n| What is `Agent`, and which methods do I call on it? | [Agent](references/interface-agent.md) |\n| I am subclassing an agent — what do I override? | [BaseAgent](references/interface-base-agent.md) |\n| What is on an `Event`, and what may I assume about its lifetime? | [Event](references/interface-event.md) |\n| What does a node read and write on `ctx`? | [Context](references/architecture-context.md) |\n| Who creates the child Context, stamps events, retries, catches errors? | [NodeRunner](references/architecture-node-runner.md) |\n| Why are Runner, NodeRunner and Workflow three separate things? | [Runner roles](references/architecture-runner-roles.md) |\n| How does a human-in-the-loop pause and resume work for one node? | [Checkpoint and resume](references/architecture-checkpoint-resume.md) |\n| How does a whole workflow survive a pause, and what does `is_resumable` change? | [Workflow resumability](references/architecture-workflow-resumability.md) |\n| How are spans created, and what attributes do they carry? | [Observability](references/architecture-observability.md) |\n| Why does the model not see the raw event log? | [LLM context orchestration](references/architecture-llm-context-orchestration.md) |\n| Is this change a breaking change? Where does a new export belong? | [API principles](references/api-principles.md) |\n\n## Where the code lives\n\n| Concept | Module |\n|---|---|\n| `BaseNode`, `START` | `workflow/_base_node.py` |\n| `Workflow`, `_LoopState` | `workflow/_workflow.py` |\n| `Graph`, edge compilation | `workflow/_graph.py` |\n| `NodeRunner` | `workflow/_node_runner.py` |\n| `DynamicNodeScheduler`, `DynamicNodeState`, `DynamicNodeRun` | `workflow/_dynamic_node_scheduler.py` |\n| `ReplayManager` (resume scan) | `workflow/utils/_replay_manager.py` |\n| `check_interception` (replay fast-forward) | `workflow/utils/_replay_interceptor.py` |\n| `NodeInterruptedError`, `NodeTimeoutError` | `workflow/_errors.py` |\n| `Context`, `ctx.run_node()` | `agents/context.py` |\n| `ReadonlyContext` | `agents/readonly_context.py` |\n| `InvocationContext` | `agents/invocation_context.py` |\n| `BaseAgent`, `LlmAgent` (aliased `Agent`) | `agents/base_agent.py`, `agents/llm_agent.py` |\n| `BaseLlmFlow`, `SingleFlow`, `AutoFlow`, `flows/llm_flows/` subpackages (`context/`, `prompt/`, `tools/`, `extensions/`, `core/`) | `flows/llm_flows/` |\n| `Event`, `NodeInfo` | `events/event.py` |\n| `EventActions` | `events/event_actions.py` |\n| Branch paths (`parent.child@1`) | `events/_branch_path.py` |\n| Node paths (`wf@1/child@2`) | `events/_node_path_builder.py` |\n| `Runner`, `InMemoryRunner` | `runners.py` |\n| `LiveRequestQueue`, `LiveRequest` | `live/live_request_queue.py` |\n| Node spans, `TelemetryContext` | `telemetry/node_tracing.py` |\n| `ResumabilityConfig` | `apps/_configs.py` |\n\nEverything under `workflow/` is a leading-underscore module. Treat those names\nas internal — they can change without a major version bump, so a change there\nis not automatically a breaking change.\n",".agents/skills/adk-architecture/references/api-principles.md":"# API Principles\n\nGuidelines for designing and maintaining the ADK public API surface.\n\n## Public API Surface\n\nThe public API surface of ADK includes:\n- All public classes, methods, and functions in the `google.adk` namespace.\n- The names, required parameters, and expected behavior of all built-in Tools.\n- The structure and schema of persisted data (Sessions, Memory, Evaluation datasets).\n- The JSON request/response format of the ADK API server.\n- The command-line interface (CLI) commands, arguments, and flags.\n- The expected file structure for agent definitions (e.g., `agent.py` convention loaded by CLI).\n\n## Design Principles\n\n### 1. Stability and Backward Compatibility\n- ADK adheres to Semantic Versioning 2.0.0.\n- Any change that forces a developer to alter their existing code to upgrade is a **breaking change** and necessitates a MAJOR version bump.\n- Avoid breaking changes whenever possible by using optional parameters and deprecation cycles.\n- **Session, Artifact, and Memory Service Changes**:\n  Adding any new field to `Session`, `ArtifactVersion`, `MemoryEntry`, or to their associated service base classes (`BaseSessionService`, `BaseArtifactService`, `BaseMemoryService`) and database schemas is strictly considered a **breaking change** (even if the new field is optional or provides a default value).\n  - *Rationale*: There are numerous downstream implementations and custom subclasses across diverse storage backends (e.g. relational databases, key-value stores, document databases, external cloud backends). Downstream storage implementations do not automatically persist, deserialize, or round-trip fields they were not compiled or migrated to handle, leading to silent state loss or deserialization errors when sessions or memories are reloaded.\n\n### 2. Self-Containment\n- Each package should be as self-contained as possible to reduce coupling.\n- Within the ADK framework, importing from a package's `__init__.py` is **not allowed**. Import from the specific module directly.\n\n### 3. Explicit Exports\n- The public API of a package must be explicitly exported in `__init__.py`.\n- **Only public names** should be imported into `__init__.py`. This keeps `__init__.py` minimal and prevents accidental exposure of internal implementation details.\n\n### 4. Intuitive Naming\n- Public method and class names should be concise and intuitive.\n- Private method names can be longer and more self-explanatory to reduce the need for comments.\n\n#### Examples\n\n**Public Naming**\n- **Good**: `Runner.run()`, `Session.get_events()`\n- **Bad**: `Runner.orchestrate_agent_invocation_loop()`, `Session.retrieve_all_events_from_storage()`\n\n**Private Naming**\n- **Good**: `_prepare_context_for_llm()`, `_should_trim_history()`\n- **Bad**: `_prep()`, `_trim()`\n",".agents/skills/adk-architecture/references/architecture-checkpoint-resume.md":"# Checkpoint and Resume Lifecycle\n\nHITL (Human-in-the-Loop) follows this pattern:\n\n1. **Interrupt**: Node yields an event with `long_running_tool_ids`.\n   Each ancestor propagates the interrupt upward via `ctx.interrupt_ids`.\n2. **Persist**: Only the leaf node's interrupt event is persisted to\n   session. Workflow sets `ctx._interrupt_ids` directly (no internal\n   event needed).\n3. **Resume**: User sends a `FunctionResponse` message. The Runner\n   scans session events to find the matching `invocation_id`, then\n   reconstructs node state from persisted events.\n4. **Continue**: The interrupted node receives the FR and continues\n   execution. Downstream nodes receive the resumed node's output.\n\n## run_id on resume\n\nResumed nodes reuse the same `run_id` from the original\nexecution. From the node's perspective, the execution never paused\n— events before and after the resume share the same run_id.\n\nFresh dispatches (first run, loop re-trigger) get a new run_id.\n\n## Resume behavior by `rerun_on_resume`\n\nA node with multiple interrupt IDs may receive partial FRs (only\nsome resolved). The behavior depends on `rerun_on_resume`:\n\n**`rerun_on_resume=True`** (Workflow, orchestration nodes):\n\n| FRs received | Status | Behavior |\n|---|---|---|\n| Partial | PENDING | Re-execute immediately with partial `resume_inputs`. Node handles remaining interrupts internally (e.g., Workflow dispatches resolved children, keeps unresolved as WAITING). |\n| All | PENDING | Re-execute with all `resume_inputs`. |\n\nThis is critical for Workflow — when one child's FR arrives, it\nre-runs immediately to dispatch that resolved child. It doesn't\nwait for all children's FRs.\n\n**`rerun_on_resume=False`** (leaf nodes, simple HITL):\n\n| FRs received | Status | Behavior |\n|---|---|---|\n| Partial | WAITING | Stay waiting. Need all FRs. |\n| All | COMPLETED | Auto-complete. Output = aggregated `resolved_responses`. No re-execution. |\n\n## Resume with prior output and interrupts\n\nA node can produce output AND interrupt in the same execution (e.g.,\na Workflow where child A completes with output and child B interrupts).\nOn resume:\n\n- Some interrupt IDs are resolved (provided in `resume_inputs`)\n- Remaining interrupt IDs carry forward via `prior_interrupt_ids`\n- Prior output carries forward via `prior_output`\n- NodeRunner pre-populates ctx with these values before re-executing\n\n```python\nrunner = NodeRunner(\n    node=node, parent_ctx=ctx,\n    run_id=prior_run_id,  # reuse\n    prior_output=cached_output,\n    prior_interrupt_ids={'fc-2'},  # still unresolved\n)\nchild_ctx = await runner.run(\n    node_input=input,\n    resume_inputs={'fc-1': response},\n)\n```\n",".agents/skills/adk-architecture/references/architecture-context.md":"# Context\n\n## Two scoping objects\n\n- **InvocationContext** — one per invocation. Holds shared state (session,\n  services, event queue) reachable from every node.\n- **Context** — one per node execution. Holds per-node results (output, route,\n  interrupt IDs) and is the API surface node code actually touches.\n\nEvery Context references the same InvocationContext (`_invocation_context`).\nService access (artifacts, memory, auth) is delegated through it.\n\n```text\nRoot Context                      ← created by Runner from the InvocationContext\n└── Context [runner.node]         ← the root node (e.g., Workflow)\n    ├── Context [child_a]         ← child node A\n    └── Context [child_b]         ← child node B\n        └── Context [grandchild]  ← nested child\n```\n\nThe Runner creates the root Context and passes it as `parent_ctx` to the root\nnode's `NodeRunner`. The root Context exists only to be that parent; it carries\nno node path of its own.\n\nInvocationContext holds:\n\n- `session`, `agent`, `user_content`, `branch`\n- `invocation_id`, `app_name`, `user_id`\n- Services: `artifact_service`, `memory_service`, `credential_service`\n- `run_config`, `live_request_queue`\n- `is_resumable` — derived from the app's `ResumabilityConfig`\n- `_event_queue` — private queue drained by the Runner's main loop. Nodes never\n  touch it directly; they go through `ic._enqueue_event(event)`.\n\n## 1:1 node-context mapping\n\nEvery node execution gets its own Context. The relationship is strictly 1:1,\nso the Context tree mirrors the node execution tree.\n\n`NodeRunner._create_child_context()` builds the child from the parent. The\nchild inherits:\n\n- `_invocation_context` — the same object, so session and services are shared\n  (copied with `model_copy` when the child runs on a sub-branch)\n- `node_path` — parent path plus `name@run_id`\n- `run_id` — a sequential counter string per node path, reused on resume\n- `event_author`\n- the enclosing Workflow's dynamic-node scheduler\n\nThe child does not inherit output, route or interrupt IDs — those are\nper-execution results and start empty, unless a resume carries them forward via\n`prior_output` / `prior_interrupt_ids`.\n\n## Node result properties\n\nThese are how a node hands results back to its parent:\n\n- **`ctx.output`** — the node's result. Set once per execution, either by\n  `yield value` (the framework assigns it) or by `ctx.output = X`. A second\n  write raises `ValueError`.\n- **`ctx.route`** — routing value for conditional edges, `RouteValue` or a\n  list of them. Independent of output. Workflow-specific.\n- **`ctx.interrupt_ids`** — read-only set. The framework fills it when the node\n  yields an Event carrying `long_running_tool_ids`; the getter returns a copy.\n\nOutput and interrupts can coexist in one execution — a Workflow whose child A\nfinished while child B is waiting has both. The orchestrator's `_finalize`\ndecides what to propagate upward.\n\n## Class hierarchy\n\n```text\nReadonlyContext\n  └── Context\n```\n\n**ReadonlyContext** — read-only view handed to callbacks and plugins:\n`user_content`, `invocation_id`, `agent_name`, `session`, `user_id`,\n`run_config`, and `state` as an immutable `MappingProxyType`.\n\n**Context** — full read-write context for node execution.\n\n## Property reference\n\n| Category | Members |\n|---|---|\n| State & actions | `state` (mutable `State`), `actions` (`EventActions`), `custom_metadata` |\n| Node results | `output`, `route`, `interrupt_ids` (read-only) |\n| Node identity | `node`, `parent_ctx`, `node_path`, `run_id`, `attempt_count`, `resume_inputs` |\n| Scoping | `branch`, `isolation_scope` |\n| Errors | `error`, `error_node_path` — set by NodeRunner when the node fails and retries are exhausted |\n| Telemetry | `telemetry_context` |\n| Session | `session`, `get_invocation_context()` |\n| Child execution | `run_node()` |\n| Artifacts | `load_artifact()`, `save_artifact()`, `get_artifact_version()`, `list_artifacts()` |\n| Memory | `search_memory()`, `add_session_to_memory()`, `add_events_to_memory()`, `add_memory()` |\n| Auth | `request_credential()`, `load_credential()`, `save_credential()`, `get_auth_response()` |\n| Tools | `request_confirmation()`, `tool_confirmation`, `function_call_id` |\n| UI | `render_ui_widget()` |\n",".agents/skills/adk-architecture/references/architecture-llm-context-orchestration.md":"# LLM context orchestration from events\n\n## Source versus view\n\nThe event stream and the LLM context are not the same thing:\n\n- **Events are the ground truth.** Immutable records of what happened — user\n  messages, model responses, tool calls, results. They are the audit log and\n  the persistence state.\n- **LLM context is an orchestrated view.** What gets sent to a model is not a\n  dump of the event log. It is filtered and transformed for the role, task and\n  branch of the agent currently running.\n\nTreating the two as interchangeable is the root of most \"why did the model see\nthat?\" bugs.\n\n## Delegation\n\nA coordinator hands work to a sub-agent with the `transfer_to_agent` tool,\nwhich sets `actions.transfer_to_agent` on the event rather than calling the\nsub-agent inline. The alternative is `AgentTool`, which wraps an agent so it is\ninvoked as an ordinary tool and returns its result to the caller.\n\nEither way the sub-agent does not inherit the coordinator's full transcript\nverbatim; it is given the task framing plus whatever history its branch\nexposes.\n\n## Branch isolation\n\nEvents from every node and branch share one session, in chronological order.\nIsolation comes from the `branch` field, a dot-separated path built by\n`_BranchPath`: `create_sub_branch('parent', name='child', run_id='1')` yields\n`'parent.child@1'`. A node running on a sub-branch sees only events on its own\npath, which is what keeps parallel siblings from polluting each other.\n\n`Event.isolation_scope` is a separate, coarser tag — NodeRunner stamps it from\n`ctx.isolation_scope` when the event does not carry one already.\n\n## History trimming and compaction\n\nLong histories overflow the context window and drag stale retry loops back into\nthe prompt. When the app sets `events_compaction_config`, the Runner compacts\nevents after the invocation. Because compaction rewrites aged history, do not\nstore transient status on an event and expect to read it back later.\n",".agents/skills/adk-architecture/references/architecture-node-runner.md":"# NodeRunner\n\n`NodeRunner` is the per-node executor. It creates the child Context, drives\n`BaseNode.run()`, opens the node's span, enriches and enqueues events, retries\non failure, and returns the child Context to the caller.\n\n## Two communication channels\n\n- **Context** — parent ↔ child. Output, route, state, resume inputs and\n  interrupt IDs flow through `ctx`. The orchestrator reads `ctx` after the\n  child finishes to decide what happens next.\n- **Event** — persistence and streaming. Events are appended to the session and\n  streamed to the caller. They carry message content, state deltas, function\n  calls and interrupt markers.\n\nA node writes to `ctx` to talk to its parent. It yields Events to persist data\nand stream to the user.\n\n## Execution flow\n\n```text\nOrchestrator\n  │\n  ├─ NodeRunner(node=child, parent_ctx=ctx)\n  │    │\n  │    ├─ _create_child_context()       → child Context (attempt_count)\n  │    ├─ start_as_current_node_span()  → ctx._telemetry_context\n  │    ├─ _execute_node()               → iterate node.run()\n  │    │    ├─ _track_event_in_context()    → write results to ctx\n  │    │    └─ _enqueue_event()             → enrich + persist\n  │    ├─ _flush_output_and_deltas()    → emit deferred output/route/deltas\n  │    └─ return child ctx\n  │\n  └─ reads ctx.output, ctx.route, ctx.interrupt_ids, ctx.error\n```\n\n1. **Create child Context.** Shares the InvocationContext, builds `node_path`\n   from the parent, assigns `run_id`, records `attempt_count`. If the session\n   already holds events for this node path, resolved responses are rehydrated\n   into `ctx._resume_inputs` before the node runs.\n\n2. **Open the span** via `node_tracing.start_as_current_node_span`, storing the\n   resulting `TelemetryContext` on `ctx`. Opening it inside the `try` is\n   deliberate — exceptions are recorded on the span.\n\n3. **Iterate `node.run()`.** For each yielded Event:\n\n   - **Track in context** — `_track_event_in_context` copies output, route and\n     `long_running_tool_ids` onto `ctx`, which is the source of truth. Route\n     and `transfer_to_agent` are only picked up from *native* events (no\n     author, or authored by this node), so a composite parent does not\n     re-bubble a decision its own sub-agent already handled. The event ID is\n     also registered on the node's span.\n   - **Enrich** — `_enrich_event` stamps `author` (`ctx.event_author` or the\n     node name), `invocation_id`, `node_info.path`, `branch`, and\n     `isolation_scope`. For an event carrying output it also sets\n     `node_info.output_for` to this node path plus its output ancestors.\n     `node_info.run_id` is **not** stamped separately — it is a property\n     derived from the last segment of `node_info.path` (`wf@1/child@2`).\n   - **Flush deltas** — for non-partial events, pending state and artifact\n     deltas move from `ctx.actions` onto the event.\n   - **Enqueue** — `ic._enqueue_event(event)` puts it on the shared queue for\n     session persistence.\n\n4. **Flush deferred output.** If `ctx.output` or `ctx.route` were set directly\n   rather than yielded, `_flush_output_and_deltas` emits one final Event after\n   `_run_impl` returns, bundling any remaining deltas onto it.\n\n5. **Return the child ctx.** The orchestrator reads `output`, `route`,\n   `interrupt_ids` and `error`.\n\n## Timeouts, retries and errors\n\n- `node.timeout` wraps the iteration in `asyncio.wait_for`; a timeout raises\n  `NodeTimeoutError`.\n- On any exception, NodeRunner enqueues an Event with `error_code` and\n  `error_message`, then consults `node.retry_config`. A retry sleeps for the\n  configured delay, increments `attempt_count` and rebuilds the child Context\n  from scratch. Retry count is **not** persisted, so it does not survive a\n  resume.\n- A child started with `ctx.run_node()` that fails reaches the node as\n  `DynamicNodeFailError`. The child's runner already emitted the error event,\n  so NodeRunner only matches the retry policy against the child's error. A\n  node with a `retry_config` copies the run-id counters under it at the start\n  of every attempt (`_snapshot_dynamic_children`), and a retry for such a\n  failure restores that copy (`_reset_dynamic_children_for_retry`). The retry\n  then hands out the same run ids and replays the children the failed attempt\n  completed, at any depth. A retry for the node's own exception keeps the\n  counters, so its children run again.\n- A parallel worker (`_ParallelWorker`, or a `Node` with\n  `parallel_worker=True`) is not retried for an item's `DynamicNodeFailError`\n  (`_delegates_item_retry`): the item node already applied the retry policy\n  to that item.\n- A `Workflow` reports a failing child by setting `ctx.error` rather than\n  raising, and NodeRunner applies the retry policy to that error as well.\n- When retries are exhausted, the error is recorded on `ctx.error` and\n  `ctx.error_node_path` and the Context is returned normally — NodeRunner does\n  not re-raise to the orchestrator.\n- `NodeInterruptedError` from a dynamic child is swallowed here: the child's\n  interrupt IDs are already on `ctx`, so the caller just reads\n  `ctx.interrupt_ids`.\n- When `abort_signal` is triggered (`ic.is_aborted` is `True`), active node\n  tasks are cancelled without retrying. In `_node_runner_utils`, the outer\n  workflow runner loop catches the cancellation, calls\n  `runner._synthesize_abort_events_if_needed(ic)` to emit `INVOCATION_ABORTED`\n  events (and seal any dangling `FunctionCall`s), and finishes the generator\n  cleanly so `after_run` callbacks still run.\n\n## Output delegation (`use_as_output`)\n\nWhen a child is scheduled with `use_as_output=True`, its output Event also\ncounts as the parent's output. NodeRunner sets `ctx._output_delegated`, drops\nthe output field from the parent's own event (keeping the event if it still\ncarries deltas), stamps `node_info.output_for` with the ancestor paths, and\npublishes non-tool output events on the outermost explicit claiming ancestor's\nbranch (leaving tool responses such as `finish_task` paired on the child's\nbranch).\n",".agents/skills/adk-architecture/references/architecture-observability.md":"# Observability\n\nNode tracing lives in `telemetry/node_tracing.py`. Spans are **scope-based**:\n`NodeRunner` opens one around each node execution with an async context\nmanager, so the span closes on the way out even if the node raises.\n\n```python\nasync with node_tracing.start_as_current_node_span(parent_ctx, node) as tel_ctx:\n  ctx._telemetry_context = tel_ctx\n  await self._execute_node(ctx, node_input)\n```\n\n## TelemetryContext, not a raw span\n\nEach `Context` exposes `ctx.telemetry_context`, a frozen dataclass:\n\n| Member | Purpose |\n|---|---|\n| `otel_context` | OTel context holding the current span. Passed to children so their spans parent correctly. |\n| `add_event(event)` | Records an event ID as belonging to this node's span. |\n\nThe parent's `otel_context` is what makes the span tree mirror the node tree —\n`start_as_current_node_span` passes `context.telemetry_context.otel_context` as\nthe explicit parent rather than relying on whatever OTel considers current.\nInside the span the child builds a fresh `TelemetryContext` from\n`context_api.get_current()`.\n\nWhen attaching OTel context by hand (`context_api.attach()`), pair it with\n`detach()` in a `finally` — an unbalanced attach leaks the context into\nwhatever coroutine runs next on the loop.\n\n## Which span a node gets\n\n`start_as_current_node_span` dispatches on node type:\n\n| Node type | Span |\n|---|---|\n| `BaseAgent` subclass | none of its own — passes through, the agent emits its own `invoke_agent {name}` span |\n| `Workflow` | `invoke_workflow {name}` |\n| anything else | `invoke_node {name}` |\n\n`invoke_agent` follows OpenTelemetry semantic conventions v1.36 for backward\ncompatibility; `invoke_workflow` follows v1.41; `invoke_node` is not in any\nsemconv release yet.\n\n## Attributes\n\n| Attribute | Set on | Value |\n|---|---|---|\n| `gen_ai.operation.name` | all | `\"invoke_workflow\"` / `\"invoke_node\"` |\n| `gen_ai.conversation.id` | all | `ctx.session.id` |\n| `gen_ai.workflow.name` | workflow | the workflow's `name`, when non-empty |\n| `gen_ai.workflow.nested` | workflow | `True` only for a nested workflow; the entrypoint workflow omits the attribute entirely |\n| `gcp.vertex.agent.associated_event_ids` | all | IDs collected via `tel_ctx.add_event()`, stamped on span close when non-empty and the span is recording |\n| `adk.experimental.response.source` | `execute_tool`, `call_llm` | Callback provenance when a callback or plugin answered or replaced the result and `ADK_EXPERIMENTAL_TELEMETRY=true` is set (`\"before_tool_callback\"`, `\"after_tool_callback\"`, `\"on_tool_error_callback\"` on `execute_tool`; `\"before_model_callback\"`, `\"on_model_error_callback\"` on `call_llm`); omitted when the tool or model itself answered |\n\nNesting is detected through an OTel context key set by the first workflow in\nthe invocation. Because the key rides on the propagated `otel_context`, an\nagent-as-tool that spins up its own runner still reports `nested=true`.\n\n## Metrics\n\nRecorded from `telemetry/_metrics.py` under the `gcp.vertex.agent` meter:\n\n| Function | Recorded when |\n|---|---|\n| `record_workflow_invocation_duration` | an `invoke_workflow` span closes, tagged with `nested` and any error |\n| `record_agent_invocation_duration` | an agent invocation completes |\n| `record_invoke_agent_inference_calls` | per agent, count of model calls |\n| `record_invoke_agent_tool_calls` | per agent, count of tool calls |\n| `record_tool_execution_duration` | a tool finishes |\n| `record_client_operation_duration`, `record_client_token_usage` | model client calls |\n\n## Python logging\n\nUse the `google_adk` logger namespace so callers can filter ADK output:\n\n```python\nlogger = logging.getLogger(\"google_adk.\" + __name__)\n\nlogger.debug(\"node %s started.\", ctx.node_path)\n```\n\nUse `%`-style arguments, not f-strings — the formatting is then skipped\nentirely when the level is disabled.\n\n`NodeRunner` already logs node start, node end, execute-loop boundaries,\nrehydrated resume inputs, retries (`warning`) and unhandled exceptions\n(`logger.exception`). Do not re-log those from inside a node.\n",".agents/skills/adk-architecture/references/architecture-runner-roles.md":"# Runner vs NodeRunner vs Workflow\n\nThese three are deliberately separate:\n\n- **Runner** = lifecycle orchestrator (InvocationContext, session,\n  plugins, invocation boundaries)\n- **NodeRunner** = task scheduler (asyncio tasks, node execution,\n  completions)\n- **Workflow** = graph engine (edges, traversal, node sequencing)\n\nMerging Runner and NodeRunner would deadlock on nested workflows\n(inner workflow's NodeRunner would block the outer's Runner).\n",".agents/skills/adk-architecture/references/architecture-workflow-resumability.md":"# Workflow resumability: model and direction\n\nHow a `Workflow` node preserves and restores execution state across a\nhuman-in-the-loop pause, how that compares to peer agent frameworks, and where\nit is heading. This is the whole-workflow view; the interrupt/resume lifecycle\nof a single node is covered separately.\n\nThe first thing to be clear about: a `Workflow` reconstructs its progress from\nthe session event stream on every run, so it resumes whether or not resumability\nis configured. The `is_resumable` flag does not switch resume on or off. What it\nswitches on is **durability** — persisting loadable checkpoints and letting an\ninvocation be continued across separate runner calls.\n\n## Current model\n\n### Resume is always on: reconstruction by event replay\n\nOn every run, the workflow scans the current invocation's session events and\nrebuilds its in-memory loop state (which nodes completed, their outputs, which\nare still waiting on interrupts). Completed nodes are fast-forwarded from their\nrecovered output rather than re-executed; the interrupted node re-runs with the\nsupplied responses.\n\nThis path (`_run_impl` -> `ReplayManager.scan_workflow_events`) has no\n`is_resumable` guard — the scan matches events purely by invocation id. The loop\nstate is not persisted and the checkpoint the workflow writes is not read back\non the load path; the session event log is the source of truth. So within an\ninvocation, a workflow is inherently replay-resumable, flag or no flag.\n\n### What `is_resumable` actually adds: durability\n\n`ResumabilityConfig.is_resumable` is a durability switch. When it is set:\n\n1.  **Cross-call resume.** The runner will continue an existing invocation\n    without a fresh user message and set up a \"resumed invocation\" context,\n    rehydrating the recorded agent state and end-of-agent markers from history.\n    With the flag off, the runner requires a new message and starts a fresh\n    invocation instead.\n2.  **Checkpoint markers in the log.** Composite agents (`LlmAgent`,\n    `SequentialAgent`, `LoopAgent`, `ParallelAgent`, and `LlmAgent`s wrapped as\n    workflow nodes) write `agent_state` / `end_of_agent` events into the session\n    only when the invocation is resumable. These make progress loadable across a\n    process boundary.\n3.  **Function-response routing.** Routing an incoming function response back to\n    its originating invocation is enabled only when resumable.\n\nThe config's own definition is durability-shaped: pause an invocation on a\nlong-running call, and resume it from the last event if it paused or failed\nmidway, best-effort and at-least-once, with in-memory state lost. So the\naccurate statement is: the flag decides whether progress is persisted as\nloadable checkpoints and whether an invocation can be resumed across runner\ncalls — not whether the workflow can resume. Resumability here is really\ndurability.\n\n### The `Workflow` node writes a checkpoint, but does not read one\n\n`Workflow._emit_node_checkpoint` does persist a node-status snapshot when\n`ic.is_resumable` — an `agent_state` event carrying `{\"nodes\": {name: {status,\ninterrupts}}}` for each static child (`resume_inputs` is intentionally omitted\nfrom the checkpoint). Two sibling markers are\nwritten under the same flag: `_maybe_reemit_replayed_output` re-surfaces a\nfast-forwarded node's output, and `_emit_end_of_agent` records that the\nworkflow ran to completion.\n\nWhat is missing is the other half. Nothing loads that checkpoint: setup still\nbegins with `ReplayManager.scan_workflow_events` over the whole invocation, and\n`_run_impl` carries a `TODO: resume from checkpoint event`. So the checkpoint\nis currently an observable record of progress rather than the durable state\nresume actually runs off. Resume cost still scales with history length.\n\n## How peer frameworks do it\n\nEvery mainstream agent framework persists a **state snapshot with a position\ncursor** and, on resume, **loads that snapshot** — none reconstruct by replaying\nthe entire history.\n\nFramework                | Durable unit                                                        | Position cursor                                                                    | Resume\n------------------------ | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------\nLangGraph (graph)        | `StateSnapshot` per super-step in a pluggable checkpointer          | `next` nodes + parent-pointer chain + per-task pending writes                      | re-invoke same thread id; load latest checkpoint, re-run only the interrupted node\npydantic-graph (graph)   | `NodeSnapshot{state, node, status}` via a state-persistence backend | the snapshot's `node` = next node to run; `status` created/pending/running/success | `iter_from_persistence()` loads the next `created` snapshot\nOpenAI Agents SDK (loop) | serialized `RunState` blob                                          | run cursor inside the state; correlate by tool-call id                             | deserialize the state, apply approvals, resume the run\nPydantic AI (loop)       | message history + deferred-tool results                             | implicit in the transcript; correlate by tool-call id                              | new run over the prior message history\n\nADK's durable unit today is the event log itself, and resume is by replay over\nit. Two patterns from the peers are worth copying, both consistent across them:\n\n-   **A snapshot is the source of truth for resume.** The runtime writes a\n    snapshot as it advances and reads the latest one to continue. Resume cost is\n    bounded by the snapshot size, not by history length.\n-   **Resume re-runs the paused unit from its start** (LangGraph and\n    pydantic-graph both re-execute the whole node, not a saved program counter),\n    which keeps the durable state small and pushes an idempotency contract onto\n    the node author — the same at-least-once contract ADK already documents.\n\n## Direction: load the checkpoint instead of replaying\n\nThe write half exists. The remaining step — peer-aligned, and the one ADK's own\ncomposite agents already take — is to make that checkpoint the thing resume\nreads:\n\n-   On resume, seed the loop state from the most recent `agent_state`\n    checkpoint instead of scanning the invocation's events, then continue:\n    re-run only the interrupted node and dispatch newly-ready successors.\n-   This makes resume cost independent of history length and unifies the\n    `Workflow` node with composite agents and with LangGraph / pydantic-graph /\n    the OpenAI SDK.\n\nThis only applies when durability is on. Without `is_resumable` nothing is\npersisted, and the workflow continues to resume within an invocation by replay.\n\n## Open considerations\n\n-   **Payload completeness.** A workflow checkpoint must carry (or be able to\n    recover) each completed node's output, run id, and branch — the equivalent\n    of LangGraph's per-task pending writes — to fully replace event replay.\n-   **Partial interrupt resolution.** A node with several interrupts may receive\n    only some responses on a resume. The re-run-vs-wait behavior differs between\n    an orchestrating node (re-run to dispatch the resolved branch) and a leaf\n    node (wait for all), keyed on `rerun_on_resume`. This is decided in the\n    shared replay-interception logic and should be settled before a load path\n    relies on it.\n-   **Versioning.** Long-lived paused runs can outlive a code change. A version\n    marker on the checkpoint lets a resume route to a compatible code path (the\n    OpenAI SDK makes this an explicit recommendation).\n-   **Serialization.** Keep payloads JSON-serializable (Pydantic `model_dump`),\n    so any persistence backend works and no code objects are serialized; node\n    objects are rebound from the in-memory graph definition on resume.\n",".agents/skills/adk-architecture/references/interface-agent.md":"# Agent\n\n`Agent` is a type alias for `LlmAgent` (`Agent: TypeAlias = LlmAgent`), the\nmodel-backed agent most users instantiate. It is not the same thing as\n`BaseAgent`, which is the abstract base every agent derives from.\n\n`BaseAgent` extends `BaseNode`, so an agent is a node: it can stand alone under\na Runner or sit inside a Workflow graph.\n\n## Key fields\n\n- **`name`** — unique identifier within the agent tree. Validated: must be a\n  valid Python identifier, and `\"user\"` is rejected because it is reserved for\n  end-user input.\n- **`description`** — capability description the model uses when choosing\n  which sub-agent to delegate to.\n- **`sub_agents`** — child agents. Names must be unique across the tree;\n  each child's `parent_agent` is wired up automatically.\n- **`before_agent_callback` / `after_agent_callback`** — intercept the agent\n  lifecycle.\n\n## Entrance methods\n\n| Method | Use |\n|---|---|\n| `run_async(parent_context)` | Text conversation. Yields `Event`s. Runs the before/after callbacks, the error callback, and invocation instrumentation around `_run_async_impl`. |\n| `run_live(parent_context)` | Video/audio conversation. Marked `@final` — override `_run_live_impl` instead. |\n| `run(ctx=..., node_input=...)` | Inherited from `BaseNode`, `@final`. This is what a Workflow calls when the agent is a graph node; it routes into `_run_impl`, which for `BaseAgent` delegates to `run_async`. |\n\nWhich one you call depends on the caller, not on age: a Workflow calls `run()`,\ndirect text callers use `run_async()`. Nothing in the source marks `run_async`\ndeprecated, and it remains the path every agent's real logic runs through.\n\n## Other methods\n\n- **`clone(...)`** — copy the agent, detached from its parent.\n- **`find_agent(name)` / `find_sub_agent(name)`** — search the agent tree.\n- **`root_agent`** — walk up to the top of the tree.\n- **`from_config(config, config_abs_path)`** — build an agent from a config\n  object. Both `@deprecated` and `@experimental`; do not build on it.\n",".agents/skills/adk-architecture/references/interface-base-agent.md":"# BaseAgent\n\n`BaseAgent` is the abstract base for every agent. It extends `BaseNode`, so an\nagent is a node with agent-specific lifecycle on top: callbacks, error\nhandling, invocation instrumentation, and an agent tree.\n\n## What to override\n\nOverride **`_run_async_impl(ctx)`** for text conversation, and\n**`_run_live_impl(ctx)`** for live audio/video. Both receive an\n`InvocationContext`. Every built-in composite agent — `LlmAgent`,\n`SequentialAgent`, `LoopAgent`, `ParallelAgent` — implements these two and\nnothing else.\n\nDo **not** override `_run_impl`. `BaseAgent` already overrides it (marked\n`@override`) as the bridge from node execution into `run_async`, which is what\napplies the before/after callbacks, the error callback and the invocation\nmetrics. Replacing it silently drops all of that.\n\n```text\nWorkflow calls node.run()          (BaseNode, @final)\n  └─ BaseAgent._run_impl           (bridge — do not override)\n      └─ BaseAgent.run_async       (callbacks, instrumentation, error handling)\n          └─ your _run_async_impl  ← override point\n```\n\n`LlmAgent` is the exception that proves the rule: it overrides `_run_impl` too,\nin order to run through a dedicated node wrapper. That is framework-internal.\n\n## Key attributes to configure\n\n- **`name`** — must be a valid Python identifier, unique within the agent tree,\n  and cannot be `\"user\"`.\n- **`description`** — capability description used by the model for delegation.\n- **`sub_agents`** — child agents for hierarchical delegation. Duplicate names\n  across the tree are rejected at validation time.\n- **`before_agent_callback` / `after_agent_callback`** — lifecycle hooks. Both\n  accept a list; the canonical forms are exposed as\n  `canonical_before_agent_callbacks` / `canonical_after_agent_callbacks`.\n\n## Author attribution\n\nWhen an agent runs as a workflow node, `_run_impl` copies each event's author\nonto `ctx.event_author` so the enclosing NodeRunner does not overwrite it with\nthe parent workflow's name. Events therefore stay attributed to the agent that\nactually produced them.\n",".agents/skills/adk-architecture/references/interface-base-node.md":"# BaseNode\n\nBaseNode is the primitive unit of execution in the workflow runtime.\nEvery computation — LLM calls, tool execution, orchestration — is\na node. It is a Pydantic `BaseModel` subclass.\n\n## The node contract\n\nEvery node follows a two-method pattern:\n\n- `run()` is `@final` — normalizes yields to Events. Never override.\n- `_run_impl()` is the extension point — subclasses implement their\n  logic here as an async generator.\n\n```python\nclass MyNode(BaseNode):\n    async def _run_impl(self, *, ctx, node_input):\n        result = do_work(node_input)\n        yield result  # becomes Event(output=result)\n```\n\n**Why this split:** `run()` guarantees consistent normalization\nregardless of what the subclass does. The subclass only thinks\nabout its domain logic.\n\n**Normalization rules** (`run()` applies these to each yield):\n\n- `None` → skipped\n- `Event` → pass through\n- `RequestInput` → interrupt Event\n- any other value → `Event(output=value)`\n\n**Generator conventions:**\n\nA node can yield three types of data:\n\n- **Output** — the node's result value. Flows between nodes\n  (parent reads `ctx.output` after child completes). At most one\n  per execution (second raises `ValueError`).\n- **Message** — user-visible content streamed to the end user\n  (e.g., progress text, partial responses). Multiple allowed.\n- **Route** — Workflow-specific concept. Triggers conditional\n  edges in the graph. Set via `ctx.route` or `event.actions.route`.\n\nAdditional rules:\n\n- Yielding nothing produces no output event\n- `yield None` is silently skipped\n\nA custom node interacts with the runtime through two arguments:\n\n- **`ctx`** (Context) — communicate results to the parent node\n- **`node_input`** — data passed by the parent/orchestrator\n\n## Output and streaming\n\nThree ways to produce output (pick one per execution):\n\n```python\n# 1. Yield a value (most common)\nasync def _run_impl(self, *, ctx, node_input):\n    yield compute(node_input)\n\n# 2. Set ctx.output directly\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.output = compute(node_input)\n    return\n    yield  # generator contract\n\n# 3. Yield an Event with output\nasync def _run_impl(self, *, ctx, node_input):\n    yield Event(output=compute(node_input))\n```\n\nA second output raises `ValueError` — at most one per execution.\n\n**Streaming messages** — yield Events with `message` to send\nuser-visible text (`message` is an alias for `content` on Event):\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    yield Event(message='working...')\n    yield final_result  # this is the output\n```\n\n## State and routing\n\n**Mutating state:**\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.state['key'] = 'value'  # recorded as state_delta\n    yield result\n```\n\n**Setting route for conditional edges:**\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.route = 'approve' if score > 0.8 else 'reject'\n    yield node_input\n```\n\n## Advanced: child nodes and HITL\n\n**Running child nodes** via `ctx.run_node()`:\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    child_result = await ctx.run_node(some_node, node_input)\n    yield f'child said: {child_result}'\n```\n\nRequires `rerun_on_resume = True` on the calling node.\n\n**Requesting interrupt (HITL):**\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    if ctx.resume_inputs and 'fc-1' in ctx.resume_inputs:\n        yield f'approved: {ctx.resume_inputs[\"fc-1\"]}'\n        return\n    yield Event(long_running_tool_ids={'fc-1'})\n```\n\n## Configuration reference\n\n| Field | Type | Default | Purpose |\n|---|---|---|---|\n| `name` | `str` | required | Unique identifier. Validated: must be a valid Python identifier. |\n| `description` | `str` | `''` | Human-readable description |\n| `rerun_on_resume` | `bool` | `False` | Re-execute from scratch on resume (required for `ctx.run_node()`). When `False` the node completes immediately using the resume input as its output. |\n| `wait_for_output` | `bool` | `False` | Stay WAITING until output *or* route is yielded, so predecessors can re-trigger the node (join nodes). Never yielding either deadlocks the node. |\n| `retry_config` | `RetryConfig \\| None` | `None` | Retry on failure. Not persisted, so the count restarts after a resume. |\n| `timeout` | `float \\| None` | `None` | Max execution time in seconds; exceeding it raises `NodeTimeoutError`, which `retry_config` can retry. |\n| `input_schema` | `SchemaType \\| None` | `None` | Validate/coerce input data before `run()` |\n| `output_schema` | `SchemaType \\| None` | `None` | Validate/coerce output data |\n| `state_schema` | `type[BaseModel] \\| None` | `None` | Declare expected `ctx.state` keys and types; mutations are validated at runtime. Children inherit it unless they declare their own. Prefixed keys (`app:`, `user:`, `temp:`) bypass validation. |\n\n`START` (in the same module) is a sentinel `BaseNode` marking a graph's entry\npoint. It is never executed — the Workflow seeds triggers for its successors\ndirectly.\n",".agents/skills/adk-architecture/references/interface-event.md":"# Event\n\n`Event` is the record of one thing that happened in a session — a message, a\ntool call, a state change, a node's output. It is the unit of persistence and\nthe substrate every resume path reads. It extends `LlmResponse`, so response\nfields (`content`, `partial`, `error_code`, `error_message`, ...) are Event\nfields too.\n\n## Key fields\n\n| Field | Meaning |\n|---|---|\n| `invocation_id` | Invocation this event belongs to. Non-empty before appending to a session. |\n| `author` | `'user'` or the name of the agent/node that appended it. |\n| `content` | The message payload, inherited from `LlmResponse`. |\n| `actions` | `EventActions` — `state_delta`, `artifact_delta`, `route`, `transfer_to_agent`, `escalate`, `agent_state`, `end_of_agent`, requested auth configs and tool confirmations. Function calls live in `content`, not here. |\n| `output` | Generic data output from a workflow node. |\n| `node_info` | `NodeInfo`: `path` (e.g. `wf@1/child@2`), `output_for`, `message_as_output`. |\n| `long_running_tool_ids` | IDs of long-running calls. Setting these is what makes the framework treat the event as an interrupt. |\n| `branch` | Dot-separated branch path for isolating peer sub-agents' history. |\n| `isolation_scope` | Scope tag for which logical context this event belongs to. |\n| `id`, `timestamp` | Identity and time. |\n\n`NodeInfo.run_id` is a **derived property**, not a stored field: it is parsed\noff the last `name@run_id` segment of `node_info.path`. Nothing stamps it\nseparately, so a path without an `@` yields an empty run id.\n\n## Convenience kwargs\n\nThe constructor routes four shorthand kwargs onto nested fields, so these are\nequivalent to writing the nested form:\n\n| Kwarg | Lands on |\n|---|---|\n| `message=` | `content` (converted via the genai content transformer) |\n| `state=` | `actions.state_delta` |\n| `route=` | `actions.route` |\n| `node_path=` | `node_info.path` |\n\nPassing both `message` and `content` raises `ValueError`.\n\n## Methods\n\n- `get_function_calls()` / `get_function_responses()` — inherited from\n  `LlmResponse`; pull the function calls or responses out of `content`.\n- `is_final_response()` — whether this is an agent's final response.\n- `has_trailing_code_execution_result()` — whether the content ends in a code\n  execution result.\n- `message` — property aliasing `content`, with a matching setter.\n- `node_name` — the node name parsed off `node_info.path`.\n\n## State lifecycle and immutability\n\n- **Events are immutable once saved.** Never assume an event is mutated or\n  cleared after it lands in a session; resume works by reading the log\n  forward, not by rewriting it.\n- **Signals resolve by later events, not by edits.** To decide whether a\n  request is pending or resolved, look for the matching later event (the\n  function response), not a flag flipped on the original.\n- **Aborted invocations seal dangling calls.** When an invocation is cancelled\n  via `abort_signal`, the runner appends a terminal event with\n  `error_code='INVOCATION_ABORTED'` carrying synthetic `FunctionResponse`\n  parts for any unclosed `FunctionCall`s so later turns see a balanced log.\n- **Beware stateful flags on events.** Background compaction may rewrite or\n  drop aged events, so a transient status stored on one can survive or vanish\n  in ways you did not intend.\n",".agents/skills/adk-architecture/references/interface-runner.md":"# Runner\n\n`Runner` is the public entry point for executing agents and workflows. It owns\nthe invocation lifecycle: building the `InvocationContext`, draining the event\nqueue onto the session, and wiring in the artifact, session, memory and\ncredential services plus the plugin manager.\n\n`InMemoryRunner` is the batteries-included subclass that supplies in-memory\nservices; it accepts either an `agent=` or a `node=`.\n\n## Entrance methods\n\n### `run_async`\n\nThe main asynchronous entry point. Use this in production.\n\n- Yields events as they are produced; does not block concurrent calls for other\n  queries.\n- Runs event compaction after the invocation when the app has\n  `events_compaction_config` set.\n\n| Argument | Meaning |\n|---|---|\n| `user_id` | User ID of the session. |\n| `session_id` | Session ID. |\n| `invocation_id` | Set to resume an interrupted invocation. |\n| `new_message` | Message to append to the session. Optional — omit it when resuming. |\n| `state_delta` | State changes to apply to the session. |\n| `run_config` | Run config for the agent. |\n| `yield_user_message` | Yield the user-message event before agent/node events. |\n| `abort_signal` | Optional `asyncio.Event` for cooperative cancellation. Setting it halts the active invocation cleanly. |\n\n### `run`\n\nSynchronous convenience wrapper for local testing: runs the async path on a\nbackground thread and re-yields its events. Takes `user_id`, `session_id`,\n`new_message`, `state_delta` and `run_config` — no `invocation_id`, so it\ncannot resume.\n\n### `run_live`\n\nAudio/video streaming entry point, driven by a `LiveRequestQueue`\n(`from google.adk.live import LiveRequestQueue`) rather than a single\n`new_message`.\n\n### `run_debug`\n\nConvenience harness for local iteration: takes one message or a list of\nmessages, creates the session if needed, and prints the exchange (`quiet` and\n`verbose` control how much).\n\n## Execution cancellation (`abort_signal`)\n\nWhen `abort_signal` is passed to `run_async` and set during execution:\n\n- `InvocationContext.is_aborted` flips to `True` and active agent or workflow\n  tasks are cancelled cleanly without surfacing an unhandled `CancelledError` to\n  the caller.\n- `Runner._synthesize_abort_events_if_needed` appends and yields synthetic\n  abort `Event`s with `error_code='INVOCATION_ABORTED'`. When function calls are\n  dangling, it emits one abort event per `(author, branch, isolation_scope)`\n  tuple sealing those calls with synthetic `FunctionResponse(response={'error':\n  'Invocation was aborted by client.'})` parts so each response pairs with its\n  call in the issuing agent's view. If nothing is dangling, it yields a single\n  root-authored abort event instead. This preserves session log invariants so\n  that subsequent conversational turns or resumes do not fail on dangling tool\n  calls or re-run the cancelled tool.\n- `PluginManager.run_after_run_callback` still executes before `run_async`\n  exits.\n- In the FastAPI server (`/run_sse`), client HTTP disconnects automatically set\n  `abort_signal` on the active runner invocation.\n",".agents/skills/adk-architecture/references/interface-workflow.md":"# Workflow\n\nWorkflow is a graph-based orchestration node. It extends BaseNode\nand implements `_run_impl()` as a scheduling loop that drives static\ngraph nodes and tracks dynamic nodes spawned by `ctx.run_node()`.\n\n## Two kinds of child nodes\n\nWorkflow manages two kinds of child nodes through a unified scheduler (`DynamicNodeScheduler`):\n\n- **Static (graph) nodes** — declared in `edges`, compiled into a\n  `Graph`. Scanned upfront into `_LoopState.recovered_executions` by\n  `ReplayManager.scan_workflow_events(ctx)`. When scheduled by the\n  orchestration loop, `_start_node_task` checks `check_interception` on any\n  recovered execution to mark `_LoopState.replayed_nodes` and pre-populate\n  `_LoopState.runs[node_path]` (`DynamicNodeRun` with `is_static=True`), then\n  delegates to `ctx._run_node_internal` (`DynamicNodeScheduler`). Tracked in\n  `_LoopState.nodes` by node name (`NodeState`).\n- **Dynamic nodes** — spawned at runtime via `ctx.run_node()` from\n  inside a graph node's `_run_impl`. Lazily rehydrated from `ReplayManager`\n  by `DynamicNodeScheduler._rehydrate_from_events` and tracked in\n  `_LoopState.runs` by full `node_path` (`DynamicNodeRun`,\n  which holds `state: NodeState`, `output`, `task`, `transfer_to_agent`,\n  `recovered_state`, and `is_static`). Managed by `DynamicNodeScheduler`.\n\nStatic and dynamic nodes share the same `DynamicNodeScheduler`, replay\ninterception (`check_interception` / `ReplayManager`), and\n`_LoopState.interrupt_ids` set, so the Workflow sees a unified view of\nexecution and all pending interrupts.\n\n## Implementing a graph node\n\nA graph node is a regular BaseNode placed in a Workflow's edges.\nIn `_start_node_task`, the Workflow checks `check_interception` against\n`_LoopState.recovered_executions` (to record `_LoopState.replayed_nodes` and\nseed `_LoopState.runs` with `is_static=True`) and dispatches\n`ctx._run_node_internal` to `DynamicNodeScheduler`, which either returns a\nreplayed mock `Context` (`create_mock_context`) or wraps the node in a\n`NodeRunner` with a child `Context`, reading `ctx.output`, `ctx.route`, and\n`ctx.interrupt_ids` after it completes.\n\n**Output** — two paths. At most one per execution. The Workflow\nreads the output to pass downstream.\n\n```python\n# Yield (persisted immediately)\nasync def _run_impl(self, *, ctx, node_input):\n    yield compute(node_input)\n\n# ctx (deferred until node end)\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.output = compute(node_input)\n    return\n    yield\n```\n\n**Routing** — two paths. The Workflow uses the route to select\nconditional edges.\n\n```python\n# Yield (persisted immediately)\nasync def _run_impl(self, *, ctx, node_input):\n    yield Event(route='approve' if node_input > 0.8 else 'reject')\n\n# ctx (deferred until node end)\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.route = 'approve' if node_input > 0.8 else 'reject'\n    yield node_input\n```\n\n**State** — two paths. `ctx.state` deltas are flushed onto the next\nyielded Event, or a final Event at node end.\n\n```python\n# Yield (persisted immediately)\nasync def _run_impl(self, *, ctx, node_input):\n    yield Event(state={'count': 1})\n\n# ctx (flushed onto next/final Event)\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.state['count'] = 1\n    yield result\n```\n\n**Interrupts** — yield only (`ctx.interrupt_ids` is read-only). The\nWorkflow marks the node WAITING and propagates the interrupt IDs\nupward. On resume, if `rerun_on_resume=True` (default for Workflow),\nthe node is re-executed with `ctx.resume_inputs` populated.\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    if ctx.resume_inputs and 'fc-1' in ctx.resume_inputs:\n        yield f'approved: {ctx.resume_inputs[\"fc-1\"]}'\n        return\n    yield Event(long_running_tool_ids={'fc-1'})\n```\n\n## Dynamic nodes via ctx.run_node()\n\nA graph node can spawn child nodes at runtime:\n\n```python\nclass Orchestrator(BaseNode):\n    rerun_on_resume: bool = True  # required\n\n    async def _run_impl(self, *, ctx, node_input):\n        result = await ctx.run_node(some_node, input_data)\n        yield f'child returned: {result}'\n```\n\n### Requirements\n\n- The calling node **must** have `rerun_on_resume = True`. Without\n  this, the Workflow cannot re-execute the node on resume to let it\n  re-acquire its dynamic children's results.\n\n### Tracking\n\nDynamic nodes are tracked by **full node_path**, not by name alone.\nEach segment is `node_name@run_id`:\n\n```text\nwf@1/graph_node_a@1/dynamic_child@1        ← dynamic node under graph_node_a\nwf@1/graph_node_a@1/dynamic_child@1/inner@1  ← transitive dynamic node\n```\n\nThe node name comes from the node's own `name` field. The run id comes from\nthe `run_id` argument to `ctx.run_node()`, or a generated counter when that\nargument is omitted. There is no `name=` parameter on `ctx.run_node()` — pass\na node whose `name` is what you want, and pass `run_id=` to pin the suffix.\n\nEach unique `node_path` is tracked in `_LoopState.runs`\n(`dict[str, DynamicNodeRun]`). This enables:\n\n- **Dedup** — if the same path is encountered again (after resume),\n  the cached output is returned without re-execution.\n- **Resume** — if the node was interrupted, its state is\n  reconstructed from session events via `ReplayManager` and `check_interception`.\n\n### Unified interception and resume protocol (DynamicNodeScheduler)\n\n`DynamicNodeScheduler.__call__` wraps single-step node execution (`_execute_step`)\nin a sequential `transfer_to_agent` loop: if a completed agent step sets\n`child_ctx.actions.transfer_to_agent`, `__call__` resolves the target agent and\nparent context (`resolve_and_derive_transfer_context`), delegates the next step\nto the target context's owning scheduler, and only preserves `use_as_output`\nwhile `curr_parent_ctx is ctx`.\n\nWithin a single step (`_execute_step`), when replay is enabled:\n- If `node_path` is not in `_state.runs` (for recovered static nodes,\n  `_start_node_task` already inserted a `DynamicNodeRun` with `is_static=True`),\n  `_rehydrate_from_events` scans `ReplayManager` for that `node_path` and\n  populates `_state.runs[node_path]` if historical events exist.\n- If `node_path` is still not in `_state.runs`, `_check_existing_run` returns\n  `(None, False)` and `_execute_step` runs the node fresh via\n  `_run_node_internal(..., is_fresh=True)` (`NodeRunner`).\n- If `node_path` is in `_state.runs`, `_check_existing_run` awaits any in-flight\n  concurrent `run.task`, or calls `check_interception(node=curr_node,\n  recovered=run.recovered_state, current_run=run)`:\n\n1. **Same-turn dedup / failed / waiting (`current_run` present and `not current_run.is_static`)** —\n   if `current_run.state.status == COMPLETED`, returns `should_run=False` with\n   `current_run.output`, `current_run.route`, and\n   `current_run.transfer_to_agent`. If `FAILED`, returns `should_run=True`\n   with `resume_inputs=recovered.resolved_responses` so a dynamic run that\n   failed in the current turn reruns instead of consulting stale recovered\n   events. If `WAITING` with `interrupts`, returns `should_run=False` with\n   those `interrupts`. (Every static dispatch gets a fresh `run_id`, so loop\n   edges never reuse a `node_path`; static runs have `is_static=True` so\n   no-outcome static nodes fast-forward in step 7 via\n   `should_run = (current_run is not None and not current_run.is_static)`.)\n\n2. **Nested `Workflow` (`isinstance(node, Workflow)`)** — before checking\n   completion or interrupts, `check_interception` immediately returns\n   `should_run=True` with `resume_inputs=recovered.resolved_responses`. A child\n   `Workflow` never fast-forwards at the parent boundary; it always re-enters\n   `_run_impl` so its own `ReplayManager` and scheduler can fast-forward or\n   resume its internal children.\n\n3. **Waiting / Partial resume (`recovered.interrupt_ids - recovered.resolved_ids` non-empty)** —\n   - If `node.rerun_on_resume` is `True` and `recovered.resolved_ids` is\n     non-empty (**partial resume**): returns `should_run=True` with\n     `resume_inputs=recovered.resolved_responses`.\n   - Otherwise: returns `should_run=False` with `interrupts=unresolved`,\n     propagating unresolved interrupt IDs to `_state.interrupt_ids` via a\n     mock `Context` (`create_mock_context`).\n\n4. **Prior failure (`recovered.error_code is not None`)** — returns\n   `should_run=True` with `resume_inputs=recovered.resolved_responses` so a\n   node that failed in a prior turn re-executes instead of fast-forwarding with\n   `None`.\n\n5. **Fast-forward (`recovered.route`, `recovered.output`, or `recovered.transfer_to_agent` present)** —\n   non-`Workflow` nodes that recorded an output, route, or agent transfer in a\n   prior turn return `should_run=False` with the rehydrated `output`, `route`,\n   and `transfer_to_agent` via `create_mock_context` (after awaiting\n   `ReplayManager.wait_sequence`) without re-executing `NodeRunner`.\n\n6. **All interrupts resolved, no output yet (`recovered.interrupt_ids` non-empty, `unresolved` empty)** —\n   - `rerun_on_resume=False`: fast-forwards (`should_run=False`), setting\n     `output` to the single value in `recovered.resolved_responses` (when\n     `len == 1`) or `dict(recovered.resolved_responses)`.\n   - `rerun_on_resume=True`: re-executes (`should_run=True`) via\n     `_run_node_internal(..., is_fresh=False)` with\n     `resume_inputs=recovered.resolved_responses`.\n\n7. **No output, route, or interrupts in recovered events** —\n   - If `node.wait_for_output` or `node.rerun_on_resume`: returns\n     `should_run=True` with `resume_inputs=recovered.resolved_responses`.\n   - Otherwise: returns `should_run = (current_run is not None and not current_run.is_static)`\n     (static nodes that completed with `None` output fast-forward with\n     `should_run=False`, while dynamic nodes with no recorded outcome\n     re-execute with `should_run=True`).\n\n### Interrupt propagation\n\nWhen a dynamic child interrupts:\n\n1. `DynamicNodeScheduler._record_result` sets the child's `DynamicNodeRun`\n   status to WAITING and adds its interrupt IDs to\n   `_LoopState.interrupt_ids`.\n2. `ctx.run_node()` checks `child_ctx.interrupt_ids`. If non-empty,\n   it propagates them to the calling node's `ctx._interrupt_ids`\n   and raises `NodeInterruptedError`.\n3. `NodeRunner` catches `NodeInterruptedError` in `_execute_node` and\n   records the interrupt on the calling node's `Context`.\n4. The Workflow's `_handle_completion` sees the interrupt and marks\n   the graph node as WAITING.\n\nOn resume, the Workflow re-executes the graph node (because\n`rerun_on_resume=True`). The graph node calls `ctx.run_node()`\nagain, which hits the scheduler. `_rehydrate_from_events` scans session\nevents for the child's prior state into a `_ChildScanState`, and\n`check_interception` reads that scanned state, finds the resolved FR, and\neither fast-forwards with cached output or re-executes the dynamic child\nwith `resume_inputs`.\n\n### ctx.run_node() options\n\n| Argument | Effect |\n|---|---|\n| `node_input` | Data handed to the child. |\n| `use_as_output` | The child's output becomes the calling node's output. |\n| `run_id` | Pins the `@run_id` suffix on the child's node path. |\n| `use_sub_branch` | Runs the child on a sub-branch so its events are isolated. |\n| `override_branch`, `override_isolation_scope` | Replace the inherited branch / scope tag. |\n| `raise_on_wait` | Defaults to `False`. If `True`, raises `NodeInterruptedError` when a child `Workflow` or `wait_for_output=True` node finishes with `output=None` instead of returning `None`. |\n\n### Output delegation (use_as_output)\n\n`ctx.run_node(node, use_as_output=True)` makes the dynamic child's\noutput count as the calling node's output:\n\n```python\nclass Delegator(BaseNode):\n    rerun_on_resume: bool = True\n\n    async def _run_impl(self, *, ctx, node_input):\n        # child's output becomes this node's output\n        await ctx.run_node(worker, node_input, use_as_output=True)\n```\n\n- Sets `ctx._output_delegated = True` on the parent\n- NodeRunner stamps `event.node_info.output_for` with ancestor paths\n- Only one `use_as_output=True` per execution (second raises\n  `ValueError`)\n\n## Dynamic nodes from dynamic nodes (transitive)\n\nA dynamic node can itself call `ctx.run_node()`, creating a\ntransitive chain:\n\n```python\nclass Outer(BaseNode):\n    rerun_on_resume: bool = True\n\n    async def _run_impl(self, *, ctx, node_input):\n        result = await ctx.run_node(Inner(name='inner'), 'data')\n        yield result\n\nclass Inner(BaseNode):\n    rerun_on_resume: bool = True\n\n    async def _run_impl(self, *, ctx, node_input):\n        sub = await ctx.run_node(Leaf(name='leaf'), node_input)\n        yield f'inner got: {sub}'\n```\n\nThis works because:\n\n- All dynamic nodes in the subtree are tracked by the **same**\n  enclosing Workflow. The scheduler is inherited down the Context\n  tree automatically.\n- Each level gets a unique `node_path`:\n  `wf/graph_node/outer/inner/leaf`\n- Nested interrupts are correctly attributed — the scheduler\n  matches events from any descendant under a given path.\n- Only a nested **orchestration node** (another Workflow) takes over\n  scheduling. Regular nodes inherit the enclosing Workflow's scheduler.\n\n### Scoping\n\nEach Workflow has its own `DynamicNodeScheduler` and `_LoopState`.\nA nested Workflow creates a new scheduler, so dynamic nodes within\nit are scoped to that inner Workflow — not mixed with the outer\nWorkflow's state.\n\n## event_author\n\nWorkflow sets `ctx.event_author = self.name` at the start of\n`_run_impl`. This propagates to all child Contexts via NodeRunner.\nAll events emitted by children carry this author, giving the UI\nconsistent attribution.\n\nA nested Workflow overrides `event_author` with its own name, so events are\nattributed to the nearest orchestration ancestor.\n\n## Orchestration loop lifecycle\n\n```text\n_run_impl\n  ├─ SETUP\n  │    ├─ loop_state.replay_manager.scan_workflow_events(ctx)\n  │    ├─ _seed_start_triggers\n  │    └─ ctx._workflow_scheduler = DynamicNodeScheduler(state=loop_state)\n  ├─ LOOP (_run_loop):\n  │    ├─ _schedule_ready_nodes → pop triggers, dispatch via _start_node_task / ctx._run_node_internal\n  │    ├─ asyncio.wait(FIRST_COMPLETED)\n  │    ├─ _handle_completion → update state, emit checkpoint / replayed output, buffer downstream\n  │    └─ await detached dynamic tasks (loop_state.get_dynamic_tasks()) & _surface_detached_dynamic_outcome\n  ├─ _cleanup_all_tasks  (finally)\n  ├─ _collect_remaining_interrupts\n  ├─ FINALIZE: set ctx.output or ctx._interrupt_ids\n  └─ _emit_end_of_agent  (only when no interrupts remain)\n```\n\nThe event scan is unconditional: `ReplayManager` indexes progress from the\nsession on every run, whether or not the app is configured resumable.\n\nKey behaviors:\n\n- **Concurrency** — `max_concurrency` limits parallel graph nodes.\n  Dynamic nodes are excluded (they run inline, throttling would\n  deadlock).\n- **Terminal output** — nodes with no outgoing edges are terminal.\n  Their output is delegated to the Workflow's own output via\n  `output_for`. Only one terminal node may produce output.\n- **Loop edges** — a completed node can be re-triggered by a\n  downstream edge pointing back to it. Its status resets to PENDING.\n\n## Resume from session events\n\nOn every run, `Workflow` reconstructs progress from session events in three\nstages:\n\n1. **Upfront static scan (`ReplayManager.scan_workflow_events`)** — during\n   `_run_impl` SETUP, `ReplayManager` scans the invocation's events and\n   populates `loop_state.recovered_executions` (keyed by `node_name@run_id`)\n   with outputs, routes, interrupts, resolved function responses, error codes,\n   and transfers for direct static children. `Workflow` then seeds `START`\n   triggers normally (`_seed_start_triggers`).\n2. **Static node interception in `_start_node_task` and `DynamicNodeScheduler`** —\n   when `_schedule_ready_nodes` pops a trigger for a static node,\n   `_start_node_task` looks up `recovered = loop_state.recovered_executions.get(f\"{node_name}@{run_id}\")`:\n   - If `recovered` is present, `_start_node_task` calls\n     `check_interception(node=node, recovered=recovered)` to check whether the\n     run will fast-forward (`not result.should_run` and not a resolved transfer\n     interrupt), adding `node_name` to `loop_state.replayed_nodes` (so\n     checkpoint emission is skipped in `_schedule_ready_nodes` and on the\n     `COMPLETED` path of `_handle_completion`; `_handle_completion` still emits\n     a checkpoint if a replayed node comes back `WAITING`) and seeding\n     `loop_state.runs[node_path]` with\n     `DynamicNodeRun(state=NodeState(run_id=run_id), recovered_state=recovered, is_static=True)`.\n   - `_start_node_task` then dispatches `ctx._run_node_internal` to\n     `DynamicNodeScheduler._check_existing_run`, which runs\n     `check_interception(node=curr_node, recovered=run.recovered_state, current_run=run)`:\n     - **Nested `Workflow`**: `check_interception` always returns\n       `should_run=True` (with `resume_inputs=recovered.resolved_responses`),\n       so a child `Workflow` never fast-forwards at the parent level and instead\n       re-runs `_run_impl` to replay or resume its own children.\n     - **Completed non-`Workflow` node (or `rerun_on_resume=False` with all interrupts resolved)**:\n       returns `should_run=False`; `_check_existing_run` builds a mock\n       `Context` (`create_mock_context`) and awaits\n       `ReplayManager.wait_sequence` so `_handle_completion` can re-surface\n       replayed output (`_maybe_reemit_replayed_output`) and buffer downstream\n       triggers without running `NodeRunner`.\n     - **Interrupted with unresolved interrupts**: if `rerun_on_resume=True`\n       and `recovered.resolved_ids` is non-empty (**partial resume**),\n       re-executes (`should_run=True`) via `NodeRunner` with\n       `resume_inputs=recovered.resolved_responses`; otherwise stays WAITING\n       (`should_run=False` with `interrupts` populated) and records unresolved\n       IDs in `_LoopState.interrupt_ids`.\n     - **Interrupted with all interrupts resolved (`rerun_on_resume=True`) or prior failure (`error_code`)**:\n       re-executes (`should_run=True`) via `NodeRunner` with\n       `resume_inputs=recovered.resolved_responses`.\n3. **Dynamic node lazy rehydration (`_rehydrate_from_events`)** — dynamic\n   children are not scanned upfront into `recovered_executions`. When a\n   re-executing parent calls `ctx.run_node()`,\n   `DynamicNodeScheduler._execute_step` lazily populates\n   `_LoopState.runs[node_path]` via `_rehydrate_from_events` and applies the\n   same `check_interception` rules in `_check_existing_run` (with\n   `current_run=run` also deduplicating same-turn completed or waiting runs).\n\n## Key design rules for node authors\n\n1. **Set `rerun_on_resume = True`** if your node calls\n   `ctx.run_node()`. The Workflow must be able to re-execute your\n   node so it can re-acquire dynamic children's results.\n\n2. **Use deterministic names** for dynamic children. The child node's `name`\n   (plus the optional `run_id=`) determines the `node_path`, which is the\n   dedup/resume key. A name derived from a timestamp, a UUID or model output\n   produces a different path on every run, so resume never finds the prior\n   execution and the child re-runs.\n\n3. **Always `await ctx.run_node()` (or `asyncio.gather`). Never detach tasks.**\n   A dynamic child's interrupt must propagate through the awaiting caller so the\n   caller can pause and resume. If a dynamic task is left detached\n   (`asyncio.create_task(ctx.run_node(...))` left unawaited), its interrupt\n   cannot be handled by the parent and the Workflow turns it into a\n   `RuntimeError` that shuts down the workflow.\n\n4. **Yield output after all dynamic children complete.** If your\n   node calls `ctx.run_node()` and then yields, the output is\n   emitted only after all children finish. This is the expected\n   pattern.\n\n5. **Handle `NodeInterruptedError` only if you need custom logic.**\n   Normally, `ctx.run_node()` raises `NodeInterruptedError` when a\n   child interrupts. NodeRunner catches it automatically. Only\n   catch it yourself if you need to clean up or adjust state before\n   the interrupt propagates.\n\n6. **Don't set `ctx.event_author`** unless your node is an orchestration node\n   like Workflow. The Workflow sets it for you and it propagates to all\n   descendants.\n",".agents/skills/adk-debug/SKILL.md":"---\nname: adk-debug\ndescription: >-\n  Diagnoses misbehaving ADK agents by inspecting sessions, events, tool calls,\n  and the exact request that reached the model. Covers the `adk run` CLI and the\n  `adk web` dev server with its session, trace, and debug HTTP endpoints. Use\n  when an agent returns the wrong answer, ignores a tool or swallows a tool\n  error, hangs, loops, emits raw JSON instead of calling tools, is not\n  discovered by `adk web`, when a sub-agent cannot see the parent conversation,\n  or when you need the LLM request/response, token counts, or logs for a run.\n  Don't use for how ADK is designed internally (use `adk-architecture`), for\n  building a new agent or workflow (use `adk-agent-builder`), for environment or\n  dependency setup failures (use `adk-setup`), or for lint and style nits (use\n  `adk-style`).\n---\n\n# Debugging ADK agents\n\nTwo entry points. Default to `adk run`: one process, no server, and `--jsonl`\noutput that pipes straight into `grep` or `python3`. Switch to `adk web` when\nyou need the browser UI, a persisted session you can click through, or the\ntrace endpoints that expose the exact LLM request.\n\n## First moves\n\n1. Reproduce headlessly: `adk run --jsonl {agent_dir} \"{query}\"`. Without\n   `--jsonl`, `adk run` prints only text parts — tool calls and tool errors are\n   invisible.\n2. Read the log file. `adk run` writes to `/tmp/agents_log/agent.latest.log` and\n   nothing to the terminal; `adk web` does the opposite. See\n   [logs-and-traces.md](references/logs-and-traces.md).\n3. Match the symptom in [failure-modes.md](references/failure-modes.md) before\n   reading source — most reports are one of a handful of known shapes.\n4. If the text is fine but the routing is not, dump the events and read\n   `author`, `branch`, `nodeInfo.path`, and `actions` —\n   [event-flow.md](references/event-flow.md).\n5. If the model itself misbehaved, read what it actually received from the\n   `call_llm` span rather than guessing from the agent definition —\n   [logs-and-traces.md](references/logs-and-traces.md).\n\n## References\n\n- [cli-run.md](references/cli-run.md) — `adk run` flags, the JSONL event shape,\n  multi-turn and human-in-the-loop resume, exit codes, driving a `Runner` from\n  Python.\n- [web-api.md](references/web-api.md) — starting `adk web`, listing and reading\n  sessions over HTTP, posting test messages to `/run_sse`.\n- [logs-and-traces.md](references/logs-and-traces.md) — log levels and where\n  each command writes them, the trace endpoints, span attributes, and the env\n  vars that control whether prompts appear in spans.\n- [failure-modes.md](references/failure-modes.md) — ADK-specific symptoms with\n  the cause and a concrete check for each.\n- [event-flow.md](references/event-flow.md) — how an invocation becomes events,\n  callback order, the event fields that matter, and where each stage lives in\n  the source.\n\n## Ground rules\n\n- Leave sessions in place when you finish. The user may still want to open them\n  in the web UI, and `adk web` has no undelete.\n- Delete any throwaway agent you created for a repro, unless the user asked to\n  keep it.\n- Reach for a unit test in `tests/unittests/` when the bug is inside one\n  component, and for a sample under `contributing/samples/` (see\n  `adk-sample-creator`) when it only reproduces with runner, agent, and workflow\n  wired together.\n",".agents/skills/adk-debug/references/cli-run.md":"# Debugging with `adk run`\n\n`adk run {agent_dir}` with a trailing query argument runs one turn and exits;\nwithout a query it drops into an interactive prompt. Prefer the query form —\nit needs no human in the loop and composes with shell tooling.\n\n```bash\nadk run --jsonl {agent_dir} \"{query}\"\nadk run --jsonl --in_memory {agent_dir} \"{query}\"    # no persisted session\n```\n\n## Flags\n\nFlag | Default | Why you'd use it\n--- | --- | ---\n`--jsonl` | off | One JSON object per event on stdout. Without it only text parts are printed, so tool calls, tool errors, and actions are invisible.\n`--in_memory` | off | Skip the local session store, so repeated runs cannot contaminate each other.\n`--session_id {id}` | new session | In query mode, reuse that session (creating it if absent) — this is how you carry state across separate `adk run` invocations. In interactive mode it only names the file `--save_session` writes.\n`--state '{json}'` | none | Seed session state for a run that only misbehaves with particular state.\n`--replay {file.json}` | none | Replay a saved state + query list into a fresh session. Mutually exclusive with a query argument.\n`--resume {file.json}` | none | Reopen a session saved by `--save_session` and keep interacting (interactive mode only).\n`--timeout 30s` | none | Bound a hanging turn instead of waiting forever.\n`-v` / `--log_level DEBUG` | `INFO` | Raise log verbosity. Output goes to the log file, not the terminal — see `references/logs-and-traces.md`.\n`--default_llm_model {model}` | none | Override the model for agents that do not set one, e.g. to test whether the model is the problem.\n\nFull list: `adk run --help`.\n\n## JSONL event shape\n\nEach line is `Event.model_dump(mode='json', by_alias=True, exclude_none=True)`,\nso keys are camelCase (`invocationId`, `functionCall`, `longRunningToolIds`),\nwith `session_id` and `node_path` injected and `author` first. Empty `actions`\nentries are dropped, so an absent `actions` key means \"no actions\", not\n\"unknown\".\n\nIn `--jsonl` mode stdout is pure JSONL; the human-readable session banner only\nprints when `--jsonl` is off, and goes to stderr either way. So this is safe:\n\n```bash\nadk run --jsonl {agent_dir} \"{query}\" 2>/dev/null > /tmp/events.jsonl\nhead -1 /tmp/events.jsonl | python3 -m json.tool     # inspect the real shape\n```\n\nRead one event before writing a parser — the schema changes. Then filter on\nwhatever you actually saw, for example every tool call:\n\n```python\nimport json\n\nfor line in open(\"/tmp/events.jsonl\"):\n  event = json.loads(line)\n  for part in (event.get(\"content\") or {}).get(\"parts\", []):\n    if \"functionCall\" in part:\n      print(event[\"author\"], part[\"functionCall\"][\"name\"], part[\"functionCall\"].get(\"args\"))\n```\n\n## Exit codes\n\nCode | Meaning\n--- | ---\n`0` | The turn completed.\n`1` | Error — bad `--state` JSON, both a query and `--replay`, no query and no stdin, timeout, or an exception during the run.\n`2` | Paused. The run emitted an event with `longRunningToolIds`, i.e. a human-in-the-loop tool is waiting.\n\nOn exit 2 the run prints the session id. Resume by re-running with that\n`--session_id` and the answer as the query — ADK maps the query onto the\npending `adk_request_confirmation` / `adk_request_input` function response\nautomatically, so do not try to hand-craft a `FunctionResponse`. For a\nconfirmation, a plain `yes`/`no` works; pass a JSON object to supply a custom\npayload.\n\n## Driving a Runner from Python\n\nUse this when you need to assert on events rather than eyeball them. Two\nthings that bite:\n\n- `new_message` must be a `types.Content`, not a string.\n- `Runner` takes keyword arguments only, and `auto_create_session` defaults to\n  `False`, so the session must exist before you run.\n\n```python\nimport asyncio\n\nfrom google.adk import Agent\nfrom google.adk.runners import InMemoryRunner\nfrom google.genai import types\n\nagent = Agent(name=\"test\", model=\"gemini-2.5-flash\", instruction=\"...\")\nrunner = InMemoryRunner(agent=agent, app_name=\"test\")\n\n\nasync def main():\n  session = await runner.session_service.create_session(\n      app_name=\"test\", user_id=\"u\"\n  )\n  async for event in runner.run_async(\n      user_id=\"u\",\n      session_id=session.id,\n      new_message=types.Content(role=\"user\", parts=[types.Part(text=\"hello\")]),\n  ):\n    print(event.author, event.content)\n    if event.actions.transfer_to_agent:\n      print(\"  -> transfer to\", event.actions.transfer_to_agent)\n    if event.output is not None:\n      print(\"  -> output:\", event.output)\n\n\nasyncio.run(main())\n```\n\n`InMemorySessionService.create_session_sync` still exists but logs a\ndeprecation warning; use the async `create_session`.\n\nTo print events the way the CLI does, without reimplementing the formatting:\n\n```python\nfrom google.adk.utils._debug_output import print_event\n\nprint_event(event)                # text parts only\nprint_event(event, verbose=True)  # plus tool calls, tool results, code, blobs\n```\n\n`verbose` is keyword-only. Source: `src/google/adk/utils/_debug_output.py`.\n",".agents/skills/adk-debug/references/event-flow.md":"# Event flow and where to look\n\n## One user message becomes events\n\n```text\nRunner.run_async()\n  Runner._exec_with_plugin()          # plugin hooks + persisting events\n    agent.run_async()                 # BaseAgent: before/after agent callbacks\n      LlmAgent._run_async_impl()      # yields events\n        BaseLlmFlow.run_async()\n          SingleFlow | AutoFlow       # AutoFlow adds agent transfer\n            call_llm                  # request build + model call\n            handle_function_calls_async()   # tool dispatch\n```\n\n`LlmAgent._llm_flow` picks `SingleFlow` only when\n`disallow_transfer_to_parent` and `disallow_transfer_to_peers` are both set and\nthe agent has no sub-agents; otherwise it is `AutoFlow`. If an agent refuses to\ntransfer, check those two fields before suspecting the prompt.\n\nWorkflow-graph execution takes a different path: `LlmAgent._run_impl` runs the\nagent as a node via `src/google/adk/workflow/`.\n\n## Callback order\n\nBoth the plugin manager and the agent get a turn, plugins first:\n\nPoint | Plugin manager | Agent\n--- | --- | ---\nBefore model | `run_before_model_callback` | `canonical_before_model_callbacks`\nAfter model | `run_after_model_callback` | `canonical_after_model_callbacks`\nModel error | `run_on_model_error_callback` | `canonical_on_model_error_callbacks`\nBefore tool | `run_before_tool_callback` | `canonical_before_tool_callbacks`\nAfter tool | `run_after_tool_callback` | `canonical_after_tool_callbacks`\nTool error | `run_on_tool_error_callback` | `canonical_on_tool_error_callbacks`\n\nThe manager also exposes run-level hooks with no agent counterpart:\n`run_on_user_message_callback`, `run_before_run_callback`,\n`run_after_run_callback`, `run_on_event_callback`, and the agent/run error\nhooks. Source: `src/google/adk/plugins/plugin_manager.py`.\n\nA plugin callback that returns a value short-circuits the step, so an agent\nthat \"ignores\" its own callback is often a plugin that already answered.\n\n## Event fields worth reading\n\n`Event` serializes with a camelCase alias generator, so JSON from the HTTP API\nor `adk run --jsonl` uses `invocationId`, `functionCall`, `nodeInfo`,\n`longRunningToolIds`, while Python attribute access stays snake_case.\n\nField | Why it matters\n--- | ---\n`author` | `user` or the agent name — the fastest way to see which agent actually spoke.\n`branch` | `agent_1.agent_2` path. Drives which history the agent can see.\n`nodeInfo.path` | Node path inside a workflow, e.g. `wf/A@1/B@1`.\n`content.parts` | `text`, `functionCall`, `functionResponse` — a turn with no `text` part is not a bug, it is a tool round trip.\n`output` | Generic node output value. Absent on ordinary chat events.\n`longRunningToolIds` | Present means the run is parked on a human-in-the-loop tool.\n`actions.transferToAgent` | The agent handed control to a named agent.\n`actions.escalate` | The agent gave up to its parent, typically ending a loop.\n`actions.endOfAgent` | The agent finished.\n`actions.stateDelta` / `artifactDelta` | State and artifact writes made by this event.\n\n`isolationScope` also appears on task-agent events; it is internal, so read it\nfor orientation but do not build on it. Source:\n`src/google/adk/events/event.py`, `src/google/adk/events/event_actions.py`.\n\n## Source map\n\nArea | File\n--- | ---\nRunner and event persistence | `src/google/adk/runners.py`\nFlow driver, LLM call, callbacks | `src/google/adk/flows/llm_flows/base_llm_flow.py`\nRequest assembly (model, tools, schema) | `src/google/adk/flows/llm_flows/basic.py`\nWhich history reaches the model | `src/google/adk/flows/llm_flows/contents.py`\nTool dispatch and tool errors | `src/google/adk/flows/llm_flows/functions.py`\nAgent transfer | `src/google/adk/flows/llm_flows/agent_transfer.py`\nAgent config and validation | `src/google/adk/agents/llm_agent.py`\nInvocation state and call limits | `src/google/adk/agents/invocation_context.py`\nTask agents | `src/google/adk/agents/llm/task/`\nGraph orchestration | `src/google/adk/workflow/`\nEvent model | `src/google/adk/events/event.py`\nSession services | `src/google/adk/sessions/`\nPlugin hook ordering | `src/google/adk/plugins/plugin_manager.py`\nHTTP API (production-safe routes) | `src/google/adk/cli/api_server.py`\nDev-only routes, including traces | `src/google/adk/cli/dev_server.py`\nAgent discovery | `src/google/adk/cli/utils/agent_loader.py`\nLog setup | `src/google/adk/cli/utils/logs.py`\nTracing and span attributes | `src/google/adk/telemetry/tracing.py`\nEvent printer used by the CLI | `src/google/adk/utils/_debug_output.py`\n\n`src/google/adk/cli/adk_web_server.py` is a deprecated shim; `AdkWebServer` now\njust subclasses `DevServer`. Read `api_server.py` / `dev_server.py` instead.\n",".agents/skills/adk-debug/references/failure-modes.md":"# ADK failure modes\n\nMatch the symptom first; each entry names the mechanism and a check you can\nactually run.\n\n## The agent emits raw JSON instead of calling tools\n\n`output_schema` puts the model into controlled generation: it sets\n`config.response_schema` and `config.response_mime_type =\n\"application/json\"` on the request, and a model in JSON mode returns JSON, not\ntool calls.\n\nCheck the `call_llm` span's `gcp.vertex.agent.llm_request` for\n`response_mime_type` (`references/logs-and-traces.md`). ADK only\napplies the schema when the agent has no tools, or when the model can do\nschemas and tools together — so the opposite symptom, a schema that seems to be\nignored, means you landed on the other branch.\n\nSource: `src/google/adk/flows/llm_flows/basic.py`,\n`src/google/adk/utils/output_schema_utils.py`.\n\n## `ValueError` when constructing the agent\n\nThree messages come from the same validator, all meaning \"you set this on\n`generate_content_config` instead of on the agent\":\n\n- `All tools must be set via LlmAgent.tools.`\n- `System instruction must be set via LlmAgent.instruction.`\n- `Response schema must be set via LlmAgent.output_schema.`\n\nSource: `LlmAgent.validate_generate_content_config` in\n`src/google/adk/agents/llm_agent.py`.\n\n## `LlmCallsLimitExceededError: Max number of llm calls limit of N exceeded`\n\n`run_config.max_llm_calls` was hit. Treat the limit as a loop detector before\nraising it — dump the events and look for the same tool being called with the\nsame arguments turn after turn. Source:\n`src/google/adk/agents/invocation_context.py`.\n\n## A tool \"fails\" but the agent carries on\n\nTool failures are converted into a function response carrying the error, so the\nmodel sees a result and keeps going. Look for a `functionResponse` whose payload\nhas an `error` key. `FunctionTool` produces the same shape for two non-exception\ncases: missing mandatory arguments, and a confirmation-required tool that was\nnot confirmed or was rejected.\n\nTo intervene, register `on_tool_error_callback` on a plugin or\n`on_tool_error_callbacks` on the agent. Source:\n`src/google/adk/flows/llm_flows/functions.py`,\n`src/google/adk/tools/function_tool.py`.\n\n## `adk web` does not list the agent, or returns 404\n\n```bash\ncurl -s http://localhost:8000/list-apps | python3 -m json.tool\n```\n\nThe loader accepts four layouts under `{agents_dir}`, checking for a top-level\n`app` before `root_agent`:\n\n```text\n{name}/agent.py          # defines root_agent (or app)\n{name}.py                # defines root_agent (or app)\n{name}/__init__.py       # defines root_agent (or app) in the package\n{name}/root_agent.yaml   # config-defined agent\n```\n\n`__init__.py` does not need `from . import agent` — the loader imports the\n`agent` submodule itself. Pointing `adk web` at a directory that itself contains\n`agent.py` or `root_agent.yaml` runs that single agent instead of treating the\ndirectory as a collection. Source:\n`src/google/adk/cli/utils/agent_loader.py`.\n\n## A sub-agent cannot see the parent conversation\n\nEvents carry a `branch` (`agent_1.agent_2.agent_3`), and the content builder\ndrops events that do not belong to the current agent's branch — that isolation\nis deliberate, so peers do not read each other's history. Delegated task agents\nare isolated further by `isolation_scope`.\n\nThere is no flag to switch it off. Put whatever the sub-agent needs into the\ndelegation input; the sub-agent's `description` is what steers the parent into\nincluding it. Source: `_is_event_belongs_to_branch` in\n`src/google/adk/flows/llm_flows/contents.py`.\n\n## The whole agent stalls while one tool runs\n\nA synchronous tool function is awaited inline on the event loop, so anything\nblocking inside it — a `requests` call, `time.sleep`, a large file read —\nfreezes the entire run, not just that tool.\n\nMake the callable `async`, or hand synchronous `FunctionTool` and `FunctionNode`\ncallables (and all tools in live mode) to a thread pool:\n\n```python\nfrom google.adk.agents.run_config import RunConfig, ToolThreadPoolConfig\n\nrun_config = RunConfig(tool_thread_pool_config=ToolThreadPoolConfig())  # 4 workers\n```\n\nSource: `_resolve_sync_callable_runner` in\n`src/google/adk/utils/_sync_runner.py`, `FunctionTool._invoke_callable` in\n`src/google/adk/tools/function_tool.py`, and `_call_tool_in_thread_pool` in\n`src/google/adk/flows/llm_flows/tools/_thread_pool.py`.\n\n## The run stops early and nothing looks wrong\n\n`adk run` exits 2 when an event carries `longRunningToolIds`: a\nhuman-in-the-loop tool is waiting for an answer. See\n`references/cli-run.md` for how to resume.\n\n## The answer is cut off, empty, or blocked\n\nRead `gen_ai.response.finish_reasons` on the `call_llm` span rather than\ninferring from the text — `max_tokens` means raise `max_output_tokens`,\n`safety` and `recitation` mean the model refused. See\n`references/logs-and-traces.md`.\n\n## The run ends with `error_code=\"INVOCATION_ABORTED\"`\n\n`abort_signal` was set on `runner.run_async`, or the HTTP client disconnected\nfrom `/run_sse` before the stream finished. The runner synthesizes a terminal\nevent with `error_code=\"INVOCATION_ABORTED\"` and seals any in-flight\n`FunctionCall` with a `FunctionResponse` carrying `{\"error\": \"Invocation was\naborted by client.\"}` so the session remains valid for the next turn. Source: `Runner._synthesize_abort_events_if_needed` in\n`src/google/adk/runners.py`, `src/google/adk/cli/api_server.py`.\n",".agents/skills/adk-debug/references/logs-and-traces.md":"# Logs and traces\n\n## Where the logs actually go\n\nThis differs per command and is the most common reason \"I turned on `-v` and\nsaw nothing\".\n\nCommand | Destination\n--- | ---\n`adk run` | `{tempdir}/agents_log/agent.{timestamp}.log`, i.e. `/tmp/agents_log/...` on Linux, with an `agent.latest.log` symlink. It clears the root logger's handlers, so **nothing** is logged to the terminal.\n`adk web`, `adk api_server`, `adk eval`, everything else | stderr, via `logging.basicConfig`. No log file is created.\n\n```bash\nadk run -v {agent_dir} \"{query}\"\ntail -F /tmp/agents_log/agent.latest.log\n```\n\nFor `adk web`, tee stderr into a file so both you and the user can read it:\n\n```bash\nadk web -v {agents_dir} 2>&1 | tee {readable_path}/adk_web.log\n```\n\n`-v` is a shortcut for `--log_level DEBUG`; the levels are `DEBUG`, `INFO`,\n`WARNING`, `ERROR`, `CRITICAL`. ADK's own records go to the `google_adk`\nlogger, so filter with `grep google_adk` or raise only that logger in-process.\nSetup lives in `src/google/adk/cli/utils/logs.py`.\n\n## Trace endpoints\n\nOnly `adk web` registers these. `adk api_server` runs the production-safe\n`ApiServer`, which has no `/dev/...` routes, so trace lookups there 404.\n\n```bash\n# Every span for a session\ncurl -s http://localhost:8000/dev/apps/{app_name}/debug/trace/session/{session_id} \\\n  | python3 -m json.tool\n\n# The single trace recorded against one event id\ncurl -s http://localhost:8000/dev/apps/{app_name}/debug/trace/{event_id} \\\n  | python3 -m json.tool\n```\n\nThe session response is a list of spans, each with `name`, `span_id`,\n`trace_id`, `parent_span_id`, `start_time`, `end_time`, and `attributes`.\nSpans are kept in memory by the running server, so restarting it loses them.\n\nSpan name | What it covers\n--- | ---\n`call_llm` | One model call, including the `before_model` / `after_model` callbacks.\n`execute_tool (merged)` | A batch of tool calls dispatched from one model response.\n`generate_content {model}` | The underlying GenAI SDK call, when the OTel GenAI instrumentation is active.\n\n## Reading what the model actually received\n\nPull the `call_llm` spans and decode `gcp.vertex.agent.llm_request` — it is a\nJSON *string* holding `contents`, `config` (tools, `response_schema`,\n`response_mime_type`, `system_instruction`), and `model`. This is the ground\ntruth for \"why did the model do that\": compare it against what you believe the\nagent is configured to send.\n\nAttribute | Meaning\n--- | ---\n`gcp.vertex.agent.llm_request` | Full request as a JSON string.\n`gcp.vertex.agent.llm_response` | Full response as a JSON string.\n`gcp.vertex.agent.tool_call_args` / `.tool_response` | Tool arguments and result.\n`gcp.vertex.agent.event_id` | Correlates the span with an event in the session.\n`gcp.vertex.agent.invocation_id` / `.session_id` | Correlates spans across one turn.\n`gen_ai.request.model` | Model name as sent.\n`gen_ai.usage.input_tokens` / `.output_tokens` | Token counts — check these before blaming a prompt for being ignored.\n`gen_ai.response.finish_reasons` | List of lowercased reasons, e.g. `[\"max_tokens\"]` for a truncated answer or `[\"safety\"]` for a filtered one.\n\nIf the content-bearing attributes come back as `\"{}\"`, content capture is off —\nsee the env vars below, not a bug in the agent.\n\n## Environment variables\n\nVariable | Effect\n--- | ---\n`ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS` | Whether prompts and responses are written onto the legacy `gcp.vertex.agent.*` span attributes. Defaults to `true`; set `false` to strip content.\n`OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` | OTel-spec content capture for the GenAI semantic-convention spans and log records.\n`ADK_TELEMETRY_SCHEMA_VERSION_OPT_IN` | Pins the telemetry schema to `1` (default off Agent Engine) or `2`. Under `2` the `invocation` span becomes `invoke_workflow` and `call_llm` goes away, so check this first if the span names above are missing.\n`GOOGLE_CLOUD_PROJECT` | Required by `adk web --trace_to_cloud`; without it the server logs a warning and exports nothing.\n\n`--trace_to_cloud` only exports traces. `--otel_to_cloud` is the newer flag and\ncovers Cloud Trace plus Cloud Logging; `adk deploy agent_engine` already warns\nthat `--trace_to_cloud` is being replaced by it.\n\nSource: `src/google/adk/telemetry/tracing.py`,\n`src/google/adk/telemetry/context.py`,\n`src/google/adk/telemetry/_schema_version.py`.\n",".agents/skills/adk-debug/references/web-api.md":"# Debugging with `adk web`\n\n`adk web` is a FastAPI server: a browser UI at `http://localhost:8000/dev-ui/`\nplus the HTTP API below. Reach for it when you need to click through a\npersisted session, or when you need the trace endpoints\n(`references/logs-and-traces.md`).\n\n## Starting the server\n\nCheck for an existing server before starting your own — a second one will fail\nto bind port 8000, and the user may already have one with the sessions you care\nabout:\n\n```bash\ncurl -s http://localhost:8000/health     # {\"status\":\"ok\"} if one is running\n```\n\nIf none is running, start it in the background and shut it down when you are\ndone:\n\n```bash\nadk web {agents_dir}                     # http://127.0.0.1:8000\nadk web -v --reload_agents {agents_dir}\n```\n\n`{agents_dir}` is a directory of agent subdirectories, or a single agent folder\n(one containing `agent.py` or `root_agent.yaml`). It defaults to the current\ndirectory.\n\nFlag | Default | Note\n--- | --- | ---\n`--port` | `8000` | Use a second port to run two servers side by side.\n`--host` | `127.0.0.1` | Endpoints are unauthenticated; keep it on loopback.\n`--reload_agents` | off | Re-import agent modules when their files change. This is the one you want while editing an agent.\n`--reload` | **on** | Uvicorn's own source autoreload. Pass `--no-reload` when a restart mid-run is confusing you.\n`-v` / `--log_level` | `INFO` | Logs go to the terminal, not to a file — see `references/logs-and-traces.md`.\n\n`adk api_server` takes the same flags but serves only the production-safe\nroutes — no UI and no `/dev/...` debug or trace endpoints. Use `adk web` when\ndebugging.\n\n## Inspecting sessions\n\n```bash\ncurl -s http://localhost:8000/list-apps | python3 -m json.tool\n\ncurl -s http://localhost:8000/apps/{app_name}/users/{user_id}/sessions \\\n  | python3 -m json.tool\n\ncurl -s http://localhost:8000/apps/{app_name}/users/{user_id}/sessions/{session_id} \\\n  | python3 -m json.tool\n```\n\nThe session response holds the full event list. Fetch the raw JSON and write a\nsummarizer against the structure you actually see rather than a remembered\nschema. Fields worth pulling per event: `author`, `branch`, `nodeInfo.path`,\n`content.parts` (`text`, `functionCall`, `functionResponse`), `output`, and\n`actions` (`transferToAgent`, `escalate`, `endOfAgent`). Keys are camelCase.\n\n`DELETE .../sessions/{session_id}` exists; do not use it to tidy up after\nyourself, because the user may still want the session in the UI.\n\n## Sending a test message\n\nCreate a session, then post one turn. `/run` returns the whole event list as\nJSON, which is far easier to assert on than a stream:\n\n```bash\nSESSION=$(curl -s -X POST http://localhost:8000/apps/{app_name}/users/test/sessions \\\n  -H \"Content-Type: application/json\" -d '{}' \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)['id'])\")\n\ncurl -s -X POST http://localhost:8000/run \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"app_name\\\":\\\"{app_name}\\\",\\\"user_id\\\":\\\"test\\\",\\\"session_id\\\":\\\"$SESSION\\\",\n       \\\"new_message\\\":{\\\"role\\\":\\\"user\\\",\\\"parts\\\":[{\\\"text\\\":\\\"{query}\\\"}]}}\" \\\n  | python3 -m json.tool\n```\n\nUse `/run_sse` with `\"streaming\":true` and `curl -N` only when the bug is in\nstreaming itself — partial events, chunk ordering, or a stream that never\nterminates.\n\nA `POST` to a session id that does not exist returns 404 rather than creating\nit, so create the session first. Supply your own id by passing\n`{\"session_id\": \"...\"}` in the create body when you want a stable id across\nruns.\n",".agents/skills/adk-git/SKILL.md":"---\nname: adk-git\ndescription: >-\n  Writes commit messages and pull request descriptions for the adk-python\n  repository: Conventional Commits types and scopes, subject lines that say why\n  a change was made, and the linked-issue and testing-plan sections the PR\n  template requires. Use when writing or rewording a commit message, squashing\n  commits before a pull request, drafting a PR description, or checking that a\n  change is shaped to land. Don't use for generic git mechanics such as\n  rebasing, resolving conflicts, cherry-picking, or branch surgery; those need\n  no skill. Don't use to judge the content of a change (use adk-review) or for\n  code style and naming (use adk-style).\n---\n\n# Commit and Pull Request Conventions\n\n## Commit message format\n\nConventional Commits:\n\n```text\n<type>(<scope>): <description>\n```\n\nThe type decides where the commit lands in `CHANGELOG.md`. `release-please`\ngenerates the changelog from merged commit subjects, so the wrong type either\nfiles the change under the wrong heading or drops it from the release notes\nentirely.\n\n| Type                                               | Changelog section        |\n| -------------------------------------------------- | ------------------------ |\n| `feat`                                             | Features                 |\n| `fix`                                              | Bug Fixes                |\n| `perf`                                             | Performance Improvements |\n| `docs`                                             | Documentation            |\n| `refactor`, `test`, `build`, `ci`, `style`, `chore` | hidden, no entry         |\n\nThe mapping lives in `.github/release-please-config.json`. A type that is not\nlisted there produces no changelog entry at all.\n\nScope is optional. Use a short module name with no underscores\n(`fix(cli):`, `feat(a2a):`, `fix(sessions):`) or leave it off.\n\n## Subject line\n\nSay why the change exists, not which lines moved. A reviewer who reads only the\nsubject should understand the motivation.\n\n| Write                                                        | Not                                                            |\n| ------------------------------------------------------------ | -------------------------------------------------------------- |\n| `fix(sessions): prevent duplicate events when resuming HITL` | `fix(sessions): check interrupt_id before appending`           |\n| `feat(workflow): support parallel tool execution`            | `feat(workflow): add asyncio.gather call in execute_tools_node` |\n| `refactor: make graph public for dev UI serialization`       | `refactor: make graph a public field on Workflow`              |\n\nRules:\n\n1. Imperative mood: `add`, not `added`.\n2. Lowercase the first word after the colon. `release-please` copies the\n   subject into `CHANGELOG.md` verbatim, and the great majority of merged\n   commits are lowercase, so capitalizing makes one line stand out.\n3. No trailing period.\n4. Keep the subject under about 72 characters. Nothing enforces this, but each\n   commit renders as one changelog line.\n5. Reference the issue in the body, not the subject: `Fixes #1234` or\n   `Closes #1234`, or the full issue URL when the issue lives in another\n   repository.\n\nSelf-check: read the subject back and ask whether it says *why* someone made\nthe change. If it only names the edit, rewrite it.\n\n## Commit body\n\nAdd a blank line, then a short concrete explanation. For a feature, show the\nnew capability or a usage line. For a fix, say what caused the failure and how\nthe change addresses it.\n\n```text\nfeat(workflow): support JSON string parsing in schema validation\n\nParse JSON strings into dicts or Pydantic models when input_schema or\noutput_schema is defined on a node.\n```\n\n```text\nfix(sessions): prevent duplicate events when resuming HITL\n\ninterrupt_id was not checked before appending, so resuming twice appended the\nsame event twice. Ignore interrupts that were already processed.\n\nFixes #1234\n```\n\n## Before committing\n\n`pre-commit` reformats and checks staged files, and the same hooks run again in\nCI on every pull request, so a commit made with hooks skipped fails there.\n\n```bash\npre-commit install                  # once per clone\npre-commit run --files {paths}      # check only what changed\n```\n\nThe hooks include `isort`, `pyink`, `addlicense`, `mdformat`, `ruff`,\n`codespell`, and repository-local compliance checks; see\n`.pre-commit-config.yaml`. If `pre-commit` is not installed, point the user at\nthe `adk-setup` skill rather than committing unformatted code.\n\n## Pull requests\n\n- Every PR except a small documentation or typo fix needs a linked issue.\n  Put `Closes: #{issue_number}` in the PR description, or describe the problem\n  and solution inline following the issue templates.\n- Fill in the Testing Plan section of `.github/pull_request_template.md`,\n  including a summary of passing `pytest` results.\n- Do not merge on GitHub. The `Do Not Merge on GitHub` check fails on every PR\n  to `main` by design; a maintainer lands the change and it is synced back to\n  the repository. GitHub then shows the PR as closed with a `merged` label\n  rather than merged, and the landed commit carries the original authorship.\n  That red check is expected and is not something to fix.\n",".agents/skills/adk-review/SKILL.md":"---\nname: adk-review\ndescription: >-\n  Reviews the uncommitted changes in an adk-python working tree and reports\n  correctness, design, public-API stability, test, sample and documentation\n  gaps as a prioritized findings report, fixing them only when asked. Use when\n  the user asks to review local changes, wants a self-review before opening a\n  pull request, asks whether a change breaks the public API or needs tests,\n  samples or docs, or asks what is wrong with the current diff. Required for\n  changes to public APIs, core architecture (Runner, Workflow, BaseNode), new\n  features and major refactors. Don't use for a single style nit (use\n  adk-style), for diagnosing a failing test or a misbehaving agent at runtime\n  (use adk-debug), or for wording a commit message or PR description (use\n  adk-git).\n---\n\n# ADK Change Reviewer\n\nReview the working-tree diff against the seven dimensions below, report what is\nwrong, and stop. Fix only what the user then asks you to fix.\n\n## Workflow\n\n1. `git status` and `git diff` (add `--staged` for staged work) to get the exact\n   set of added, modified, and deleted files.\n2. Review the diff file by file against the checklist.\n3. Emit the report in the format below.\n4. Stop. Do not edit any file, and do not offer to. Wait for the user to ask.\n5. If the user asks for fixes, apply them, then re-run the affected tests\n   (`pytest tests/unittests/{path}`) and `pre-commit run --files {paths}`.\n\nStep 4 is the part that is easy to get wrong: an unrequested fix buries the\nfindings the user asked for and mixes review output with new, unreviewed edits.\n\n## Checklist\n\n### 1. Correctness\n\n- **Types**: no new `mypy` errors. CI diffs `mypy` output against the base\n  branch and fails only on *newly introduced* errors, so a pre-existing error in\n  a file you touched is not a blocker but a new one is.\n- **Imports**: no circular imports; absolute imports where the module already\n  uses them.\n- **Exceptions**: no bare `except:`; catch a specific type and log with enough\n  context to identify the caller.\n- **Type discrimination**: check an object's type before reading a\n  type-specific attribute (for example confirm a node is an `LlmAgent` before\n  inspecting `mode`), so an unexpected node type raises nothing.\n- **Boundaries**: `None`, empty collections, zero, and empty strings are\n  handled by validation or a fallback default.\n- **Preconditions**: state invariants are checked before the core logic runs.\n\n### 2. Design\n\n- **Complexity**: functions or classes that would be clearer split up.\n- **Coupling**: high cohesion, low coupling; no anti-patterns introduced.\n- **Performance**: redundant computation, repeated I/O in a loop, or\n  allocations that scale with input where they need not.\n- **Security**: inputs validated, sensitive data not logged, no injection,\n  resource exhaustion, or exposure of internal state.\n\n### 3. Style\n\nCross-reference the diff against the `adk-style` skill rather than restating\nits rules here: visibility and `_` prefixes, typing, Pydantic v2 patterns, lazy\nlogging, imports, async, and file organization.\n\nConfirm the changed files pass `pre-commit run --files {paths}`.\n\n### 4. Architecture and unintended outcomes\n\n- **Public API stability**: does the change modify, remove, or narrow a\n  public interface, class, method, argument list, or CLI surface under\n  `src/google/adk/`? A breaking change needs a deprecation cycle first, because\n  the package is released under Semantic Versioning and users pin minor\n  versions.\n- **Session, Artifact, and Memory service changes**: adding any new field to\n  `Session`, `ArtifactVersion`, `MemoryEntry`, or to `BaseSessionService`,\n  `BaseArtifactService`, `BaseMemoryService` and their schemas is a **breaking\n  change** (even if optional or defaulted), because existing downstream storage\n  subclasses and external backends will not persist or deserialize unknown\n  fields, causing silent state loss upon reload.\n- **Execution and resumption**: changes to workflows, nodes, or state must stay\n  compatible with the event execution lifecycle and with session resumption\n  (human-in-the-loop steps and checkpoints). See the `adk-architecture` skill.\n- **Concurrency and lifetime**: no race conditions; plugins, exporters, and\n  connections are closed on every path, including the error path.\n- **Database and runtime DDL safety**: runtime startup paths (e.g.,\n  `DatabaseSessionService.prepare_tables()` and\n  `_ensure_schema_indexes_exist()`) must never execute destructive DDL such as\n  `DROP INDEX` (which takes `ACCESS EXCLUSIVE` locks and breaks rolling\n  deployments/rollbacks). Any additive runtime DDL must account for\n  multi-container startup races (process-local `asyncio.Lock` does not prevent\n  cross-container TOCTOU races on `checkfirst=True`) by wrapping creation in a\n  `SAVEPOINT` (`connection.begin_nested()`) and tolerating concurrent creation.\n- **Scope**: a new public name, parameter or flag the fix did not need; a flag\n  that keeps the buggy behavior as the default; a fix in a shared layer when\n  one caller needed it; an existing test assertion edited to match the new\n  behavior.\n\n### 5. Documentation impact\n\n- User-facing documentation lives in the separate `adk-docs` repository, so a\n  user-visible change needs a PR there as well; note it in the report.\n- Guides under `docs/guides/` may need an update when a public API or workflow\n  pattern changes.\n- If the change alters a code unit's design contract, the `adk-unit-design`\n  skill owns the design document for that unit.\n\n### 6. Samples\n\n- Do existing samples under `contributing/samples/` still run against the\n  change?\n- Does a new capability warrant a new sample? Follow the `adk-sample-creator`\n  conventions if so.\n\n### 7. Tests\n\n- Every new or modified code path has a test under `tests/unittests/`.\n- A new test belongs in the existing `test_{module}*.py` file for its unit, not\n  in a file named after the change, which fragments that unit's coverage.\n- Tests follow the rules in the `adk-style` testing reference: one behavior per\n  test, behavior-named tests, no assertions on private attributes, minimal\n  fixtures, arrange/act/assert structure.\n\n## Ground each finding\n\nBefore reporting a finding, resolve the symbol through base classes (`Event`\nextends `LlmResponse`). For a correctness finding, name the input that makes it\nfail; for any other finding, quote the rule or convention it breaks. A finding\nthat is neither is a guess; leave it out.\n\n## Report format\n\nGroup findings by priority and give a file path and line for each. Do not\nreport a dimension with nothing to say.\n\n- 🔴 **Critical**: bugs, type errors, race conditions, resource leaks, security\n  issues.\n- 🟠 **Design**: complexity, readability, performance, architectural\n  misalignment.\n- 🟡 **Style**: lint, formatting, non-lazy logging, typing mismatches.\n- 🔵 **Docs, tests, samples**: missing or stale coverage, guides, samples.\n",".agents/skills/adk-sample-creator/SKILL.md":"---\nname: adk-sample-creator\ndescription: >-\n  Creates a new sample agent in the ADK Python repository — the sample\n  directory, its `agent.py`, and its `README.md` — following the conventions\n  the existing samples already use. Use when the user wants to add a sample or\n  example demonstrating a feature or agent pattern (dynamic nodes,\n  fan-out/fan-in, a standalone tool-using agent), asks where a new sample\n  belongs under `contributing/samples/`, or wants an existing sample's README\n  brought up to the standard structure. Don't use for building a real working\n  agent for the user's own project (use `adk-agent-builder`), or for checking\n  whether the Python blocks in a Markdown file run (use `adk-verify-snippets`).\n---\n\n# ADK Sample Creator\n\nCreates samples under `contributing/samples/`. These are deliberately minimal\nagents that each exercise one or two features — distinct from the `adk-samples`\nrepository, which hosts full end-to-end applications.\n\nRead the `adk-style` skill first for ADK 2.0 conventions if you have not\nalready.\n\n## 1. Pick the category directory\n\nAlmost every sample lives at\n`contributing/samples/{category}/{sample_name}/`. List the categories and\nconfirm with the user which one the sample belongs in before creating\nanything — a workflow sample landing outside `workflows/` is the usual mistake.\n\n```bash\nls contributing/samples/\n```\n\nCategories include `workflows`, `patterns`, `core`, `multi_agent`, `tools`,\n`models`, `live`, `mcp`, `a2a`, `evaluation`, and `plugins`. A handful of\nsamples nest one level further when a single feature needs several variants, as\n`plugins/plugin_reflect_tool_retry/basic/` does.\n\nName the sample directory in `snake_case` after the feature it demonstrates:\n`dynamic_nodes`, `fan_out_fan_in`, `streaming_tool_events`.\n\nDo not add an `_agent` suffix, and do not repeat the category as a prefix —\nevery sample is an agent, and the category is already in the path. Many existing\ndirectories still carry both; do not copy them.\n\n## 2. Write `agent.py`\n\nContents of a sample directory:\n\n| File | Required | Purpose |\n| --- | --- | --- |\n| `agent.py` | yes | The agent or workflow. Must expose `root_agent`. |\n| `README.md` | yes | See [readme-template.md](references/readme-template.md). |\n| `__init__.py` | sometimes | Present when the sample is imported as a package. |\n| `tests/*.json` | no | Recorded sessions used as eval sets. |\n\nUse absolute imports so the file can be run and imported directly.\n\nDo not set `model=` on `Agent` instances. Samples inherit the\nsystem-configured model, which keeps them working when the default model\nchanges; hardcoding `model=\"gemini-2.5-flash\"` pins the sample to a model that\nwill be retired. Set it only when the user explicitly asks for a specific model.\n\nThen pick one of the two shapes.\n\n### Pattern A — Workflow, for multi-step graphs\n\nUse when the sample needs multiple nodes, routing, or parallel execution.\n\n```python\nfrom google.adk import Agent\nfrom google.adk import Context\nfrom google.adk import Event\nfrom google.adk import Workflow\nfrom google.adk.workflow import JoinNode\nfrom google.adk.workflow import node\n```\n\nImport `Workflow` from `google.adk`, not from a private\n`google.adk.workflow._*` module.\n\n```python\nmy_agent = Agent(name=\"my_agent\", instruction=\"...\")\n\n\n@node()\nasync def my_node(node_input: str) -> str:\n  return \"result\"\n\n\nroot_agent = Workflow(\n    name=\"root_agent\",\n    edges=[(\"START\", my_node)],\n)\n```\n\nA plain function can be used as a node directly in `edges`; reach for the\n`@node(...)` decorator when you need one of its options, such as\n`rerun_on_resume=True` for a node that calls `ctx.run_node`.\n\n### Pattern B — Standalone agent, for single-agent or simple tool use\n\nUse when there is no graph and the agent drives its own loop.\n\n```python\nfrom google.adk import Agent\nfrom google.adk.tools import google_search\n\nroot_agent = Agent(\n    name=\"standalone_assistant\",\n    instruction=\"You are a helpful assistant.\",\n    description=\"An assistant that can help with queries.\",\n    tools=[google_search],\n)\n```\n\n## 3. Write `README.md`\n\nFollow [readme-template.md](references/readme-template.md) — section order,\nprompt formatting, the Mermaid topology rules, and the relative link depth for\n`docs/guides/`.\n\n## Worked examples\n\nRead these two before writing a new Pattern A sample — one dynamic graph, one\nstatic one.\n\n-   `contributing/samples/workflows/dynamic_nodes/agent.py` — a Python node\n    driving a `while` loop with `ctx.run_node`, so the number of agent calls is\n    decided at runtime rather than by the edges.\n\n    ```python\n    @node(rerun_on_resume=True)\n    async def orchestrate(ctx: Context, node_input: str) -> str:\n      yield Event(state={\"topic\": node_input})\n\n      while True:\n        headline = await ctx.run_node(generate_headline)\n        # ...\n    ```\n\n-   `contributing/samples/workflows/fan_out_fan_in/agent.py` — three functions\n    run in parallel from `START`, collected by a `JoinNode`, then aggregated.\n\n    ```python\n    join_node = JoinNode(name=\"join_for_results\")\n\n    root_agent = Workflow(\n        name=\"root_agent\",\n        edges=[(\n            \"START\",\n            (make_uppercase, count_characters, reverse_string),\n            join_node,\n            aggregate,\n        )],\n    )\n    ```\n",".agents/skills/adk-sample-creator/references/readme-template.md":"# Sample README structure\n\nEvery sample gets a `README.md` with these sections, in this order.\n\n## Overview\n\nWhat the sample does and which feature or pattern it exists to demonstrate.\n\n## Sample Inputs\n\nPrompts a reader can paste in to exercise the sample. Wrap each prompt in\nbackticks. If a prompt needs an explanation, leave a blank line between the\nprompt and the explanation and indent the explanation by two spaces — without\nthe blank line Markdown folds them into one list item.\n\n## Graph\n\nA Mermaid diagram of the structure, not of the request/response flow.\n\n-   For a `Workflow` root agent, draw the graph of nodes and edges.\n-   For an agent that orchestrates tools or sub-agents (`LlmAgent`,\n    `ManagedAgent`), draw the topology of the agent and its tools/sub-agents\n    instead of internal workflow nodes.\n\nKeep it to a few nodes and edges. A `user -> agent -> API -> tool -> user`\nsequence diagram is noise: it says nothing the topology does not.\n\n## How To\n\nThe key techniques the sample uses (for example `ctx.run_node`), with the few\nlines of code that show each one.\n\n## Related Guides\n\nLinks to the guides under `docs/guides/` that explain the classes used, each\nwith a one-line summary. From a sample at\n`contributing/samples/{category}/{sample_name}/` the guides are four levels up:\n\n```markdown\n- [Workflow](../../../../docs/guides/workflow/workflow/index.md) - Explains building complex multi-step graphs.\n```\n\n## Template\n\n````markdown\n# ADK Sample Name\n\n## Overview\n\nBrief description.\n\n## Sample Inputs\n\n- `Prompt example 1`\n\n- `Prompt example 2`\n\n  *Explanation or expected behavior*\n\n## Graph\n\nFor a Workflow root agent:\n\n```mermaid\ngraph TD\n    START --> MyNode\n```\n\nFor an agent orchestrating tools or sub-agents:\n\n```mermaid\ngraph TD\n    MyAgent[my_agent] -->|calls| MyTool(my_tool)\n```\n\n## How To\n\nExplain the details.\n\n## Related Guides\n\n- [Guide Title](../../../../docs/guides/path/to/guide.md) - Brief description of what the guide covers.\n````\n",".agents/skills/adk-setup/SKILL.md":"---\nname: adk-setup\ndescription: >-\n  Sets up a local ADK Python development environment in a git clone of the\n  open-source adk-python repository: a uv virtual environment, all dependency\n  extras, pre-commit hooks, and a first unit-test run. Runs only when\n  explicitly requested, never on its own. Use when asked to set up, bootstrap,\n  or repair a development checkout, install project dependencies, fix a\n  missing or broken .venv, or prepare a machine for contributing a pull\n  request. Don't use for debugging a running agent (use adk-debug), for commit\n  and pull-request mechanics (use adk-git), or for re-running formatters on a\n  checkout that is already set up (pre-commit run --all-files).\ndisable-model-invocation: true\n---\n\n# ADK Python Development Setup\n\nSet up a working `adk-python` checkout: a uv-managed virtual environment with\nevery dependency extra, pre-commit hooks, and a green unit-test run.\n\n## Prerequisites\n\n1. **Python.** ADK supports 3.11 through 3.15 (`requires-python = \">=3.11\"` in\n   `pyproject.toml`). These steps use 3.11, the version the repo's own tooling\n   defaults to.\n\n   ```bash\n   python3 --version\n   ```\n\n2. **uv.** Dependencies are declared in `pyproject.toml`. The `uv sync` step\n   below creates a local `uv.lock`, which this repository ignores. Run that step\n   before `tox`, whose lock runner requires the file.\n\n   ```bash\n   uv --version\n   ```\n\n   Install it if missing:\n\n   ```bash\n   curl -LsSf https://astral.sh/uv/install.sh | sh\n   ```\n\n## Setup\n\nRun these from the repository root.\n\n1. **Create and activate the virtual environment:**\n\n   ```bash\n   uv venv --python \"python3.11\" \".venv\"\n   source .venv/bin/activate\n   ```\n\n2. **Install every dependency extra:**\n\n   ```bash\n   uv sync --all-extras\n   ```\n\n   `--all-extras` is what pulls in the `test` and `dev` extras. Syncing only\n   the default dependencies leaves `pytest` and the linters uninstalled.\n\n3. **Install pre-commit and tox as standalone tools:**\n\n   ```bash\n   uv tool install pre-commit\n   uv tool install tox --with tox-uv\n   ```\n\n   Both are also in the `dev` extra; installing them as uv tools puts them on\n   PATH so the git hook and multi-version test runs work without an activated\n   venv.\n\n4. **Install `addlicense` (optional, requires Go):**\n\n   ```bash\n   go install github.com/google/addlicense@latest\n   ```\n\n   Without it the pre-commit hook prints `Warning: addlicense not installed,\n   skipping` and passes, so a missing Go toolchain does not block setup — CI\n   catches missing license headers instead.\n\n5. **Install the git hooks:**\n\n   ```bash\n   pre-commit install\n   ```\n\n   On each commit the hooks auto-format with `isort`, `pyink`, and `mdformat`,\n   then run `ruff`, `addlicense`, `codespell`, and the repo's own compliance\n   checks.\n\n6. **Verify the environment:**\n\n   ```bash\n   pytest tests/unittests -n auto\n   ```\n\n   A green run here is the success criterion for setup. `-n auto` needs\n   `pytest-xdist`, which arrives with the `test` extra in step 2.\n\n## Key commands\n\n| Task                                 | Command                                            |\n| :----------------------------------- | :------------------------------------------------- |\n| Run unit tests                       | `pytest tests/unittests`                           |\n| Run unit tests in parallel           | `pytest tests/unittests -n auto`                   |\n| Run one test file                    | `pytest tests/unittests/agents/test_base_agent.py` |\n| Run tests on every supported Python  | `tox` (uv downloads any missing interpreters)      |\n| Format and lint everything           | `pre-commit run --all-files`                       |\n| Launch the web UI                    | `adk web {agents_dir}`                             |\n| Run an agent from the CLI            | `adk run {agent_dir}`                              |\n| Build the wheel                      | `uv build`                                         |\n",".agents/skills/adk-style/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2FSKILL.md -->",".agents/skills/adk-style/references/async.md":"# Async and Concurrency Style Guide\n\n- **I/O belongs in `async def`**: network calls, file system access, database\n  queries, and subprocess waits all go in async functions. ADK runs everything\n  on one event loop, so a synchronous call inside it stalls every concurrent\n  agent, not just the caller.\n- **Don't block the event loop**: no synchronous HTTP clients, `time.sleep`,\n  or blocking file reads inside async code.\n- **Wrap synchronous I/O in `asyncio.to_thread`** when a library offers no\n  async API (`open()`, `pathlib`, most cloud SDK clients):\n\n```python\nasync def save_data(path: Path, data: bytes) -> None:\n  # Wrap the blocking write so the event loop stays free.\n  await asyncio.to_thread(path.write_bytes, data)\n```\n\nNo hook checks this — a blocking call passes CI and shows up later as\nunexplained latency under concurrency, so it is worth catching in review.\n",".agents/skills/adk-style/references/documentation.md":"# Documentation and Comments\n\n## Public API Documentation\n\n- **Classes**: explain the intended usage, with a concise example when the\n  usage is not obvious from the signature. Document every public attribute.\n  For Pydantic models, document fields with an attribute docstring under the\n  field — see the Pydantic reference.\n- **Methods and functions**: document every argument, the return value, and\n  each exception raised.\n- **Docstrings say what the API does.** Design history and rejected\n  alternatives belong in the pull request.\n\n## Internal Implementation Comments\n\n- **Explain why, not what.** The code says what it does; a comment earns its\n  place by saying why it does it that way — the constraint, the bug, or the\n  ordering requirement that is not visible from the code.\n- **No links to RFCs, design docs, issues, or pull requests.** They rot faster\n  than the code, and a reader who cannot open the link is left with nothing.\n  Put the reasoning in the comment itself and the link in the pull request.\n  The same applies to issue numbers, and to warnings, log lines and error\n  messages.\n",".agents/skills/adk-style/references/file-organization.md":"# File Organization\n\n- One class per file in `workflow/`.\n- New modules under `src/google/adk/` are private by default — see\n  the visibility reference for the naming rules and how to expose a\n  public symbol.\n\n## File Headers\n\nEvery module under `src/google/adk/` starts with:\n\n1. The Apache 2.0 license header (added by the `addlicense` hook).\n2. `from __future__ import annotations`.\n3. Imports: standard library, third party, then relative.\n\n`from __future__ import annotations` is checked by\n`scripts/compliance_checks.py`, which exempts `__init__.py`, `version.py`,\n`tests/`, and `contributing/samples/`.\n\n## Where tests go\n\nMirror the source path under `tests/unittests/`:\n\n```text\nsrc/google/adk/tools/environment/_edit_file_tool.py\ntests/unittests/tools/environment/test_edit_file_tool.py\n```\n\nWhen one source file needs several test files, use the source file name —\nwithout the leading underscore or extension — as a shared prefix:\n\n```text\nsrc/google/adk/workflow/_workflow.py\ntests/unittests/workflow/test_workflow.py\ntests/unittests/workflow/test_workflow_hitl.py\ntests/unittests/workflow/test_workflow_nested.py\n```\n",".agents/skills/adk-style/references/formatting.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Fformatting.md -->",".agents/skills/adk-style/references/imports.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Fimports.md -->",".agents/skills/adk-style/references/logging.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Flogging.md -->",".agents/skills/adk-style/references/pydantic.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Fpydantic.md -->",".agents/skills/adk-style/references/testing.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Ftesting.md -->",".agents/skills/adk-style/references/typing.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Ftyping.md -->",".agents/skills/adk-style/references/visibility.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Fvisibility.md -->",".agents/skills/adk-unit-design/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-unit-design%2FSKILL.md -->",".agents/skills/adk-unit-design/references/design-template.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-unit-design%2Freferences%2Fdesign-template.md -->",".agents/skills/adk-unit-guide/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-unit-guide%2FSKILL.md -->",".agents/skills/adk-unit-guide/references/guide-template.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-unit-guide%2Freferences%2Fguide-template.md -->",".agents/skills/adk-verify-snippets/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-verify-snippets%2FSKILL.md -->","AGENTS.md":"## Project Overview\n\nThe Agent Development Kit (ADK) is an open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents.\n\n### Key Components\n\n- **Agent**: Blueprint defining identity, instructions, and tools.\n- **Runner**: Stateless execution engine that orchestrates agent execution.\n- **Tool**: Functions/capabilities agents can call.\n- **Session**: Conversation state management.\n- **Memory**: Long-term recall across sessions.\n- **Workflow** (ADK 2.0): Graph-based orchestration of complex, multi-step agent interactions.\n- **BaseNode** (ADK 2.0): Contract for all nodes, supporting output streaming and human-in-the-loop steps.\n- **Context** (ADK 2.0): Holds execution state and telemetry context mapped 1:1 to nodes.\n\nFor details on how the Runner works and the invocation lifecycle, please refer to the `adk-architecture` skill and the referenced documentation therein.\n\n## ADK Knowledge, Architecture, and Style\n\nSkills related to ADK development are in `.agents/skills/`.\n\n## Project Architecture\n\nFor detailed architecture patterns, component descriptions, and core interfaces, please refer to the **`adk-architecture`** skill at `.agents/skills/adk-architecture/SKILL.md`.\n\n## Development Setup\n\nThe project uses `uv` for package management and Python 3.11+. Please refer to\nthe **`adk-setup`** skill at `.agents/skills/adk-setup/SKILL.md` for detailed\ninstructions.\n","contributing/samples/environment_and_skills/e2b_env_skill_toolset/skills/calc-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fe2b_env_skill_toolset%2Fskills%2Fcalc-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/e2b_env_skill_toolset/skills/text-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fe2b_env_skill_toolset%2Fskills%2Ftext-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/local_env_skill_toolset/skills/calc-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Flocal_env_skill_toolset%2Fskills%2Fcalc-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/local_env_skill_toolset/skills/text-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Flocal_env_skill_toolset%2Fskills%2Ftext-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/local_environment_skill/skills/weather-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Flocal_environment_skill%2Fskills%2Fweather-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/local_environment_skill/skills/weather-skill/references/weather_info.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Flocal_environment_skill%2Fskills%2Fweather-skill%2Freferences%2Fweather_info.md -->","contributing/samples/environment_and_skills/skills/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2FREADME.md -->","contributing/samples/environment_and_skills/skills/skills/weather-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Fskills%2Fweather-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/skills/skills/weather-skill/references/weather_info.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Fskills%2Fweather-skill%2Freferences%2Fweather_info.md -->","contributing/samples/environment_and_skills/skills/tests/current_humidity.json":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Ftests%2Fcurrent_humidity.json -->","contributing/samples/environment_and_skills/skills/tests/current_weather.json":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Ftests%2Fcurrent_weather.json -->","contributing/samples/environment_and_skills/skills/tests/support_hours.json":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Ftests%2Fsupport_hours.json -->","contributing/samples/environment_and_skills/skills/tests/wind_speed.json":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Ftests%2Fwind_speed.json -->","contributing/samples/environment_and_skills/skills_agent/skills/weather-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills_agent%2Fskills%2Fweather-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/skills_agent/skills/weather-skill/references/weather_info.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills_agent%2Fskills%2Fweather-skill%2Freferences%2Fweather_info.md -->","contributing/samples/environment_and_skills/skills_inject_state/skills/code-review-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills_inject_state%2Fskills%2Fcode-review-skill%2FSKILL.md -->","contributing/samples/plugins/plugin_basic/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fplugins%2Fplugin_basic%2FREADME.md -->","contributing/samples/plugins/plugin_reflect_tool_retry/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fplugins%2Fplugin_reflect_tool_retry%2FREADME.md -->","contributing/samples/tools/built_in_multi_tools/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Fbuilt_in_multi_tools%2FREADME.md -->","contributing/samples/tools/function_tools/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Ffunction_tools%2FREADME.md -->","contributing/samples/tools/long_running_functions/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Flong_running_functions%2FREADME.md -->","contributing/samples/tools/model_consult/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Fmodel_consult%2FREADME.md -->","contributing/samples/tools/output_schema_with_tools/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Foutput_schema_with_tools%2FREADME.md -->","contributing/samples/tools/parallel_functions/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Fparallel_functions%2FREADME.md -->","contributing/samples/tools/pydantic_argument/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Fpydantic_argument%2FREADME.md -->","docs/guides/agents/base_agent/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fbase_agent%2Findex.md -->","docs/guides/agents/config/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fconfig%2Findex.md -->","docs/guides/agents/context/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fcontext%2Findex.md -->","docs/guides/agents/invocation_context/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Finvocation_context%2Findex.md -->","docs/guides/agents/llm_agent/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fllm_agent%2Findex.md -->","docs/guides/agents/llm_agent/single_turn.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fllm_agent%2Fsingle_turn.md -->","docs/guides/agents/llm_agent/task.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fllm_agent%2Ftask.md -->","docs/guides/agents/managed_agent/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fmanaged_agent%2Findex.md -->","docs/guides/agents/remote_a2a_agent/task.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fremote_a2a_agent%2Ftask.md -->","docs/guides/agents/run_config/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Frun_config%2Findex.md -->","docs/guides/live/tools/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Flive%2Ftools%2Findex.md -->","docs/guides/plugins/reflect_retry_model_plugin/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fplugins%2Freflect_retry_model_plugin%2Findex.md -->","docs/guides/plugins/reflect_retry_tool_plugin/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fplugins%2Freflect_retry_tool_plugin%2Findex.md -->","docs/guides/plugins/tool_call_integrity_plugin/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fplugins%2Ftool_call_integrity_plugin%2Findex.md -->","docs/guides/skills/skill/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fskills%2Fskill%2Findex.md -->","docs/guides/skills/skill_registry/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fskills%2Fskill_registry%2Findex.md -->","docs/guides/tools/agent_tool/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fagent_tool%2Findex.md -->","docs/guides/tools/base_tool/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fbase_tool%2Findex.md -->","docs/guides/tools/base_toolset/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fbase_toolset%2Findex.md -->","docs/guides/tools/function_tool/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Ffunction_tool%2Findex.md -->","docs/guides/tools/long_running_tool/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Flong_running_tool%2Findex.md -->","docs/guides/tools/mcp_tool/agent_to_mcp/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmcp_tool%2Fagent_to_mcp%2Findex.md -->","docs/guides/tools/mcp_tool/mcp_toolset/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmcp_tool%2Fmcp_toolset%2Findex.md -->","docs/guides/tools/model_consult/force_escalation_plugin/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmodel_consult%2Fforce_escalation_plugin%2Findex.md -->","docs/guides/tools/model_consult/model_consult_tool/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmodel_consult%2Fmodel_consult_tool%2Findex.md -->","docs/guides/tools/model_consult/observability_plugin/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmodel_consult%2Fobservability_plugin%2Findex.md -->","docs/guides/tools/node_tool/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fnode_tool%2Findex.md -->","docs/guides/tools/skill_toolset/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fskill_toolset%2Findex.md -->","docs/guides/tools/tool_args_matcher/tool_args_matcher/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Ftool_args_matcher%2Ftool_args_matcher%2Findex.md -->","docs/guides/tools/tool_confirmation/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Ftool_confirmation%2Findex.md -->","docs/guides/tools/tool_context/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Ftool_context%2Findex.md -->","llms-full.txt":"# Agent Development Kit (ADK) - New llms-full.txt location\n\n> The machine-readable llms.txt documentation for Agent Development Kit (ADK) is\n> no longer hosted statically in this repository. It is now automatically\n> generated and hosted on the ADK documentation site. Please use the links below\n> to access the source of truth.\n\n- [llms.txt](https://adk.dev/llms.txt)\n- [llms-full.txt](https://adk.dev/llms-full.txt)\n- [ADK Documentation](https://adk.dev/)\n- [ADK - Coding with AI](https://adk.dev/tutorials/coding-with-ai/)\n","llms.txt":"# Agent Development Kit (ADK) - New llms.txt location\n\n> The machine-readable llms.txt documentation for Agent Development Kit (ADK) is\n> no longer hosted statically in this repository. It is now automatically\n> generated and hosted on the ADK documentation site. Please use the links below\n> to access the source of truth.\n\n- [llms.txt](https://adk.dev/llms.txt)\n- [llms-full.txt](https://adk.dev/llms-full.txt)\n- [ADK Documentation](https://adk.dev/)\n- [ADK - Coding with AI](https://adk.dev/tutorials/coding-with-ai/)\n","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2FSKILL.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_agg.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_agg.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_causal_effect.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_causal_effect.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_classify.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_classify.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_detect_anomalies.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_detect_anomalies.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_evaluate.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_evaluate.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_forecast.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_forecast.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_generate.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate_embedding.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_generate_embedding.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate_table.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_generate_table.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_if.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_if.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_key_drivers.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_key_drivers.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_score.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_score.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_search.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_search.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_similarity.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_similarity.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_contribution_analysis.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_contribution_analysis.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_correlation.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_correlation.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_detect_change_points.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_detect_change_points.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_seasonality.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_seasonality.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_trend.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_trend.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/remote_models.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fremote_models.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/vector_search.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fvector_search.md -->","src/google/adk/integrations/bigquery/skills/bigquery-graph/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2FSKILL.md -->","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/best_practices.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fgraph_schema%2Fbest_practices.md -->","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/ddl_reference.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fgraph_schema%2Fddl_reference.md -->","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/feature_parity.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fgraph_schema%2Ffeature_parity.md -->","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/graph_schema_ddl_advisor.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fgraph_schema%2Fgraph_schema_ddl_advisor.md -->","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/semantic_queries.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fsemantic_queries.md -->","src/google/adk/skills/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fskills%2FREADME.md -->"},"files":{".agents/skills/adk-agent-builder/SKILL.md":"---\nname: adk-agent-builder\ndescription: >-\n  Builds ADK (Agent Development Kit) Python agents: LLM agents with tools,\n  graph workflows of function and agent nodes, conditional routing, fan-out and\n  join, schema-validated delegation between agents, human-in-the-loop pauses,\n  and pytest coverage for all of it. Use when asked to create an agent or a\n  workflow, add a tool to one, branch or loop between nodes, run steps in\n  parallel, pause for user approval, or test an agent. Don't use for explaining\n  how ADK works internally or designing its core components (use\n  `adk-architecture`), for an agent that already runs but misbehaves (use\n  `adk-debug`), for authoring a sample under `contributing/` (use\n  `adk-sample-creator`), or for naming, typing, and formatting conventions (use\n  `adk-style`).\n---\n\n# ADK Agent Builder\n\nRead only the reference that matches the task. Loading the whole tree costs\ncontext and buries the part that matters.\n\nEvery API below was checked against `google-adk` 2.6.2. If a symbol is missing\nat runtime, read the source under `src/google/adk/` rather than guessing a\nneighbouring name.\n\n## Start here\n\n| Task | Reference |\n|---|---|\n| First agent, environment, `adk` CLI | [getting-started.md](references/getting-started.md) |\n| Which import path is the canonical one | [import-paths.md](references/import-paths.md) |\n| The rules that cause most runtime failures | [best-practices.md](references/best-practices.md) |\n\n## Building blocks\n\n- [tool-catalog.md](references/tool-catalog.md) — function tools, MCP, OpenAPI,\n  Google API toolsets, built-in tools, custom `BaseTool` and `BaseToolset`.\n- [function-nodes.md](references/function-nodes.md) — plain functions as nodes:\n  parameter resolution, generators, `node_input` typing rules.\n- [llm-agent-nodes.md](references/llm-agent-nodes.md) — an `LlmAgent` used as a\n  workflow node: output types, instruction templates, `output_schema`,\n  auto-wrapping behavior.\n- [task-mode.md](references/task-mode.md) — `mode='task'` and\n  `mode='single_turn'` delegation with schema-validated input and output.\n\n## Graph orchestration\n\n- [routing-and-conditions.md](references/routing-and-conditions.md) — routed\n  edges, dict routing maps, default routes, self-loops, revision loops.\n- [parallel-and-fanout.md](references/parallel-and-fanout.md) — fan-out edges,\n  `JoinNode` fan-in, `parallel_worker=True` list processing.\n- [dynamic-nodes.md](references/dynamic-nodes.md) — scheduling nodes at runtime\n  with `ctx.run_node()` and imperative workflow construction.\n- [human-in-the-loop.md](references/human-in-the-loop.md) — `RequestInput`,\n  resume behavior, resumable vs replayed sessions.\n- [advanced-patterns.md](references/advanced-patterns.md) — nested workflows,\n  retries, custom `BaseNode` subclasses, graph validation rules.\n- [multi-agent.md](references/multi-agent.md) — chat-transfer hierarchies, and\n  the deprecated `SequentialAgent` / `LoopAgent` / `ParallelAgent` shells that\n  `Workflow` replaces.\n\n## Runtime and verification\n\n- [state-and-events.md](references/state-and-events.md) — the `Context` object,\n  `Event` fields, and how state flows between nodes.\n- [session-and-state.md](references/session-and-state.md) — session services,\n  artifacts, memory, and state key scoping.\n- [callbacks-and-plugins.md](references/callbacks-and-plugins.md) — the six\n  agent callbacks and app-level plugins.\n- [testing.md](references/testing.md) — `pytest` with `InMemoryRunner`, faking a\n  model, asserting on node output.\n",".agents/skills/adk-agent-builder/references/advanced-patterns.md":"# Advanced Workflow Patterns\n\nNested workflows, retries, timeouts, custom node classes, and the graph\nvalidation rules that reject a malformed graph at construction time.\n\n```python\nfrom google.adk import Context, Event, Workflow\nfrom google.adk.workflow import BaseNode, Edge, FunctionNode, RetryConfig, START\n```\n\n## Nested workflows\n\nA `Workflow` is both an agent and a node, so one can sit inside another. The\ninner workflow takes the predecessor's output as its `START` input, and its\nterminal output flows on to the next node outside.\n\n```python\ninner = Workflow(name='inner_pipeline', edges=[('START', step_a, step_b)])\n\nouter = Workflow(\n    name='outer_pipeline',\n    edges=[('START', pre_process, inner, post_process)],\n)\n```\n\n## Retries\n\nEvery `RetryConfig` field defaults to `None`, which means \"use the built-in\nfallback\" rather than \"disabled\":\n\n| Field | Fallback | Meaning |\n|---|---|---|\n| `max_attempts` | 5 | Total attempts including the first; 0 or 1 disables retrying |\n| `initial_delay` | 1.0 | Seconds before the first retry |\n| `max_delay` | 60.0 | Ceiling on the computed delay |\n| `backoff_factor` | 2.0 | Multiplier applied per attempt |\n| `jitter` | 1.0 | Randomness factor; 0.0 removes it |\n| `exceptions` | all | Exception classes or class-name strings to retry on |\n\n```python\napi_node = FunctionNode(\n    func=flaky_api_call,\n    name='api_call',\n    retry_config=RetryConfig(max_attempts=5, exceptions=[TimeoutError]),\n)\n```\n\nDelay for attempt *n* is\n`min(initial_delay * backoff_factor ** n, max_delay) * (1 + random(0, jitter))`.\n\nRead the current try from the context — it is 1 on the first attempt:\n\n```python\ndef my_node(ctx: Context, node_input: str) -> str:\n  if ctx.attempt_count > 1:\n    logger.warning('retry %d', ctx.attempt_count)\n  return 'result'\n```\n\n## Timeouts\n\n`timeout` is a per-node wall-clock limit in seconds. Exceeding it raises\n`NodeTimeoutError` (importable from `google.adk.workflow`), which the retry\nmachinery treats like any other exception.\n\n## Execution cancellation\n\nPass an `asyncio.Event` as `abort_signal` to `runner.run_async` to halt an\nin-flight workflow or agent cleanly. Setting the signal cancels active tasks,\nemits a terminal `Event(error_code='INVOCATION_ABORTED')`, seals any dangling\n`FunctionCall` events with a synthetic `FunctionResponse`, and still runs\n`after_run` plugin callbacks.\n\n```python\nimport asyncio\n\nabort_signal = asyncio.Event()\n\nasync for event in runner.run_async(\n    user_id='u1',\n    session_id='s1',\n    new_message=user_content,\n    abort_signal=abort_signal,\n):\n  if should_stop(event):\n    abort_signal.set()\n```\n\nInside a node or tool, `ctx.is_aborted` reflects whether the signal has been\nset.\n\n## Custom node classes\n\n`BaseNode` is a Pydantic model. Declare fields as fields, and override\n`_run_impl` — **not** `run`, which is `@final` and does the normalization of\nyielded values into events.\n\n```python\nfrom typing import Any, AsyncGenerator\n\nfrom typing_extensions import override\n\n\nclass BatchProcessorNode(BaseNode):\n  \"\"\"Processes a list of items in fixed-size batches.\"\"\"\n\n  batch_size: int = 10\n\n  @override\n  async def _run_impl(\n      self, *, ctx: Context, node_input: Any\n  ) -> AsyncGenerator[Any, None]:\n    items = node_input if isinstance(node_input, list) else [node_input]\n    results = []\n    for i in range(0, len(items), self.batch_size):\n      results.extend(await process_batch(items[i:i + self.batch_size]))\n    yield Event(output=results)\n\n\nbatcher = BatchProcessorNode(name='batch_processor', batch_size=25)\n```\n\n`_run_impl` may yield an `Event`, a `RequestInput`, a bare value (wrapped as\n`Event(output=...)`), or `None` (skipped). There is no `get_name()` to override\n— the node's name is the `name` field.\n\n### `BaseNode` fields\n\n| Field | Default | Purpose |\n|---|---|---|\n| `name` | required | Node identity within the graph; must be unique |\n| `description` | `''` | Human-readable label |\n| `rerun_on_resume` | `False` | Re-run after an interrupt instead of taking the answer as output |\n| `wait_for_output` | `False` | Finishing without an output event leaves the node WAITING, not COMPLETED |\n| `retry_config` | `None` | Retry policy |\n| `timeout` | `None` | Seconds before `NodeTimeoutError` |\n| `input_schema` | `None` | Validates and coerces `node_input` |\n| `output_schema` | `None` | Validates and coerces `event.output` |\n| `state_schema` | `None` | Validates `ctx.state` writes; `app:`, `user:`, `temp:` keys bypass it |\n\n### `wait_for_output`\n\nWith `wait_for_output=True`, a node that completes without emitting an output\nevent moves to WAITING rather than COMPLETED, and no downstream node fires. An\nupstream predecessor can trigger it again later — useful for a node that\naccumulates across several triggers before producing one answer.\n\n```python\nclass CollectorNode(BaseNode):\n  wait_for_output: bool = True\n\n  @override\n  async def _run_impl(self, *, ctx, node_input):\n    collected = ctx.state.get('collected', []) + [node_input]\n    yield Event(state={'collected': collected})\n    if len(collected) >= 3:\n      yield Event(output=collected)  # now COMPLETED, downstream fires\n```\n\n`JoinNode` reaches a similar result by a different mechanism — it sets\n`_requires_all_predecessors`, so the orchestrator holds it until every\npredecessor has run and then hands it all their outputs at once.\n\n## Wrapping a tool as a node\n\n`_ToolNode` is private and keyword-only. Its input must be a dict of tool\narguments, or `None`.\n\n```python\nfrom google.adk.tools import FunctionTool\nfrom google.adk.workflow._tool_node import _ToolNode\n\n\ndef search(query: str) -> str:\n  \"\"\"Search for information.\"\"\"\n  return f'Results for: {query}'\n\n\ntool_node = _ToolNode(tool=FunctionTool(search), name='search_node')\n\nagent = Workflow(\n    name='with_tool',\n    edges=[('START', prepare_query, tool_node, process_results)],\n)\n```\n\n## Graph validation\n\n`Workflow` validates the graph when it is constructed, in this order. Each check\nraises `ValueError` naming the offending node or edge.\n\n1. No duplicate node names.\n2. A `START` node exists.\n3. No edge leaving `START` carries a route.\n4. Every node is reachable from `START`, and `START` has no incoming edges.\n5. No two edges share both a source and a target — routes are not part of edge\n   identity.\n6.  A `route` list on one edge never mixes `__DEFAULT__` with named routes.\n7. No unconditional cycle — a cycle needs at least one routed edge.\n8. Where a source declares `output_schema` and its target declares\n   `input_schema`, the two must be the same schema.\n9. No edge into a `mode='chat'` `LlmAgent` from anything but `START`, because a\n   chat agent reads conversation history rather than a node input.\n\nNodes with no outgoing edges are the graph's terminals; their outputs become the\nworkflow's own output.\n\n## Ways to declare edges\n\n```python\nedges = [\n    ('START', node_a),                 # simple\n    (node_a, node_b, 'route'),         # routed\n    (node_a, (node_b, node_c)),        # fan-out\n    ((node_b, node_c), join_node),     # fan-in\n    ('START', node_a, node_b, node_c), # chain of three edges\n    (classifier, {'ok': handler_a, 'err': handler_b}),  # routing map\n]\n```\n\n`Edge` objects are the explicit form. It is a Pydantic model, so its fields are\nkeyword-only:\n\n```python\nedges = [\n    Edge(from_node=START, to_node=node_a),\n    Edge(from_node=node_a, to_node=node_b, route='success'),\n]\n```\n\nTo build the graph yourself, pass `graph=` instead of `edges=`:\n\n```python\nfrom google.adk.workflow._graph import Graph\n\ngraph = Graph.from_edge_items([('START', node_a), (node_a, node_b)])\nagent = Workflow(name='my_workflow', graph=graph)\n```\n\n## Where the code lives\n\n| Component | File |\n|---|---|\n| `Workflow` | `src/google/adk/workflow/_workflow.py` |\n| `Graph`, `Edge`, `DEFAULT_ROUTE` | `src/google/adk/workflow/_graph.py` |\n| graph validation rules | `src/google/adk/workflow/utils/_graph_validation.py` |\n| `BaseNode`, `START` | `src/google/adk/workflow/_base_node.py` |\n| `FunctionNode` | `src/google/adk/workflow/_function_node.py` |\n| `@node`, `Node` | `src/google/adk/workflow/_node.py` |\n| `JoinNode` | `src/google/adk/workflow/_join_node.py` |\n| `_ParallelWorker` | `src/google/adk/workflow/_parallel_worker.py` |\n| `_ToolNode` | `src/google/adk/workflow/_tool_node.py` |\n| `RetryConfig` | `src/google/adk/workflow/_retry_config.py` |\n| running an `LlmAgent` as a node | `src/google/adk/workflow/_llm_agent_wrapper.py` |\n| dynamic node scheduling | `src/google/adk/workflow/_dynamic_node_scheduler.py` |\n| `Context` | `src/google/adk/agents/context.py` |\n| `Event` | `src/google/adk/events/event.py` |\n| `RequestInput` | `src/google/adk/events/request_input.py` |\n",".agents/skills/adk-agent-builder/references/best-practices.md":"# ADK Workflow Rules That Bite\n\nThe failure modes that account for most broken ADK workflows. Each one is a\nsilent or confusing failure, not a clear error — that is why they are collected\nhere rather than left to be discovered.\n\n## Type everything with Pydantic models\n\nUse a `BaseModel` for node inputs, node outputs, LLM `output_schema`,\n`RequestInput.response_schema`, and structured state values. `dict[str, Any]`\ngives up validation, IDE completion, and the automatic dict-to-model conversion\nthat `FunctionNode` performs from type hints.\n\n```python\n# Loses validation and downstream typing\ndef lookup_flights(node_input: dict[str, Any]) -> dict[str, Any]:\n  return {'flight_cost': 500, 'details': 'Economy'}\n\n\n# Validated on the way in and on the way out\nclass FlightInfo(BaseModel):\n  flight_cost: int\n  details: str\n\n\ndef lookup_flights(node_input: Itinerary) -> FlightInfo:\n  return FlightInfo(flight_cost=500, details='Economy')\n```\n\n## `event.output` is plumbing; `event.content` is the UI\n\nThe web UI renders `event.content` only. A node that produces something a human\nshould read must emit it as a message, in addition to the output the next node\nconsumes.\n\n```python\ndef final_output(node_input: str):\n  yield Event(message=node_input)  # rendered in the web UI\n  yield Event(output=node_input)   # passed to the downstream node\n```\n\n`Event(message=...)` is a constructor convenience that writes `event.content`.\nStream partial text with `Event(message='chunk', partial=True)`. LLM agents emit\ntheir content events automatically; function nodes do not.\n\n## Write state through `Event(state=...)`, not `ctx.state[key] = ...`\n\nState passed to the `Event` constructor lands in `event.actions.state_delta`, so\nit is part of event history and survives the replay that non-resumable\nhuman-in-the-loop performs. A direct `ctx.state` mutation is a side effect that\nreplay does not reproduce.\n\n```python\n# Recorded in event history\ndef save(node_input: str):\n  return Event(output=node_input, state={'user_request': node_input})\n\n\n# Lost on replay\ndef save(ctx: Context, node_input: str) -> str:\n  ctx.state['user_request'] = node_input\n  return node_input\n```\n\nReading is always `ctx.state[...]`.\n\n## At most one output event per node execution\n\nA node may yield many events, but only one of them may carry `output`. Two\noutput events are merged into a list, which silently changes the downstream\nnode's `node_input` type from `str` to `list[str]`. The same limit applies to\n`route`, except there a second routed event raises `ValueError`.\n\n```python\ndef my_node(node_input: str):\n  yield Event(message='Processing...')   # display only\n  yield Event(state={'status': 'done'})  # state only\n  yield Event(output='final result')     # the one output\n```\n\nThis holds for function nodes, LLM agent nodes, and nested workflows alike.\n\n## A function either yields or returns — never both\n\nPython turns any function containing `yield` into a generator and discards its\n`return value`. Mixing the two loses the output with no error.\n\n```python\n# Generator: every event is yielded\ndef my_node(node_input: str):\n  yield Event(state={'key': 'value'})\n  yield Event(output='result')\n\n\n# Plain function: one return, which may be an Event or a bare value\ndef my_node(node_input: str):\n  return Event(output='result', state={'key': 'value'})\n\n\n# Broken: the return is silently ignored\ndef my_node(node_input: str):\n  yield Event(state={'key': 'value'})\n  return Event(output='result')\n```\n\n## `{var}` in an instruction reads state, never `node_input`\n\nInstruction placeholders resolve against `ctx.state` only. `node_input` is\ndelivered to the model as the user message, so `{node_input}` is just a missing\nstate key and raises `KeyError` at call time.\n\n```python\n# KeyError: 'node_input' is not a state key\nAgent(name='summarizer', instruction='Summarize this: {node_input}')\n\n# The predecessor's output is already the user message\nAgent(name='summarizer', instruction='Summarize the text in one sentence.')\n\n# Anything else the instruction needs must be in state first\nAgent(name='writer', instruction='Write about \"{topic}\". Feedback: {feedback?}')\n```\n\n`{var?}` substitutes an empty string when the key is absent; `{var}` raises.\n\n## A chat-mode agent can only follow `START`\n\nGraph validation rejects an edge into an `LlmAgent` with `mode='chat'` from any\nnode other than `START`, because a chat agent reads conversational history\nrather than a node input. Set `mode='single_turn'` (the default for an\nauto-wrapped agent) or `mode='task'` on agents used mid-graph.\n\n## Everything in `event.output` must be JSON-serializable\n\n- `FunctionNode` converts a returned `BaseModel` with `model_dump()`, so\n  returning a model is safe. A `types.Content` or any other non-serializable\n  object is not.\n- An LLM agent with `output_schema` validates then dumps, so\n  `ctx.state[output_key]` is a plain `dict`, never a model instance. Read it\n  with `data['field']`, or rebuild the model with `MyModel(**data)`.\n- This bites hardest at a `JoinNode`, which parks partial inputs in session\n  state while waiting. If a predecessor is an LLM agent without `output_schema`,\n  the parked value is a `types.Content` and `DatabaseSessionService` raises\n  `TypeError` on write.\n\n## Give every loop iteration its own `interrupt_id`\n\nA node that yields `RequestInput` inside a loop must vary the `interrupt_id` per\niteration. Reusing one id makes event-based state reconstruction match an\nearlier iteration's answer to the current interrupt, which restarts the loop\nforever.\n\n```python\nreview_count = ctx.state.get('review_count', 0)\nyield RequestInput(\n    interrupt_id=f'review_{review_count}',\n    message='Approve?',\n)\n```\n\nIncrement the counter through `Event(state={'review_count': review_count + 1})`\nwhen the response arrives.\n",".agents/skills/adk-agent-builder/references/callbacks-and-plugins.md":"# Callbacks and Plugins\n\nCallbacks hook one agent; plugins hook every agent under an `App`. Both follow\nthe same contract: **return `None` to let the normal thing happen, return a\nvalue to replace it.**\n\n```python\nfrom google.adk.agents.callback_context import CallbackContext\nfrom google.adk.models.llm_request import LlmRequest\nfrom google.adk.models.llm_response import LlmResponse\nfrom google.adk.tools import BaseTool, ToolContext\n```\n\n`CallbackContext` and `ToolContext` are both aliases for `Context`.\n\n## The eight agent callbacks\n\n| Field | Arguments | Return to override |\n|---|---|---|\n| `before_agent_callback` | `(CallbackContext)` | `types.Content` — skips the agent entirely |\n| `after_agent_callback` | `(CallbackContext)` | `types.Content` — replaces the agent's output |\n| `before_model_callback` | `(CallbackContext, LlmRequest)` | `LlmResponse` — skips the model call |\n| `after_model_callback` | `(CallbackContext, LlmResponse)` | `LlmResponse` — replaces the response |\n| `on_model_error_callback` | `(CallbackContext, LlmRequest, Exception)` | `LlmResponse` — suppresses the error |\n| `before_tool_callback` | `(BaseTool, dict, ToolContext)` | `dict` — skips the tool call |\n| `after_tool_callback` | `(BaseTool, dict, ToolContext, dict)` | `dict` — replaces the tool result |\n| `on_tool_error_callback` | `(BaseTool, dict, ToolContext, Exception)` | `dict` — suppresses the error |\n\nEvery one may be sync or async, and every one accepts either a single callable\nor a list. A list runs in order and stops at the first callback that returns\nsomething other than `None`.\n\n## Examples\n\nBlocking a request before it reaches the model:\n\n```python\ndef guard(\n    callback_context: CallbackContext, llm_request: LlmRequest\n) -> LlmResponse | None:\n  for content in llm_request.contents:\n    for part in content.parts or []:\n      if part.text and 'unsafe' in part.text:\n        return LlmResponse(content=types.ModelContent('I cannot process that.'))\n  return None\n\n\nagent = LlmAgent(\n    name='guarded', model='gemini-3.5-flash', before_model_callback=guard\n)\n```\n\nObserving without changing anything — note the explicit `return None`:\n\n```python\ndef log_response(\n    callback_context: CallbackContext, llm_response: LlmResponse\n) -> LlmResponse | None:\n  logger.info('model said: %s', llm_response.content)\n  return None\n```\n\nAuditing and repairing tool calls:\n\n```python\ndef audit(tool: BaseTool, args: dict, tool_context: ToolContext) -> dict | None:\n  logger.info('calling %s with %s', tool.name, args)\n  return None\n\n\ndef repair(\n    tool: BaseTool, args: dict, tool_context: ToolContext, tool_response: dict\n) -> dict | None:\n  if 'error' in tool_response:\n    return {'result': 'Tool execution failed, please try again.'}\n  return None\n\n\nagent = LlmAgent(\n    name='audited',\n    model='gemini-3.5-flash',\n    tools=[my_tool],\n    before_tool_callback=audit,\n    after_tool_callback=repair,\n)\n```\n\nDegrading gracefully on failure:\n\n```python\ndef handle_model_error(\n    callback_context: CallbackContext,\n    llm_request: LlmRequest,\n    error: Exception,\n) -> LlmResponse | None:\n  return LlmResponse(content=types.ModelContent('Service unavailable.'))\n\n\nagent = LlmAgent(\n    name='resilient',\n    model='gemini-3.5-flash',\n    on_model_error_callback=handle_model_error,\n)\n```\n\n## Plugins\n\nA plugin is the same set of hooks applied to every agent, tool, and model call\nin an app, plus a few that only make sense at app scope. All hooks are async and\nkeyword-only.\n\n```python\nfrom google.adk.plugins.base_plugin import BasePlugin\n\n\nclass MyPlugin(BasePlugin):\n\n  def __init__(self):\n    super().__init__(name='my_plugin')\n\n  async def before_agent_callback(self, *, agent, callback_context):\n    return None\n\n  async def before_model_callback(self, *, callback_context, llm_request):\n    return None\n```\n\nBeyond the eight agent-level hooks, `BasePlugin` adds\n`on_user_message_callback`, `before_run_callback`, `on_event_callback`,\n`after_run_callback`, `on_agent_error_callback`, and `on_run_error_callback`.\n\nRegister plugins on the `App`:\n\n```python\nfrom google.adk.apps import App\nfrom google.adk.plugins.context_filter_plugin import ContextFilterPlugin\n\napp = App(\n    name='my_app',\n    root_agent=root_agent,\n    plugins=[ContextFilterPlugin(num_invocations_to_keep=3)],\n)\n```\n\n## Built-in plugins\n\n| Plugin | Module under `google.adk.plugins` | Purpose |\n|---|---|---|\n| `ContextFilterPlugin` | `context_filter_plugin` | Trims history to the last N invocations |\n| `SaveFilesAsArtifactsPlugin` | `save_files_as_artifacts_plugin` | Stores file outputs as session artifacts |\n| `GlobalInstructionPlugin` | `global_instruction_plugin` | Prepends an instruction to every agent |\n| `LoggingPlugin` | `logging_plugin` | Logs the invocation lifecycle |\n| `DebugLoggingPlugin` | `debug_logging_plugin` | Verbose request and response logging |\n| `ReflectAndRetryToolPlugin` | `reflect_retry_tool_plugin` | Retries a failed tool call after letting the model reflect |\n| `MultimodalToolResultsPlugin` | `multimodal_tool_results_plugin` | Routes non-text tool results into content |\n| `AutoTracingPlugin` | `auto_tracing_plugin` | Emits tracing spans automatically |\n| `BigQueryAgentAnalyticsPlugin` | `bigquery_agent_analytics_plugin` | Exports invocation analytics to BigQuery |\n",".agents/skills/adk-agent-builder/references/dynamic-nodes.md":"# Dynamic Node Scheduling\n\n`await ctx.run_node(...)` runs another node from inside a node and returns its\noutput. It turns graph control flow into ordinary Python: loops, conditionals,\nand early exits, written as loops, conditionals, and early exits.\n\n```python\nfrom google.adk import Agent, Context, Event, Workflow\nfrom google.adk.workflow import FunctionNode, node\n```\n\n## Example\n\n```python\nclass Feedback(BaseModel):\n  grade: str\n\n\ngenerate_headline = Agent(\n    name='generate_headline',\n    instruction='Write a headline about the topic \"{topic}\".',\n)\n\nevaluate_headline = Agent(\n    name='evaluate_headline',\n    mode='single_turn',\n    instruction='Grade whether the headline is tech-related.',\n    output_schema=Feedback,\n)\n\n\n@node(rerun_on_resume=True)\nasync def orchestrate(ctx: Context, node_input: str) -> str:\n  yield Event(state={'topic': node_input})\n  while True:\n    headline = await ctx.run_node(generate_headline)\n    feedback = Feedback.model_validate(\n        await ctx.run_node(evaluate_headline, node_input=headline)\n    )\n    if feedback.grade == 'tech-related':\n      yield headline\n      break\n\n\nroot_agent = Workflow(name='root_agent', edges=[('START', orchestrate)])\n```\n\n## `ctx.run_node` arguments\n\n```python\nawait ctx.run_node(\n    node,                     # a function, Agent, BaseTool, or BaseNode\n    node_input=None,\n    *,\n    use_as_output=False,\n    run_id=None,\n    use_sub_branch=False,\n    override_branch=None,\n)\n```\n\n| Argument | Effect |\n|---|---|\n| `use_as_output` | The child's output becomes the parent's output; the parent's own output events are suppressed |\n| `run_id` | Names this execution instead of auto-numbering it |\n| `use_sub_branch` | Appends `node_name@run_id` to the branch, isolating events from sibling runs |\n| `override_branch` | Uses a specific branch instead of the parent's |\n\n## Rules the framework enforces\n\n**The calling node needs `rerun_on_resume=True`.** Calling `run_node` without it\nraises immediately. The reason is resumption: a dynamically scheduled child may\ninterrupt for user input, and the only way the parent can receive the answer is\nto be re-run from the top.\n\n**An explicit `run_id` must contain a non-digit.** Auto-generated ids are plain\nnumbers (`\"1\"`, `\"2\"`, ...), so an all-digit custom id would collide with one.\n`ValueError` names the offending id.\n\n**`use_as_output=True` at most once per parent execution.** A second call raises\n`Node {path} already has a use_as_output delegate.` (A `Workflow` calling\n`run_node` is exempt.)\n\n**`await` the call directly.** Wrapping it in `asyncio.create_task()` leaves the\nchild unsupervised: its errors are swallowed and it is not cancelled when the\nparent is interrupted.\n\n## Imperative workflows\n\nStandard Python replaces routed edges entirely:\n\n```python\nasync def orchestrator(ctx: Context, node_input: str):\n  res_a = await ctx.run_node(step_a, node_input=node_input)\n  if 'success' in res_a:\n    return await ctx.run_node(step_b, node_input=res_a)\n  return await ctx.run_node(step_c, node_input=res_a)\n```\n\n### Three traps in this style\n\n**A raw function's parameters bind from state, not from `node_input`.** Node\nparameter binding defaults to `'state'`, so a value passed as\n`run_node(fn, node_input=x)` reaches the function only through a parameter\nliterally named `node_input`.\n\n```python\ndef my_worker(node_input: str):  # this name, or the value never arrives\n  return f'Done: {node_input}'\n```\n\n**A child that itself calls `run_node` is a parent too**, so it also needs\n`rerun_on_resume=True`. Raw functions default to `False`, so wrap it:\n\n```python\ninner = FunctionNode(func=inner_orchestrator, rerun_on_resume=True)\n```\n\n**A generator cannot `return` a value.** In a node that uses `yield`, produce\nthe result with `yield Event(output=...)`; `return value` is a syntax error in\nan async generator and silently ignored in a sync one.\n",".agents/skills/adk-agent-builder/references/function-nodes.md":"# Function Nodes\n\nAny Python function can be a workflow node. Put the callable straight into\n`edges` and the framework wraps it in a `FunctionNode`.\n\n```python\nfrom google.adk import Context, Event\nfrom google.adk.workflow import FunctionNode, RetryConfig, node\n```\n\n## Parameter resolution\n\n`FunctionNode` inspects the signature and fills each parameter by name:\n\n| Parameter name | Bound to |\n|---|---|\n| `ctx` | the workflow `Context` |\n| `node_input` | the predecessor node's output |\n| anything else | `ctx.state[param_name]`, falling back to the default |\n\n```python\ndef with_context(ctx: Context, node_input: str) -> str:\n  return f'Session {ctx.session.id}: {node_input}'\n\n\ndef input_only(node_input: str) -> str:\n  return node_input.upper()\n\n\ndef from_state(node_input: str, user_name: str) -> str:\n  # user_name comes from ctx.state['user_name']\n  return f'{user_name}: {node_input}'\n\n\ndef no_params() -> str:\n  return 'hello'\n```\n\nPass `parameter_binding='node_input'` to `FunctionNode` or `@node` to bind every\nparameter from a `node_input` dict instead of from state. That mode also infers\n`input_schema` and `output_schema` from the signature, and is what the framework\nuses when a node is exposed as an agent's tool.\n\n## Return values\n\nA plain return is wrapped in `Event(output=...)`. Returning `None` emits no\nevent, so no downstream node fires.\n\n```python\ndef process(node_input: str) -> str:\n  return f'Processed: {node_input}'\n\n\nasync def fetch_data(node_input: str) -> dict:\n  return {'data': await some_api_call(node_input)}\n\n\ndef maybe_output(node_input: str) -> str | None:\n  if not node_input:\n    return None  # downstream stays idle\n  return f'Got: {node_input}'\n```\n\nGenerators may yield `Event` objects or bare values — a bare yield is wrapped\nthe same way a return is.\n\n```python\nasync def multi(ctx: Context):\n  yield Event(message='working...')\n  yield 'output value'  # becomes Event(output='output value')\n```\n\n## Input coercion\n\n`FunctionNode` runs each argument through a Pydantic `TypeAdapter` built from\nthe annotation, so the annotation is both documentation and a coercion rule:\n\n| Annotation | Incoming value | Result |\n|---|---|---|\n| a `BaseModel` subclass | `dict` | validated model instance |\n| `list[Model]`, `dict[K, Model]` | nested dicts | recursively converted |\n| `str` (including `str \\| None`) | `types.Content` | concatenated text of the text parts |\n| anything else | any | validated by `TypeAdapter`, `TypeError` on mismatch |\n\nThe `types.Content` to `str` rule drops non-text parts (inline data, file data,\nexecutable code) and logs a warning when it does.\n\n```python\nclass Order(BaseModel):\n  item: str\n  quantity: int\n\n\ndef process_order(node_input: Order) -> str:\n  # {'item': 'widget', 'quantity': 3} arrives as Order(item='widget', quantity=3)\n  return f'Order: {node_input.quantity}x {node_input.item}'\n```\n\nA union annotation (`list | dict`) accepts anything — the adapter is satisfied\nby any member, so wrong types reach the body. Use `isinstance` checks inside the\nfunction when a union is unavoidable.\n\n## What `node_input` will actually be\n\n| Predecessor | `node_input` |\n|---|---|\n| function returning `str` / `dict` | that value |\n| function returning `Event(output=X)` | `X` |\n| `LlmAgent` without `output_schema` | `str` — the model's concatenated text |\n| `LlmAgent` with `output_schema` | `dict` — the validated model, dumped |\n| `JoinNode` | `dict[str, Any]` keyed by predecessor node name |\n| a `parallel_worker=True` node | `list` of per-item results |\n| `START` without `input_schema` | `types.Content` (the user's message) |\n| `START` with `input_schema` | the parsed schema type |\n\n## Explicit `FunctionNode`\n\nConstruct one directly when you need to override its properties. Every argument\nis keyword-only, including `func`.\n\n```python\napi_node = FunctionNode(\n    func=flaky_api_call,\n    name='api_call',             # defaults to func.__name__\n    rerun_on_resume=True,        # re-run after a human-in-the-loop interrupt\n    retry_config=RetryConfig(max_attempts=3, initial_delay=1.0),\n    timeout=30.0,\n)\n```\n\n## The `@node` decorator\n\n`@node` is the same thing with less ceremony, and it also accepts an already-made\nnode, an agent, or a tool.\n\n```python\n@node\ndef plain(node_input: str) -> str:\n  return node_input\n\n\n@node(name='custom_name', rerun_on_resume=True)\nasync def renamed(node_input: str) -> str:\n  return node_input\n\n\n# Called as a function on an existing callable\nmy_node = node(some_func, name='renamed')\n\n# Fan a list out across parallel workers\nworker = node(some_func, parallel_worker=True)\n```\n\nAccepted keywords: `name`, `rerun_on_resume`, `retry_config`, `timeout`,\n`parallel_worker`, `max_parallel_workers`, `auth_config`,\n`require_confirmation`, `parameter_binding`.\n\n## Routing and state from a node\n\n```python\ndef classify(node_input: str):\n  route = 'urgent' if 'urgent' in node_input else 'normal'\n  return Event(output=node_input, route=route)\n\n\ndef update_counter(node_input: str):\n  return Event(output=node_input, state={'last_input': node_input})\n```\n\n## Requiring confirmation before a node runs\n\n`require_confirmation` pauses the workflow with an `adk_request_input`\ninterrupt (`wf_tool_confirmation:{node_path}`) before the function runs. It\naccepts a `bool` or a sync/async predicate callable receiving the function's\nbound arguments (plus `ctx` or `node_input` if declared). `@node` automatically\ndefaults `rerun_on_resume=True` when `require_confirmation` is set.\n\n```python\n@node(require_confirmation=True)\ndef delete_file(path: str) -> str:\n  return f'Deleted {path}'\n\n\n@node(require_confirmation=lambda amount: amount > 1000)\ndef transfer_funds(amount: int) -> str:\n  return f'Transferred {amount}'\n```\n\n## Requiring authentication before a node runs\n\n`auth_config` makes the framework request user credentials before the node's\nfirst execution. It requires `rerun_on_resume=True`, because the node runs again\nonce the credential arrives.\n\n```python\nsecured = FunctionNode(\n    func=call_private_api,\n    auth_config=my_auth_config,\n    rerun_on_resume=True,\n)\n```\n\nInside the node, read the credential with\n`AuthHandler(auth_config).get_auth_response(ctx.state)`\n(`google.adk.auth.auth_handler.AuthHandler`).\n",".agents/skills/adk-agent-builder/references/getting-started.md":"# Getting Started: Creating ADK Agents\n\nEnvironment, the agent directory convention, a first LLM agent, and the jump to\ngraph workflows.\n\n## CLI commands\n\n| Command | What it does |\n|---|---|\n| `adk create {agent_name}` | Scaffolds an agent directory |\n| `adk run {agent_dir}` | Runs the agent in the terminal |\n| `adk web {agent_dir}` | Dev server on `http://localhost:8000` (development only) |\n| `adk api_server {agent_dir}` | HTTP API for the agent |\n\n## 1. Environment\n\n```bash\nuv venv --python python3.11 .venv\nsource .venv/bin/activate\nuv pip install google-adk\n```\n\n`pip install google-adk` in a `python -m venv` works too. ADK requires Python\n3.10 or newer.\n\n## 2. API keys\n\nPut a `.env` file in the **agent directory**, not its parent — the loader looks\nbeside `agent.py`. Do not commit it.\n\nGoogle AI Studio (get a key at https://aistudio.google.com/app/apikey):\n\n```bash\nGOOGLE_GENAI_USE_ENTERPRISE=FALSE\nGOOGLE_API_KEY=YOUR_API_KEY\n```\n\nVertex AI, after `gcloud auth application-default login`:\n\n```bash\nGOOGLE_GENAI_USE_ENTERPRISE=TRUE\nGOOGLE_CLOUD_PROJECT=your-project-id\nGOOGLE_CLOUD_LOCATION=us-central1\n```\n\nVertex AI express mode swaps the project/location pair for an API key:\n\n```bash\nGOOGLE_GENAI_USE_ENTERPRISE=TRUE\nGOOGLE_API_KEY=YOUR_EXPRESS_MODE_KEY\n```\n\n`GOOGLE_GENAI_USE_VERTEXAI` is the old name for the same switch. It still works\nbut emits a `DeprecationWarning`; `GOOGLE_GENAI_USE_ENTERPRISE` wins when both\nare set.\n\n## 3. Directory layout\n\nThe CLI discovers agents by convention:\n\n```text\nmy_agent/\n├── __init__.py    # from . import agent\n├── agent.py       # defines root_agent (and optionally app)\n└── .env\n```\n\n`__init__.py` must re-export the module, or the agent will not appear in\n`adk web`:\n\n```python\nfrom . import agent\n```\n\n## 4. A basic LLM agent\n\n`LlmAgent` (aliased as `Agent`) binds a model, an instruction, and tools.\n\n```python\nfrom google.adk import Agent\n\n\ndef get_weather(city: str) -> dict:\n  \"\"\"Returns the current weather for a specified city.\"\"\"\n  return {\n      'status': 'success',\n      'city': city,\n      'weather': 'sunny',\n      'temperature': '72F',\n  }\n\n\nroot_agent = Agent(\n    model='gemini-3.5-flash',\n    name='root_agent',\n    description='An assistant that reports the weather.',\n    instruction=(\n        'You are a helpful assistant. Use get_weather to look up the'\n        ' weather in any city. Be concise.'\n    ),\n    tools=[get_weather],\n)\n```\n\n| Field | Purpose |\n|---|---|\n| `model` | Model id, e.g. `'gemini-3.5-flash'`, `'gemini-3.1-pro-preview'` |\n| `instruction` | System prompt; `{var}` placeholders resolve from session state |\n| `tools` | Python callables; name, docstring, and type hints become the tool schema |\n| `description` | How a parent agent decides to route to this one |\n| `output_key` | Session-state key to store the agent's final text under |\n\nA tool function needs a docstring and type hints on every parameter — the LLM\nsees only those. Return a `dict` or a `str`.\n\n## 5. Running it programmatically\n\n```python\nimport asyncio\n\nfrom google.adk.runners import InMemoryRunner\nfrom google.genai import types\n\nfrom my_agent import agent\n\n\nasync def main():\n  runner = InMemoryRunner(app_name='my_app', agent=agent.root_agent)\n  session = await runner.session_service.create_session(\n      app_name='my_app', user_id='user1'\n  )\n  message = types.Content(\n      role='user',\n      parts=[types.Part.from_text(text=\"What's the weather in Paris?\")],\n  )\n  async for event in runner.run_async(\n      user_id='user1', session_id=session.id, new_message=message\n  ):\n    if event.content and event.content.parts:\n      text = event.content.parts[0].text\n      if text:\n        print(f'{event.author}: {text}')\n\n\nasyncio.run(main())\n```\n\n## 6. From one agent to a workflow\n\nA `Workflow` replaces \"one LLM decides everything\" with an explicit graph. The\nsmallest one has a single edge from `START`:\n\n```python\nfrom google.adk import Workflow\n\n\ndef greet(node_input: str) -> str:\n  return f'Hello! You said: {node_input}'\n\n\nroot_agent = Workflow(name='my_workflow', edges=[('START', greet)])\n```\n\n### Sequential pipeline of LLM agents\n\n> **Deprecated.** `SequentialAgent` is deprecated in favour of `Workflow`.\n> Prefer the explicit edge list above for new code; this form is documented\n> because existing agents still use it.\n\n`SequentialAgent` generates `START -> a -> b -> c` for you. Each agent's\n`output_key` publishes to session state, and the next agent reads it through an\ninstruction placeholder.\n\n```python\nfrom google.adk.agents import LlmAgent, SequentialAgent\n\nwriter = LlmAgent(\n    name='CodeWriterAgent',\n    model='gemini-3.5-flash',\n    instruction=(\n        'Write Python code that fulfills the user request. Output only the'\n        ' code block.'\n    ),\n    description='Writes initial Python code from a specification.',\n    output_key='generated_code',\n)\n\nreviewer = LlmAgent(\n    name='CodeReviewerAgent',\n    model='gemini-3.5-flash',\n    instruction=(\n        'Review this code and reply with a bulleted list of issues, or \"No'\n        ' major issues found.\" if it is clean:\\n\\n{generated_code}'\n    ),\n    description='Reviews code and provides feedback.',\n    output_key='review_comments',\n)\n\nrefactorer = LlmAgent(\n    name='CodeRefactorerAgent',\n    model='gemini-3.5-flash',\n    instruction=(\n        'Improve this code:\\n\\n{generated_code}\\n\\nAddressing these'\n        ' comments:\\n\\n{review_comments}\\n\\nOutput only the final code'\n        ' block.'\n    ),\n    description='Refactors code based on review comments.',\n    output_key='refactored_code',\n)\n\nroot_agent = SequentialAgent(\n    name='CodePipelineAgent',\n    sub_agents=[writer, reviewer, refactorer],\n    description='Writes, reviews, and refactors Python code.',\n)\n```\n\n### Graph with conditional routing\n\nA node returns `Event(route=...)` and the edge dict picks the branch.\n\n```python\nfrom google.adk import Event, Workflow\n\n\ndef parse_input(node_input: str) -> dict:\n  return {'text': node_input, 'word_count': len(node_input.split())}\n\n\ndef classify(node_input: dict):\n  route = 'long' if node_input['word_count'] > 10 else 'short'\n  return Event(output=node_input, route=route)\n\n\ndef handle_short(node_input: dict) -> str:\n  return f\"Short ({node_input['word_count']} words): {node_input['text']}\"\n\n\ndef handle_long(node_input: dict) -> str:\n  return f\"Long ({node_input['word_count']} words): {node_input['text'][:50]}...\"\n\n\nroot_agent = Workflow(\n    name='classifier_workflow',\n    input_schema=str,\n    edges=[\n        ('START', parse_input, classify),\n        (classify, {'short': handle_short, 'long': handle_long}),\n    ],\n)\n```\n\n### Parallel list processing\n\n`parallel_worker=True` makes a node run once per item of a list input and\nreturn a list of results.\n\n```python\nfrom google.adk import Workflow\nfrom google.adk.workflow import node\n\n\ndef split_input(node_input: str) -> list:\n  return [item.strip() for item in node_input.split(',')]\n\n\n@node(parallel_worker=True)\ndef process_item(node_input: str) -> dict:\n  return {'item': node_input, 'upper': node_input.upper()}\n\n\ndef format_results(node_input: list) -> str:\n  return '\\n'.join(f\"- {r['item']} -> {r['upper']}\" for r in node_input)\n\n\nroot_agent = Workflow(\n    name='parallel_processor',\n    input_schema=str,\n    edges=[('START', split_input, process_item, format_results)],\n)\n```\n\n### Mixing function nodes and an LLM agent\n\n```python\nfrom google.adk import Workflow\nfrom google.adk.agents import LlmAgent\n\n\ndef get_weather(city: str) -> dict:\n  \"\"\"Get the current weather for a city.\"\"\"\n  return {'city': city, 'temp': '72F', 'condition': 'sunny'}\n\n\ndef extract_city(node_input: str) -> str:\n  return node_input.strip()\n\n\nweather_agent = LlmAgent(\n    name='weather_reporter',\n    model='gemini-3.5-flash',\n    instruction='Use get_weather, then give a natural-language report.',\n    tools=[get_weather],\n)\n\n\ndef sign_off(node_input: str) -> str:\n  return f'{node_input}\\n\\nHave a great day!'\n\n\nroot_agent = Workflow(\n    name='weather_workflow',\n    input_schema=str,\n    edges=[('START', extract_city, weather_agent, sign_off)],\n)\n```\n\n## Troubleshooting\n\n| Symptom | Cause |\n|---|---|\n| `No module named 'google.adk'` | Virtual environment not activated, or `google-adk` not installed in it |\n| Agent missing from the `adk web` dropdown | `__init__.py` lacks `from . import agent`, or `agent.py` defines no `root_agent` |\n| API key errors | `.env` sits in the parent directory instead of the agent directory |\n| Model not found | Typo in the model id; non-Google models (Anthropic, LiteLLM) need extra dependencies |\n",".agents/skills/adk-agent-builder/references/human-in-the-loop.md":"# Human-in-the-Loop\n\nPause a workflow to ask the user something, then continue with their answer.\n\n```python\nfrom google.adk import Context, Event, Workflow\nfrom google.adk.apps import App, ResumabilityConfig\nfrom google.adk.events import RequestInput\nfrom google.adk.workflow import FunctionNode\n```\n\n## Asking\n\nYield or return a `RequestInput`. The node's output stream is normalized, so\neither works — a plain function can return one directly without becoming a\ngenerator.\n\n```python\nasync def approval_gate(ctx: Context, node_input: str):\n  yield RequestInput(message='Please approve this action:')\n\n\ndef evaluate_request(request: TimeOffRequest):\n  if request.days <= 1:\n    return TimeOffDecision(approved=True)  # no interrupt at all\n  return RequestInput(\n      interrupt_id='manager_approval',\n      message='Please review this time off request.',\n      payload=request,\n      response_schema=TimeOffDecision,\n  )\n```\n\nThe workflow emits an `adk_request_input` function call and stops. Responding to\nthat function call resumes it.\n\n| Field | Type | Notes |\n|---|---|---|\n| `interrupt_id` | `str` | Auto-generated UUID when omitted |\n| `message` | `str \\| None` | Shown to the user |\n| `payload` | `Any` | Arbitrary data carried along with the request |\n| `response_schema` | Pydantic class, Python type, or JSON-schema dict | Expected response shape |\n\n## Resuming: `rerun_on_resume`\n\nThe flag on the interrupted node decides what happens when the answer arrives.\n\n**`rerun_on_resume=False`** (the default for `FunctionNode`) — the node is *not*\nre-executed; the user's response becomes its output and flows downstream.\n\n```python\napproval_node = FunctionNode(func=ask_approval, rerun_on_resume=False)\n```\n\n**`rerun_on_resume=True`** (the default for an `LlmAgent` used as a node) — the\nnode runs again from the top, with answers available in `ctx.resume_inputs`,\nkeyed by `interrupt_id`.\n\n```python\nasync def interactive_node(ctx: Context, node_input: str):\n  if ctx.resume_inputs:\n    answer = list(ctx.resume_inputs.values())[0]\n    yield Event(output=f'User said: {answer}')\n  else:\n    yield RequestInput(message='What should I do?')\n```\n\n## Several questions from one node\n\nBecause a re-run node sees every answer so far, one node can walk a form:\n\n```python\nasync def multi_step_form(ctx: Context, node_input: str):\n  if not ctx.resume_inputs:\n    yield RequestInput(interrupt_id='ask_name', message='What is your name?')\n    return\n\n  if 'ask_email' not in ctx.resume_inputs:\n    yield RequestInput(interrupt_id='ask_email', message='What is your email?')\n    return\n\n  yield Event(output={\n      'name': ctx.resume_inputs['ask_name'],\n      'email': ctx.resume_inputs['ask_email'],\n  })\n```\n\n## Feeding the answer back into a loop\n\nA review-and-revise cycle turns the answer into a route. Vary the\n`interrupt_id` per iteration (see the best-practices reference for why) and read\n`ctx.resume_inputs` with the same id:\n\n```python\nasync def review(ctx: Context, node_input: Any):\n  review_count = ctx.state.get('review_count', 0)\n  interrupt_id = f'review_{review_count}'\n\n  response = ctx.resume_inputs.get(interrupt_id)\n  if response:\n    yield Event(\n        output=response,\n        route='approved' if response.get('approved') else 'rejected',\n        state={'review_count': review_count + 1},\n    )\n    return\n\n  yield RequestInput(\n      interrupt_id=interrupt_id,\n      message='Approve this plan?',\n      response_schema=ApprovalSchema,\n  )\n```\n\nWire the routes back with `(review, {'rejected': revise, 'approved': publish})`.\n\n## Resumable versus replayed\n\n**Replay (the default).** With no `App`, or with `is_resumable=False`, each\nresponse replays the workflow from `START`. Completed nodes are skipped and\nstate is rebuilt from event history, so only the interrupted node actually runs\nagain. Fine for a single interrupt; the replay cost grows with the graph.\n\n**Resumable.** Export an `App` with `is_resumable=True` and the workflow\ncheckpoints its progress into `event.actions.agent_state` and resumes at the\ninterrupted node instead of replaying.\n\n```python\nroot_agent = Workflow(name='my_workflow', edges=[...])\n\napp = App(\n    name='my_app',\n    root_agent=root_agent,\n    resumability_config=ResumabilityConfig(is_resumable=True),\n)\n```\n\nExport both `root_agent` and `app` from `agent.py` — the loader prefers `app`\nbut other tooling looks for `root_agent`. Use resumable mode for multi-step\ninterrupts, for `LongRunningFunctionTool`, and for any graph large enough that\nreplaying it is wasteful.\n\n## Human-in-the-loop from an LLM agent\n\nAn `LlmAgent` pauses through a `LongRunningFunctionTool` rather than\n`RequestInput`:\n\n```python\nfrom google.adk.tools import LongRunningFunctionTool\n\n\ndef approval_tool(request: str) -> str:\n  \"\"\"Request human approval for an action.\"\"\"\n  return f'Approved: {request}'\n\n\nllm_agent = LlmAgent(\n    name='agent_with_approval',\n    model='gemini-3.5-flash',\n    instruction='When you need approval, use approval_tool.',\n    tools=[LongRunningFunctionTool(func=approval_tool)],\n)\n```\n\nThe agent node already defaults to `rerun_on_resume=True`, so it picks the\nconversation back up on its own.\n\n## Answering from client code\n\n```python\nfrom google.genai import types\n\nfunction_call_id = interrupt_event.content.parts[0].function_call.id\n\nresponse = types.Content(\n    role='user',\n    parts=[types.Part(\n        function_response=types.FunctionResponse(\n            id=function_call_id,\n            name='adk_request_input',\n            response={'result': \"User's answer here\"},\n        )\n    )],\n)\n\nasync for event in runner.run_async(\n    user_id=user_id, session_id=session_id, new_message=response\n):\n  ...\n```\n\nA non-dict answer is carried under the `result` key as shown; a dict response\nmatching `response_schema` is passed through as-is.\n",".agents/skills/adk-agent-builder/references/import-paths.md":"# ADK Import Paths\n\nPrefer the canonical short import. The verbose module path is listed only where\nthere is no short form, or where the short form does not exist yet.\n\n## Canonical imports\n\nThese cover most agent code:\n\n```python\nfrom google.adk import Agent, Context, Event, Runner, Workflow\nfrom google.adk.events import Event, EventActions, RequestInput\nfrom google.adk.workflow import BaseNode, Edge, FunctionNode, JoinNode, node\nfrom google.adk.workflow import DEFAULT_ROUTE, RetryConfig, START\n```\n\n`google.adk` and `google.adk.workflow` load their members lazily, so importing\nfrom them does not pull in the whole package.\n\n## Agents\n\n| Component | Import |\n|---|---|\n| `Agent` (alias of `LlmAgent`) | `from google.adk import Agent` |\n| `LlmAgent` | `from google.adk.agents import LlmAgent` |\n| `BaseAgent` | `from google.adk.agents import BaseAgent` |\n| `SequentialAgent` (deprecated — use `Workflow`) | `from google.adk.agents import SequentialAgent` |\n| `ParallelAgent` (deprecated — use `Workflow`) | `from google.adk.agents import ParallelAgent` |\n| `LoopAgent` (deprecated — use `Workflow`) | `from google.adk.agents import LoopAgent` |\n| `RunConfig` | `from google.adk.agents import RunConfig` |\n\n## Workflow graph\n\n| Component | Import |\n|---|---|\n| `Workflow` | `from google.adk.workflow import Workflow` |\n| `Edge` | `from google.adk.workflow import Edge` |\n| `DEFAULT_ROUTE` (`\"__DEFAULT__\"`) | `from google.adk.workflow import DEFAULT_ROUTE` |\n| `START` | `from google.adk.workflow import START` |\n| `Graph` | `from google.adk.workflow._graph import Graph` |\n\n`Graph` is private; construct workflows from `edges=[...]` unless you need\n`Graph.from_edge_items(...)` directly.\n\n## Nodes\n\n| Component | Import |\n|---|---|\n| `BaseNode` | `from google.adk.workflow import BaseNode` |\n| `FunctionNode` | `from google.adk.workflow import FunctionNode` |\n| `Node` | `from google.adk.workflow import Node` |\n| `@node` decorator | `from google.adk.workflow import node` |\n| `JoinNode` | `from google.adk.workflow import JoinNode` |\n| `RetryConfig` | `from google.adk.workflow import RetryConfig` |\n| `NodeTimeoutError` | `from google.adk.workflow import NodeTimeoutError` |\n| `_ToolNode` (private) | `from google.adk.workflow._tool_node import _ToolNode` |\n\nParallel-worker behavior has no importable class. Set `parallel_worker=True` on\n`@node` or on an `LlmAgent`; the framework wraps it with an internal\n`_ParallelWorker`.\n\n## Events and context\n\n| Component | Import |\n|---|---|\n| `Event` | `from google.adk import Event` |\n| `EventActions` | `from google.adk.events import EventActions` |\n| `RequestInput` | `from google.adk.events import RequestInput` |\n| `Context` | `from google.adk import Context` |\n| `CallbackContext` (alias of `Context`) | `from google.adk.agents.callback_context import CallbackContext` |\n| `ReadonlyContext` | `from google.adk.agents.readonly_context import ReadonlyContext` |\n| `ToolContext` (alias of `Context`) | `from google.adk.tools import ToolContext` |\n\n## Task delegation\n\n| Component | Import |\n|---|---|\n| `FinishTaskTool` | `from google.adk.agents.llm.task._finish_task_tool import FinishTaskTool` |\n| `TaskRequest`, `TaskResult` | `from google.adk.agents.llm.task._task_models import TaskRequest, TaskResult` |\n\nAll three are private. Setting `mode='task'` attaches `FinishTaskTool`\nautomatically — there is no reason to import it in agent code.\n\n## Tools\n\n| Component | Import |\n|---|---|\n| `FunctionTool` | `from google.adk.tools import FunctionTool` |\n| `BaseTool` | `from google.adk.tools import BaseTool` |\n| `BaseToolset` | `from google.adk.tools.base_toolset import BaseToolset` |\n| `LongRunningFunctionTool` | `from google.adk.tools import LongRunningFunctionTool` |\n| `AgentTool` | `from google.adk.tools import AgentTool` |\n| `McpToolset` | `from google.adk.tools.mcp_tool import McpToolset` |\n| `StdioConnectionParams` | `from google.adk.tools.mcp_tool import StdioConnectionParams` |\n| `SseConnectionParams` | `from google.adk.tools.mcp_tool import SseConnectionParams` |\n| `StreamableHTTPConnectionParams` | `from google.adk.tools.mcp_tool import StreamableHTTPConnectionParams` |\n| `OpenAPIToolset`, `RestApiTool` | `from google.adk.tools.openapi_tool import OpenAPIToolset, RestApiTool` |\n\n`MCPToolset` (all caps) still resolves but raises a deprecation warning — use\n`McpToolset`.\n\n## Runner, sessions, app\n\n| Component | Import |\n|---|---|\n| `Runner` | `from google.adk import Runner` |\n| `InMemoryRunner` | `from google.adk.runners import InMemoryRunner` |\n| `InMemorySessionService` | `from google.adk.sessions import InMemorySessionService` |\n| `DatabaseSessionService` | `from google.adk.sessions import DatabaseSessionService` |\n| `VertexAiSessionService` | `from google.adk.sessions import VertexAiSessionService` |\n| `App`, `ResumabilityConfig` | `from google.adk.apps import App, ResumabilityConfig` |\n| `BasePlugin` | `from google.adk.plugins.base_plugin import BasePlugin` |\n\n`DatabaseSessionService` needs the `db` extra; importing it without\n`sqlalchemy` installed raises a \"missing extra\" error rather than\n`ImportError`.\n\n## Live streaming\n\n| Component | Import |\n|---|---|\n| `LiveRequestQueue` | `from google.adk.live import LiveRequestQueue` |\n| `LiveRequest` | `from google.adk.live import LiveRequest` |\n\n## Models\n\n| Component | Import |\n|---|---|\n| `BaseLlm` | `from google.adk.models.base_llm import BaseLlm` |\n| `LiteLlm` | `from google.adk.models.lite_llm import LiteLlm` |\n| `LlmRequest` | `from google.adk.models.llm_request import LlmRequest` |\n| `LlmResponse` | `from google.adk.models.llm_response import LlmResponse` |\n\n## Code executors\n\n| Component | Import |\n|---|---|\n| `BuiltInCodeExecutor` | `from google.adk.code_executors.built_in_code_executor import BuiltInCodeExecutor` |\n\n## google-genai types\n\n| Component | Import |\n|---|---|\n| `types` | `from google.genai import types` |\n| `Content`, `ModelContent`, `Part` | `from google.genai.types import Content, ModelContent, Part` |\n| `GenerateContentConfig` | `from google.genai.types import GenerateContentConfig` |\n",".agents/skills/adk-agent-builder/references/llm-agent-nodes.md":"# LLM Agents as Workflow Nodes\n\nPut an `LlmAgent` straight into `edges` and the framework runs it as a node,\nconverting the model's answer into the next node's `node_input`.\n\n```python\nfrom google.adk import Workflow\nfrom google.adk.agents import LlmAgent\n```\n\nThere is no wrapper class to import or subclass — the wrapping is internal.\n\n## Basic usage\n\n```python\nwriter = LlmAgent(\n    name='writer',\n    model='gemini-3.5-flash',\n    instruction=\"Write a short story based on the user's prompt.\",\n)\n\nreviewer = LlmAgent(\n    name='reviewer',\n    model='gemini-3.5-flash',\n    instruction='Review the following story and provide feedback.',\n)\n\nagent = Workflow(name='story_pipeline', edges=[('START', writer, reviewer)])\n```\n\n## What the next node receives\n\n| Agent config | `node_input` for the next node |\n|---|---|\n| no `output_schema` | `str` — the model's text parts, concatenated, thoughts excluded |\n| `output_schema=MyModel` | `dict` — the validated model, `model_dump(exclude_none=True)` |\n\n```python\nclass CodeOutput(BaseModel):\n  code: str\n  language: str\n\n\nwriter = LlmAgent(\n    name='writer',\n    model='gemini-3.5-flash',\n    instruction=\"Write code. Return JSON with 'code' and 'language'.\",\n    output_schema=CodeOutput,\n)\n\n\ndef process_code(node_input: dict) -> str:\n  return node_input['code']\n```\n\nSet `output_schema` whenever the downstream node needs fields rather than prose,\nand always when the agent feeds a `JoinNode` — the join parks partial results in\nsession state, and a raw `types.Content` there breaks a database-backed session\nservice.\n\n### Do not assert on `event.output` for an LLM agent's own event\n\nThe wrapper sets `event.output` internally, but the runner clears it on a copy\nbefore the event reaches your loop, so the same text is not rendered twice.\n`event.output` is therefore `None` when you read the agent's own event out of\n`runner.run_async(...)`. Assert on the downstream node's output, on\n`session.state[output_key]`, or on `event.content.parts[*].text` instead.\n\n## Auto-wrapping defaults\n\nAn `LlmAgent` placed in a workflow gets `mode='single_turn'` if `mode` is unset,\n`rerun_on_resume=True`, and its own content branch so parallel agents do not see\neach other's turns. Change the behavior on the agent, not on the wrapper:\n\n```python\n# single_turn (the default here): isolated, no session history\nclassifier = LlmAgent(\n    name='classifier',\n    model='gemini-3.5-flash',\n    instruction='Classify the input as positive, negative, or neutral.',\n    output_schema=ClassificationResult,\n)\n\n# task: multi-turn within the delegated task, supports human-in-the-loop\ntask_agent = LlmAgent(\n    name='task_agent',\n    model='gemini-3.5-flash',\n    mode='task',\n    instruction='Process the request.',\n)\n```\n\n`mode='chat'` is only legal directly after `START` — see the graph validation\nrules in the advanced-patterns reference.\n\n## Instruction as a function\n\nFor an instruction that depends on more than placeholder substitution, pass a\ncallable taking a `ReadonlyContext`:\n\n```python\nfrom google.adk.agents.readonly_context import ReadonlyContext\n\n\ndef build_instruction(ctx: ReadonlyContext) -> str:\n  agents = ctx.state.get('active_agents', [])\n  return f\"Coordinate these agents: {', '.join(agents)}\"\n\n\nagent = LlmAgent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    instruction=build_instruction,\n)\n```\n\n## Storing output in state\n\n`output_key` writes the agent's output into session state, where a later\ninstruction template or a state-bound function parameter can read it:\n\n```python\nagent = LlmAgent(\n    name='writer',\n    model='gemini-3.5-flash',\n    instruction='Write a draft.',\n    output_key='draft',  # lands in state['draft']\n)\n```\n\n## Controlling history\n\n`include_contents='none'` runs the agent without session history, which is what\nyou want for a classifier or extractor that should judge only the current input:\n\n```python\nagent = LlmAgent(\n    name='stateless',\n    model='gemini-3.5-flash',\n    instruction='Process this input independently.',\n    include_contents='none',\n)\n```\n\n## Tools\n\n```python\ndef search_database(query: str) -> str:\n  \"\"\"Search the database for relevant records.\"\"\"\n  return f'Results for: {query}'\n\n\nagent = LlmAgent(\n    name='assistant',\n    model='gemini-3.5-flash',\n    instruction='Help the user with their request.',\n    tools=[search_database],\n)\n```\n\n`tools` accepts plain callables (wrapped as `FunctionTool`), `BaseTool`\ninstances, and `BaseToolset` instances.\n\n## Generation config\n\nModel-level knobs go in `generate_content_config`. Instructions, tools, and\nresponse schema do **not** — set those as agent fields, or they are ignored.\n\n```python\nfrom google.genai import types\n\nagent = LlmAgent(\n    name='creative',\n    model='gemini-3.5-flash',\n    instruction='Write creative stories.',\n    generate_content_config=types.GenerateContentConfig(\n        temperature=0.9,\n        top_p=0.95,\n        max_output_tokens=2048,\n    ),\n)\n```\n\n## Transfer between agents\n\nAn `LlmAgent` with `sub_agents` can hand control to one of them by reasoning\nabout their `description` fields:\n\n```python\nspecialist = LlmAgent(\n    name='specialist',\n    model='gemini-3.5-flash',\n    description='Handles specialized requests.',\n    instruction='Answer specialized questions.',\n)\n\ncoordinator = LlmAgent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    instruction='Route requests to the specialist when needed.',\n    sub_agents=[specialist],\n)\n```\n\n`disallow_transfer_to_parent=True` and `disallow_transfer_to_peers=True` close\noff the return path and sideways moves respectively.\n",".agents/skills/adk-agent-builder/references/multi-agent.md":"# Multi-Agent Hierarchies\n\nComposing agents when the composition is a tree of agents rather than a graph of\nnodes.\n\n## Chat transfer\n\nGive a coordinator `sub_agents` and the model decides, from their `description`\nfields, when to hand over. Control passes to the sub-agent and comes back the\nsame way.\n\n```python\nfrom google.adk import Agent\n\nresearcher = Agent(\n    name='researcher',\n    description='Researches topics and reports findings.',\n    instruction='You research topics and provide findings.',\n    tools=[search_tool],\n)\n\nwriter = Agent(\n    name='writer',\n    description='Writes prose from research findings.',\n    instruction='You write content based on research.',\n)\n\nroot_agent = Agent(\n    model='gemini-3.5-flash',\n    name='coordinator',\n    instruction='Delegate research to the researcher and writing to the writer.',\n    sub_agents=[researcher, writer],\n)\n```\n\n- Only the root needs `model=`; a sub-agent without one resolves to the nearest\n  `LlmAgent` ancestor's model.\n- The `description` is the only thing the routing model sees, so make each one\n  say what the agent is *for* and how it differs from its peers. Ambiguous\n  descriptions are the usual cause of the wrong agent picking up a request.\n- `disallow_transfer_to_parent=True` blocks the way back;\n  `disallow_transfer_to_peers=True` blocks sideways moves. Both default to\n  `False`, so a sub-agent can normally return control on its own.\n\nFor schema-validated delegation rather than free-form transfer, set\n`mode='task'` or `mode='single_turn'` on the sub-agent — that is a different\nmechanism with its own tool and completion protocol.\n\n## Orchestration agents\n\n> **Deprecated.** `SequentialAgent`, `ParallelAgent`, and `LoopAgent` are all\n> deprecated in favour of `Workflow` and will be removed in a future version.\n> Build new orchestration as a `Workflow` graph instead — the getting-started\n> reference shows the equivalent edge lists. The one thing they still do that\n> `Workflow` cannot: a `Workflow` cannot yet be used as an `LlmAgent`\n> sub-agent, so reach for these only when you need model-driven transfer into\n> an orchestrated block.\n\nThese three run their `sub_agents` without asking a model what to do next.\n\n```python\nfrom google.adk.agents import LoopAgent, ParallelAgent, SequentialAgent\n\n# One after another\nroot_agent = SequentialAgent(\n    name='pipeline',\n    sub_agents=[step1_agent, step2_agent, step3_agent],\n)\n\n# All at once\nroot_agent = ParallelAgent(\n    name='fan_out',\n    sub_agents=[task_a, task_b, task_c],\n)\n```\n\n`LoopAgent` repeats its sub-agents until one calls `exit_loop` or escalates.\n`max_iterations` is optional; without it the only way out is `exit_loop`, so set\none unless a sub-agent reliably calls the tool.\n\n```python\nfrom google.adk.agents import LoopAgent\nfrom google.adk.tools import exit_loop\n\nchecker = Agent(\n    name='checker',\n    tools=[exit_loop],\n    instruction='Check the result and call exit_loop when it is good enough.',\n)\n\nroot_agent = LoopAgent(\n    name='retry_loop',\n    sub_agents=[worker_agent, checker],\n    max_iterations=5,\n)\n```\n\n## Models\n\nThe built-in default when no agent in the chain sets `model=` is\n`LlmAgent.DEFAULT_MODEL`, currently `'gemini-3.5-flash'`. Override the default\nprocess-wide with `LlmAgent.set_default_model('gemini-3.1-pro-preview')`.\n\nNon-Gemini models go through LiteLLM, with the provider as a prefix:\n\n```python\nfrom google.adk.models.lite_llm import LiteLlm\n\nroot_agent = Agent(model=LiteLlm(model='openai/gpt-4o'), ...)\n```\n\n## Common failures\n\n| Symptom | Cause |\n|---|---|\n| A sub-agent takes over and never gives control back | It has no path home; check `disallow_transfer_to_parent` and say in its instruction when to return |\n| The wrong agent answers | Two `description` fields overlap; sharpen the boundary between them |\n| `ImportError` on agent definitions | Circular imports between per-agent modules; define the tree in one `agent.py` or put shared sub-agents in their own module |\n",".agents/skills/adk-agent-builder/references/parallel-and-fanout.md":"# Parallel Execution, Fan-Out, and Fan-In\n\nTwo different things share the word \"parallel\": **fan-out** runs several\n*different* nodes on the same input, and a **parallel worker** runs one node\nacross every item of a list. Mixing them up is the most common failure here.\n\n```python\nfrom google.adk import Agent, Workflow\nfrom google.adk.workflow import JoinNode, node\n```\n\n## Fan-out: several nodes, same input\n\nA tuple of targets sends the same output to each of them concurrently:\n\n```python\nagent = Workflow(\n    name='fan_out',\n    edges=[('START', (analyze_text, translate_text, summarize_text))],\n)\n```\n\n## Fan-in: `JoinNode`\n\nA `JoinNode` waits for every predecessor, then fires once with all their outputs\nin a dict keyed by predecessor node name:\n\n```python\njoin = JoinNode(name='collect_results')\n\nagent = Workflow(\n    name='fan_out_fan_in',\n    edges=[\n        ('START', (analyze_text, translate_text, summarize_text)),\n        ((analyze_text, translate_text, summarize_text), join),\n        (join, final_processor),\n    ],\n)\n\n\ndef final_processor(node_input: dict) -> str:\n  # {'analyze_text': ..., 'translate_text': ..., 'summarize_text': ...}\n  return f\"Combined: {node_input['analyze_text']}\"\n```\n\nWhile waiting, the join holds partial inputs in session state. If a predecessor\nis an `LlmAgent` without `output_schema`, what it holds is a `types.Content`,\nwhich a database-backed session service cannot serialize — set `output_schema`\non every LLM agent feeding a join.\n\n## Multi-trigger: fan-out into one shared successor\n\nPoint several branches at one node with no join and that node runs once per\nbranch:\n\n```python\nagent = Workflow(\n    name='root_agent',\n    input_schema=str,\n    edges=[(\n        'START',\n        (make_uppercase, count_characters, reverse_string),\n        send_message,\n    )],\n)\n```\n\n`send_message` fires three times here. A `JoinNode` in the same position would\nfire once with a merged dict. Pick by whether the downstream work is per-branch\nor needs all branches at once.\n\n## Parallel workers: one node, every item of a list\n\n`parallel_worker=True` wraps a node so it receives a list, runs an instance per\nitem concurrently, and emits a list of results in input order.\n\n```python\n@node(parallel_worker=True)\ndef process_item(node_input: int) -> int:\n  return node_input * 2\n\n\ndef produce_list(node_input: str) -> list:\n  return [1, 2, 3, 4, 5]\n\n\nagent = Workflow(\n    name='parallel_processing',\n    edges=[('START', produce_list, process_item)],\n)\n# process_item emits [2, 4, 6, 8, 10]\n```\n\nThere is no `ParallelWorker` class to import; the wrapper is internal.\n\nBehavior worth knowing:\n\n- A non-list input is wrapped in a one-element list rather than rejected.\n- An empty list produces an empty list, and downstream still fires.\n- `rerun_on_resume` is forced to `True`.\n- `max_parallel_workers=N` caps concurrency; unset means unbounded. It must be\n  at least 1.\n\n### On an agent\n\nSet the flag on the `LlmAgent` itself — each item is handled by a clone:\n\n```python\nexplain_topic = Agent(\n    name='explain_topic',\n    instruction='Explain how this topic relates to \"{topic}\".',\n    output_schema=TopicExplanation,\n    parallel_worker=True,\n)\n\nagent = Workflow(\n    name='parallel_analysis',\n    edges=[('START', process_input, find_related_topics, explain_topic, aggregate)],\n)\n```\n\n### Do not put `parallel_worker=True` on a fan-out branch\n\nFan-out edges already run their targets concurrently. Adding the flag makes the\nbranch expect a list and iterate it; handed a single value it iterates once, and\nhanded `None` it emits nothing — so a downstream join waits forever.\n\n## Diamond\n\n```python\njoin = JoinNode(name='merge')\n\n\ndef combiner(node_input: dict) -> str:\n  return f\"{node_input['branch_a']} + {node_input['branch_b']}\"\n\n\nagent = Workflow(\n    name='diamond',\n    edges=[\n        ('START', splitter),\n        (splitter, (branch_a, branch_b)),\n        ((branch_a, branch_b), join),\n        (join, combiner),\n    ],\n)\n```\n\n## `SequentialAgent` and `ParallelAgent`\n\n> **Deprecated.** Both are deprecated in favour of `Workflow` and will be\n> removed in a future version. Use the edge forms above for new code.\n\nShorthands for the two commonest graphs, when you have agents rather than\nfunctions:\n\n```python\nfrom google.adk.agents import ParallelAgent, SequentialAgent\n\n# START -> writer -> reviewer -> editor\npipeline = SequentialAgent(\n    name='pipeline',\n    sub_agents=[writer_agent, reviewer_agent, editor_agent],\n)\n\n# START -> (analyzer, translator, summarizer)\nparallel = ParallelAgent(\n    name='concurrent',\n    sub_agents=[analyzer_agent, translator_agent, summarizer_agent],\n)\n```\n",".agents/skills/adk-agent-builder/references/routing-and-conditions.md":"# Routing and Conditional Branching\n\nA node emits `Event(route=...)`; the edges leaving that node decide which\nsuccessors fire.\n\n## Dict routing — the default form\n\nMap route values to targets in one edge tuple:\n\n```python\nfrom google.adk import Event, Workflow\n\n\ndef classify(node_input: str):\n  route = 'error' if 'error' in node_input else 'success'\n  return Event(output=node_input, route=route)\n\n\nagent = Workflow(\n    name='router',\n    edges=[\n        ('START', classify),\n        (classify, {'success': handle_success, 'error': handle_error}),\n    ],\n)\n```\n\nThe three-tuple form `(classify, handle_success, 'success')` does the same thing\none target at a time. Reach for it only when a single edge must match several\nroutes (see below), since the dict form maps one route to one target.\n\n## Sequence shorthand\n\nA tuple of more than two elements becomes a chain:\n\n```python\nedges = [('START', step_a, step_b, step_c)]\n# equivalent to [('START', step_a), (step_a, step_b), (step_b, step_c)]\n```\n\nChains and dict routing compose:\n\n```python\nedges = [\n    ('START', process_input, classify),\n    (classify, {'approved': send, 'rejected': discard}),\n]\n```\n\n## Route values\n\nA route is a `str`, `bool`, or `int`, or a list of those.\n\n```python\n(decision, {'approve': path_a, 'reject': path_b})   # strings\n(decision, {True: yes_path, False: no_path})        # booleans\n(decision, {0: path_0, 1: path_1})                  # integers\n```\n\n## Default route\n\n`'__DEFAULT__'` (exported as `DEFAULT_ROUTE`) fires when no other route on that\nnode matches:\n\n```python\nedges = [\n    ('START', classify),\n    (classify, {\n        'success': handler_a,\n        'error': handler_b,\n        '__DEFAULT__': fallback_handler,\n    }),\n]\n```\n\nThe default fans out like any other route, so `'__DEFAULT__': (a, b)` triggers\nboth. `'__DEFAULT__'` may not appear inside a list of routes on one edge — give\nit its own edge.\n\n## One edge, several routes\n\nPassing a list matches any value in it:\n\n```python\nedges = [\n    ('START', classifier),\n    (classifier, {'route_z': handler_b}),\n    (classifier, handler_a, ['route_x', 'route_y']),\n]\n```\n\n## Several routes from one node\n\nA node can emit a list of routes to fire multiple branches at once:\n\n```python\ndef fan_out_router(node_input: str):\n  return Event(output=node_input, route=['path_a', 'path_b'])\n\n\nagent = Workflow(\n    name='multi_route',\n    edges=[\n        ('START', fan_out_router),\n        (fan_out_router, {'path_a': branch_a, 'path_b': branch_b}),\n    ],\n)\n```\n\n## Self-loop\n\n```python\ndef guess_number(target_number: int):\n  guess = random.randint(0, 10)\n  yield Event(message=f'Guessing {guess}...')\n  if guess == target_number:\n    yield Event(message='Correct!')\n  else:\n    yield Event(route='guessed_wrong')\n\n\nagent = Workflow(\n    name='root_agent',\n    edges=[\n        ('START', validate_input, guess_number),\n        (guess_number, {'guessed_wrong': guess_number}),\n    ],\n)\n```\n\n## Revision loop\n\n```python\nedges = [\n    ('START', process_input, draft_email, human_review),\n    (human_review, {\n        'revise': draft_email,\n        'approved': send,\n        'rejected': discard,\n    }),\n]\n```\n\n## Constraints the graph validator enforces\n\n- **A cycle needs at least one routed edge.** An entirely unconditional cycle is\n  rejected at construction time, because nothing could ever break out of it.\n- **Edges leaving `START` may not carry a route.** `START` never runs, so it\n  never emits one.\n- **No two edges may share a source and a target**, even with different routes.\n  To reach one destination from both a named route and `__DEFAULT__`, point the\n  default at a thin wrapper function.\n\n## Unrouted edges always fire\n\nAn edge with no route fires on every output event from its source, whatever\nroute that event carries. So if a node routes at all, give *every* one of its\noutgoing edges a route — otherwise the unrouted one fires alongside the branch\nyou selected.\n\n```python\nedges = [\n    ('START', node_a),  # unconditional\n    (node_a, node_b),   # fires on every node_a output\n]\n```\n",".agents/skills/adk-agent-builder/references/session-and-state.md":"# Sessions, Artifacts, and Memory\n\nWhere data lives beyond a single node: the session store, binary artifacts, and\nlong-term memory.\n\n## State key scopes\n\nA prefix on the key decides how far the value travels and how long it lives.\n\n| Key form | Scope |\n|---|---|\n| `key` | This session |\n| `app:key` | The whole app — shared across every session and user |\n| `user:key` | This user, across their sessions |\n| `temp:key` | This invocation only; never persisted |\n\nA `state_schema` on a node validates writes, but prefixed keys bypass that\nvalidation.\n\nMutate, do not rebind — `state['key'] = value` records a delta, whereas\n`state = {'key': value}` just rebinds a local name and is lost.\n\n```python\ndef my_tool(tool_context: ToolContext):\n  tool_context.state['user_name'] = 'Alice'\n  tool_context.state['app:feature_flag'] = True\n```\n\nIn parallel branches, two nodes writing the same key race. Give each branch its\nown key, or write to a shared `app:`-scoped key deliberately.\n\n## Session services\n\n| Service | Use for | Import |\n|---|---|---|\n| `InMemorySessionService` | local development and tests | `from google.adk.sessions import InMemorySessionService` |\n| `DatabaseSessionService` | production on SQLite or PostgreSQL | `from google.adk.sessions import DatabaseSessionService` |\n| `VertexAiSessionService` | Vertex AI Agent Engine | `from google.adk.sessions import VertexAiSessionService` |\n\n```python\nfrom google.adk import Runner\nfrom google.adk.sessions import InMemorySessionService\n\nrunner = Runner(\n    agent=root_agent,\n    app_name='my_app',\n    session_service=InMemorySessionService(),\n)\n```\n\n`DatabaseSessionService` needs the `db` extra (`pip install \"google-adk[db]\"`).\nIt also serializes everything you put in state and in a `JoinNode`'s parked\ninputs, so a non-JSON-serializable value that works in memory fails here.\n\n## Artifacts\n\nArtifacts hold bytes — images, files, generated documents — keyed by filename\nand versioned per session.\n\n```python\nfrom google.genai import types\n\n\nasync def save_chart(tool_context: ToolContext):\n  part = types.Part.from_bytes(data=generate_chart(), mime_type='image/png')\n  version = await tool_context.save_artifact('chart.png', part)\n\n\nasync def get_chart(tool_context: ToolContext):\n  part = await tool_context.load_artifact('chart.png')\n  return part.inline_data.data\n```\n\n## Memory\n\nMemory is recall across sessions, as opposed to state, which is recall within\none.\n\n```python\nfrom google.adk.memory.in_memory_memory_service import InMemoryMemoryService\n\nrunner = Runner(\n    agent=root_agent,\n    app_name='my_app',\n    session_service=InMemorySessionService(),\n    memory_service=InMemoryMemoryService(),\n)\n```\n\nAgents reach it through the `load_memory` and `preload_memory` tools, or\ndirectly with `await ctx.search_memory(query)`.\n",".agents/skills/adk-agent-builder/references/state-and-events.md":"# Context and Events\n\nThe two objects a node touches: `Context`, which is how it reads its\nsurroundings, and `Event`, which is how it says anything.\n\n```python\nfrom google.adk import Context, Event\n```\n\n## Getting a `Context`\n\nDeclare a parameter named `ctx`. Nodes that do not need it can omit it.\n\n```python\ndef my_node(ctx: Context, node_input: str) -> str:\n  value = ctx.state.get('key', 'default')\n  return f'{ctx.session.id}: {value}'\n```\n\n## Context properties\n\nAvailable everywhere (a `Context` is also what a callback and a tool receive —\n`CallbackContext` and `ToolContext` are aliases for this same class):\n\n| Property | Type | Notes |\n|---|---|---|\n| `state` | `State` | Delta-aware session state; reads and writes like a dict |\n| `session` | `Session` | Current session, with the workflow's local events merged in |\n| `invocation_id` | `str` | This invocation |\n| `user_id` | `str` | Read-only |\n| `user_content` | `types.Content \\| None` | The message that started the invocation |\n| `agent_name` | `str` | Agent currently running |\n| `run_config` | `RunConfig \\| None` | Read-only |\n| `actions` | `EventActions` | State and artifact deltas being accumulated |\n| `branch` | `str \\| None` | Event-isolation branch |\n| `function_call_id` | `str \\| None` | Set when running as a tool |\n\nMeaningful only inside a workflow node:\n\n| Property | Type | Notes |\n|---|---|---|\n| `node` | `BaseNode \\| None` | The node being executed |\n| `node_path` | `str` | Full path, e.g. `'WorkflowA/node1'` |\n| `run_id` | `str` | This node-run, e.g. `'1'`, `'2'` |\n| `attempt_count` | `int` | 1 on the first try, higher on a retry |\n| `resume_inputs` | `dict[str, Any]` | Human-in-the-loop answers, keyed by `interrupt_id` |\n| `error`, `error_node_path` | `Exception \\| None`, `str` | Set after a node fails |\n\n## Context methods\n\n| Method | Purpose |\n|---|---|\n| `await run_node(node, node_input=None, *, use_as_output=False, run_id=None, use_sub_branch=False, override_branch=None)` | Run a node dynamically; the caller needs `rerun_on_resume=True` |\n| `await save_artifact(filename, part)` / `await load_artifact(filename)` | Session artifacts |\n| `await search_memory(query)` | Long-term memory lookup |\n| `get_auth_response(auth_config)` / `request_credential(auth_config)` | Credentials |\n| `get_invocation_context()` | Escape hatch to the underlying `InvocationContext` |\n\n## Parameters resolved from state\n\nAny function-node parameter that is not `ctx` or `node_input` is looked up in\n`ctx.state` by name, and coerced to its annotation. If the key is absent, the\nparameter's default is used.\n\n```python\n# With state {'user_name': 'Alice', 'threshold': 0.5}\ndef my_node(node_input: str, user_name: str, threshold: float) -> str:\n  return f'{user_name}: {node_input} (threshold={threshold})'\n```\n\nResolution order: `ctx` → `node_input` → `ctx.state[name]` → default.\n\n## Event fields\n\n`Event` extends `LlmResponse`. Three of its constructor arguments are\nconveniences that write somewhere else:\n\n| Constructor argument | Where it lands |\n|---|---|\n| `output=` | `event.output` — data for the next node |\n| `message=` | `event.content` — what the UI renders |\n| `state=` | `event.actions.state_delta` |\n| `route=` | `event.actions.route` |\n\n`message` and `content` are mutually exclusive; passing both raises. `message`\naccepts a string, a `types.Part`, a list of parts, or a `types.Content`.\n\nOther fields you will read: `author`, `content`, `partial`, `branch`,\n`node_info` (with `node_info.path` identifying the emitting node), and\n`is_final_response()`.\n\n## Emitting user-visible messages\n\n```python\nyield Event(message='Processing step 1...')\n\n# Multimodal\nfrom google.genai import types\n\nyield Event(message=[\n    types.Part.from_text(text='Here is the result:'),\n    types.Part.from_bytes(data=image_bytes, mime_type='image/png'),\n])\n\n# Streaming chunks, then the real output\nasync def verbose_node(ctx: Context, node_input: str):\n  yield Event(message='Processing step 1...', partial=True)\n  yield Event(message='Processing step 2...', partial=True)\n  yield Event(output='final result')\n```\n\n## What the workflow itself outputs\n\nAfter every node settles, the workflow emits one more event of its own. Its\nvalue comes from the terminal nodes — those with no outgoing edges. That event\nis authored by the workflow, with `node_info.path` set to the workflow's own\npath, which is why filtering test assertions on `event.author` picks up the\nwrapper rather than the node you meant.\n",".agents/skills/adk-agent-builder/references/task-mode.md":"# Task Delegation (`mode='task'` / `mode='single_turn'`)\n\nHand a sub-agent a schema-validated job and get a schema-validated answer back,\ninstead of transferring the whole conversation to it.\n\n## The three modes\n\n`LlmAgent.mode` is `'chat'`, `'task'`, `'single_turn'`, or unset. Unset means\n`'chat'` when the agent is a sub-agent, `'single_turn'` when it is a workflow\nnode.\n\n| Mode | How the parent reaches it | User interaction | How it finishes |\n|---|---|---|---|\n| `chat` | the `transfer_to_agent` tool | full conversation | transfers back |\n| `task` | a tool named after the sub-agent | can ask the user for clarification | calls `finish_task` |\n| `single_turn` | a tool named after the sub-agent | none — told no reply is coming | calls `finish_task` |\n\nThe delegation tool takes the sub-agent's **`name`** verbatim. An agent called\n`researcher` is exposed to the coordinator as a tool called `researcher`, and\nits `description` becomes the tool description, so write the description for a\nmodel deciding whether to call it.\n\n## Task mode\n\n```python\nfrom google.adk import Agent\nfrom pydantic import BaseModel\n\n\nclass ResearchInput(BaseModel):\n  topic: str\n  depth: str = 'standard'\n\n\nclass ResearchOutput(BaseModel):\n  summary: str\n  key_findings: str\n  confidence: str\n\n\ndef search_web(query: str) -> str:\n  \"\"\"Search the web for information.\"\"\"\n  return f'Results for \"{query}\": ...'\n\n\nresearcher = Agent(\n    name='researcher',\n    mode='task',\n    input_schema=ResearchInput,\n    output_schema=ResearchOutput,\n    description='Researches topics using web search and analysis.',\n    instruction=(\n        'Research the given topic with search_web. If the user asks for'\n        ' changes, adjust. When done, call finish_task with summary,'\n        ' key_findings, and confidence.'\n    ),\n    tools=[search_web],\n)\n\nroot_agent = Agent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    sub_agents=[researcher],\n    instruction=(\n        'When the user asks for research, call the researcher tool. Summarize'\n        ' its result for the user.'\n    ),\n)\n```\n\nSequence: the coordinator calls the `researcher` tool with structured input; the\nresearcher works, possibly talking to the user; the researcher calls\n`finish_task` with structured output; the coordinator gets the result.\n\n## Single-turn mode\n\nSame shape, no conversation. The framework appends a nudge to the sub-agent's\ninput telling it no further user replies will arrive, so it must finish from the\ninput alone.\n\n```python\nclass SummaryOutput(BaseModel):\n  summary: str\n  word_count: int\n\n\nsummarizer = Agent(\n    name='summarizer',\n    mode='single_turn',\n    output_schema=SummaryOutput,\n    description='Summarizes documents autonomously.',\n    instruction='Summarize the document with extract_text, then finish_task.',\n    tools=[extract_text],\n)\n\nroot_agent = Agent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    sub_agents=[summarizer],\n    instruction='Delegate summarization to the summarizer tool.',\n)\n```\n\n## Schemas\n\n`input_schema` types the delegation tool's parameters; `output_schema` types\n`finish_task`'s parameters. Both are optional.\n\n```python\nagent = Agent(\n    name='worker',\n    mode='task',\n    input_schema=TaskInput,    # validates the delegation call\n    output_schema=TaskOutput,  # validates the finish_task call\n    ...\n)\n```\n\nWithout them the defaults are a single string each:\n\n```python\n# delegation tool parameters\n{'request': str}   # \"Detailed instructions or context for the task sub-agent.\"\n\n# finish_task parameters\n{'result': str}\n```\n\nA schema violation is not fatal — `finish_task` returns a validation-error\nmessage and the model gets to retry.\n\n## `finish_task`\n\n`mode='task'` attaches a tool called `finish_task` to the sub-agent\nautomatically, and injects an instruction telling the model to complete the work\nbefore calling it. Its parameters come from `output_schema`, or `{'result':\nstr}` when there is none. There is nothing to import or register.\n\n## Mixed modes under one coordinator\n\n```python\nflight_searcher = Agent(\n    name='flight_searcher',\n    mode='task',                # interactive: can discuss options\n    input_schema=FlightSearchInput,\n    output_schema=FlightSearchOutput,\n    description='Searches and books flights interactively.',\n    instruction='Search flights, discuss with the user, then finish_task.',\n    tools=[search_flights, book_flight],\n)\n\nweather_checker = Agent(\n    name='weather_checker',\n    mode='single_turn',         # autonomous\n    output_schema=WeatherOutput,\n    description='Checks weather for a destination.',\n    instruction='Check the weather and call finish_task.',\n    tools=[get_weather],\n)\n\nroot_agent = Agent(\n    name='travel_planner',\n    model='gemini-3.5-flash',\n    sub_agents=[flight_searcher, weather_checker],\n    instruction=(\n        'Plan trips. Use weather_checker for weather and flight_searcher for'\n        ' booking.'\n    ),\n)\n```\n\n## Rules worth knowing\n\n- Only the coordinator needs `model=`; sub-agents inherit it from the nearest\n  `LlmAgent` ancestor.\n- Every delegating sub-agent needs a `description` — it is the entire tool\n  description the coordinator's model sees.\n- The delegation tool is marked as deferring its response and its description\n  tells the model **not** to call it in parallel with other tools. Do not build\n  a prompt that asks for several delegations in one turn.\n- A `mode='chat'` sub-agent gets neither a delegation tool nor `finish_task`; it\n  stays a `transfer_to_agent` target.\n\n## Task mode versus chat transfer\n\n| | chat (`transfer_to_agent`) | task / single_turn |\n|---|---|---|\n| input | free-form conversation | schema-validated |\n| output | free-form conversation | schema-validated |\n| control returns when | the agent transfers back | the agent calls `finish_task` |\n| user interaction | full chat | `task`: multi-turn, `single_turn`: none |\n\n## Where the code lives\n\n| Component | File |\n|---|---|\n| `mode`, `input_schema`, `output_schema` | `src/google/adk/agents/llm_agent.py` |\n| delegation tools, default input schema | `src/google/adk/tools/agent_tool.py` |\n| `finish_task` | `src/google/adk/agents/llm/task/_finish_task_tool.py` |\n| `TaskRequest`, `TaskResult` | `src/google/adk/agents/llm/task/_task_models.py` |\n",".agents/skills/adk-agent-builder/references/testing.md":"# Testing Workflow Agents\n\n`pytest` plus `InMemoryRunner`. Everything below uses the published\n`google-adk` package — no test-internal helpers.\n\n## Setup\n\n```bash\nuv add \"google-adk>=2.0\" pytest pytest-asyncio\n```\n\n```toml\n[tool.pytest.ini_options]\nasyncio_mode = \"auto\"\n```\n\n`asyncio_mode = \"auto\"` saves marking every test `@pytest.mark.asyncio`. Omit it\nif you prefer explicit marks.\n\n## Imports\n\n```python\nimport pytest\nfrom google.adk import Workflow\nfrom google.adk.agents import LlmAgent\nfrom google.adk.apps import App, ResumabilityConfig\nfrom google.adk.events import Event, RequestInput\nfrom google.adk.runners import InMemoryRunner\nfrom google.genai import types\n```\n\n## Two helpers worth having\n\n```python\nasync def run(agent, text='hi', app_name='test_app'):\n    runner = InMemoryRunner(agent=agent, app_name=app_name)\n    session = await runner.session_service.create_session(\n        app_name=app_name, user_id='u1'\n    )\n    msg = types.Content(role='user', parts=[types.Part(text=text)])\n    events = []\n    async for event in runner.run_async(\n        user_id='u1', session_id=session.id, new_message=msg\n    ):\n        events.append(event)\n    return runner, session, events\n\n\ndef node_name(event):\n    \"\"\"'workflow@1/step@1' -> 'step'.\"\"\"\n    if not event.node_info:\n        return None\n    return event.node_info.path.split('/')[-1].split('@')[0]\n```\n\n`event.author` is the *enclosing workflow's* name, not the node's, so filtering\non it silently matches the wrong events. `event.node_info.path` is the one that\nidentifies the node.\n\n## A workflow\n\n```python\nasync def test_simple_workflow():\n    def step_one(node_input: str) -> str:\n        return 'step 1 done'\n\n    def step_two(node_input: str) -> str:\n        return 'step 2 done'\n\n    agent = Workflow(\n        name='test_workflow', edges=[('START', step_one, step_two)]\n    )\n\n    _, _, events = await run(agent)\n    final = [e for e in events if node_name(e) == 'step_two' and e.output][-1]\n    assert final.output == 'step 2 done'\n```\n\n## Routing\n\n```python\nasync def test_routing():\n    def router(node_input: str):\n        route = 'error' if 'error' in node_input else 'success'\n        return Event(output=node_input, route=route)\n\n    agent = Workflow(\n        name='routing_test',\n        edges=[\n            ('START', router),\n            (router, {'success': success_handler, 'error': error_handler}),\n        ],\n    )\n\n    _, _, ok = await run(agent, text='all good')\n    assert any(node_name(e) == 'success_handler' for e in ok)\n\n    _, _, err = await run(agent, text='error case')\n    assert any(node_name(e) == 'error_handler' for e in err)\n```\n\n## Pause and resume\n\n```python\nasync def test_hitl_workflow():\n    async def ask_user(ctx, node_input: str):\n        yield RequestInput(message='Approve?', interrupt_id='ask')\n\n    def after_approval(node_input) -> str:\n        return f'Approved: {node_input}'\n\n    agent = Workflow(\n        name='hitl_test', edges=[('START', ask_user, after_approval)]\n    )\n    app = App(\n        name='hitl_test_app',\n        root_agent=agent,\n        resumability_config=ResumabilityConfig(is_resumable=True),\n    )\n    runner = InMemoryRunner(app=app)\n    session = await runner.session_service.create_session(\n        app_name='hitl_test_app', user_id='u1'\n    )\n\n    msg = types.Content(role='user', parts=[types.Part(text='start')])\n    paused = [\n        e\n        async for e in runner.run_async(\n            user_id='u1', session_id=session.id, new_message=msg\n        )\n    ]\n    fc_events = [e for e in paused if e.get_function_calls()]\n    assert fc_events, 'expected an interrupt function call'\n    fc = fc_events[-1].get_function_calls()[0]\n\n    response = types.Content(\n        role='user',\n        parts=[types.Part(function_response=types.FunctionResponse(\n            id=fc.id, name=fc.name, response={'result': 'yes'},\n        ))],\n    )\n    resumed = [\n        e\n        async for e in runner.run_async(\n            user_id='u1', session_id=session.id, new_message=response\n        )\n    ]\n    final = [e for e in resumed if node_name(e) == 'after_approval'][-1]\n    assert final.output == 'Approved: yes'\n```\n\n## State\n\nPrefer reading the session back after the run over inspecting state mid-flight.\n\n```python\nasync def test_state_management():\n    def writer(node_input: str):\n        return Event(output=node_input, state={'counter': 1})\n\n    def reader(ctx, node_input):\n        return f\"counter={ctx.state['counter']}\"\n\n    agent = Workflow(name='state_test', edges=[('START', writer, reader)])\n\n    runner, session, events = await run(agent)\n    final = [e for e in events if node_name(e) == 'reader' and e.output][-1]\n    assert final.output == 'counter=1'\n\n    after = await runner.session_service.get_session(\n        app_name='test_app', user_id='u1', session_id=session.id\n    )\n    assert after.state['counter'] == 1\n```\n\n## Parallel workers\n\n```python\nfrom google.adk.workflow import node\n\n\nasync def test_parallel_worker():\n    def produce(node_input: str) -> list:\n        return [1, 2, 3]\n\n    @node(parallel_worker=True)\n    def double(node_input: int) -> int:\n        return node_input * 2\n\n    def collect(node_input: list) -> str:\n        return f'results: {node_input}'\n\n    agent = Workflow(\n        name='parallel_test', edges=[('START', produce, double, collect)]\n    )\n\n    _, _, events = await run(agent)\n    final = [e for e in events if node_name(e) == 'collect' and e.output][-1]\n    assert final.output == 'results: [2, 4, 6]'\n```\n\n## Faking the model\n\n`BaseLlm` has exactly one abstract method, so a fake is short:\n\n```python\nfrom google.adk.models.base_llm import BaseLlm\nfrom google.adk.models.llm_response import LlmResponse\n\n\nclass FakeLlm(BaseLlm):\n\n    def __init__(self, *, responses: list[str]):\n        super().__init__(model='fake')\n        self._responses = list(responses)\n\n    async def generate_content_async(self, llm_request, stream=False):\n        yield LlmResponse(content=types.Content(\n            role='model', parts=[types.Part(text=self._responses.pop(0))],\n        ))\n\n\nasync def test_llm_agent_with_fake():\n    agent = LlmAgent(name='x', model=FakeLlm(responses=['ok']), instruction='Help.')\n    _, _, events = await run(agent, text='hi')\n    assert events[-1].content.parts[0].text == 'ok'\n```\n\nTo assert on the request shape instead, `monkeypatch` the agent's\n`canonical_model.generate_content_async`.\n\nDo **not** assert on `event.output` for an LLM agent's own event — the runner\nclears it before you see it. Assert on the downstream node's output, on\n`session.state[output_key]`, or on `event.content.parts[*].text`.\n\n## Tests that hit a real model\n\n```python\nimport os\n\nimport pytest\n\n\n@pytest.fixture(scope='session', autouse=True)\ndef adk_env():\n    if 'GOOGLE_API_KEY' not in os.environ:\n        pytest.skip('GOOGLE_API_KEY not set')\n    os.environ.setdefault('GOOGLE_GENAI_USE_ENTERPRISE', 'FALSE')\n\n\n@pytest.mark.integration\nasync def test_real_model():\n    ...\n```\n\n`pytest -m integration` runs them; `pytest -m \"not integration\"` skips them.\n\n## Habits that avoid flakes\n\n- One `InMemoryRunner` and one session per test — runners carry state.\n- A unique `app_name` per test (`request.node.name` works) so parallel pytest\n  workers do not collide.\n- `event.is_final_response()` filters for \"the agent's last word\".\n- Any LLM agent feeding a `JoinNode` needs `output_schema=`, or the join buffer\n  fails to serialize under `DatabaseSessionService`.\n- `pytest -xvs` while iterating: stop at the first failure, verbose, show prints.\n",".agents/skills/adk-agent-builder/references/tool-catalog.md":"# Tool Catalog\n\nEvery way to give an agent a capability, from a plain Python function to a whole\nremote API.\n\n## Python functions\n\nPass callables straight to `tools=`. The name, docstring, and type hints become\nthe schema the model sees, so all three are load-bearing — an undocumented or\nuntyped parameter is invisible to the model.\n\n```python\ndef get_weather(city: str, unit: str = 'celsius') -> str:\n  \"\"\"Get the current weather for a city.\n\n  Args:\n    city: The city name to look up.\n    unit: Temperature unit, 'celsius' or 'fahrenheit'.\n\n  Returns:\n    A string with the weather information.\n  \"\"\"\n  return f'Sunny, 22 degrees {unit} in {city}'\n\n\nroot_agent = Agent(tools=[get_weather], ...)\n```\n\nSync and async both work.\n\n### Getting the context inside a tool\n\nAdd a parameter annotated with `ToolContext` (or `Context` / `CallbackContext` —\nthey are all the same class). It is matched **by annotation**, not by name, and\nexcluded from the schema the model sees. A parameter literally named\n`tool_context` is used as a fallback when no annotation matches.\n\n```python\nfrom google.adk.tools import ToolContext\n\n\nasync def my_tool(query: str, tool_context: ToolContext) -> str:\n  tool_context.state['key'] = 'value'\n  await tool_context.save_artifact('f.txt', part)\n  results = await tool_context.search_memory('q')\n  return 'done'\n```\n\nA parameter named `input_stream` is also excluded, for streaming tools.\n\n## Built-in tools\n\n| Tool | Import from `google.adk.tools` |\n|---|---|\n| `google_search` | Google Search grounding |\n| `url_context` | Fetch and ground on URLs in the prompt |\n| `load_artifacts` | Pull session artifacts into context |\n| `load_memory` / `preload_memory` | Query long-term memory |\n| `exit_loop` | Break out of a `LoopAgent` |\n| `transfer_to_agent` | Hand control to another agent |\n| `get_user_choice` | Ask the user to pick an option |\n| `google_maps_grounding`, `enterprise_web_search` | Other grounding sources |\n\n## Long-running tools\n\n`LongRunningFunctionTool` returns its result asynchronously against the original\n`function_call_id`, which is how an agent pauses for a human.\n\n```python\nfrom google.adk.tools import LongRunningFunctionTool\n\n\ndef approve_expense(amount: float) -> dict:\n  \"\"\"Submit an expense for approval.\"\"\"\n  return {'status': 'pending', 'id': 'exp-123'}\n\n\nroot_agent = Agent(tools=[LongRunningFunctionTool(approve_expense)], ...)\n```\n\n## Workflows and `@node` functions as tools\n\nPass a `Workflow` (with `input_schema` set to a Pydantic `BaseModel`) or a\n`@node`-decorated function straight into `tools=`. The agent runs it in an\nisolated sub-branch (`{tool_name}@{function_call_id}`) and receives the\nterminal output.\n\nSet `ctx.actions.skip_summarization = True` inside the node when its output\nshould be emitted directly as the final user-visible text response without a\nfollow-up LLM summarization turn:\n\n```python\nfrom google.adk import Agent, Context\nfrom google.adk.workflow import node\n\n\n@node\ndef generate_report(project: str, ctx: Context) -> str:\n  \"\"\"Generate a status report for a project.\"\"\"\n  ctx.actions.skip_summarization = True\n  return f'Report for {project}: Ready'\n\n\nroot_agent = Agent(tools=[generate_report], ...)\n```\n\n## MCP servers\n\n```python\nfrom google.adk.tools.mcp_tool import McpToolset, StdioConnectionParams\nfrom mcp import StdioServerParameters\n\nroot_agent = Agent(\n    tools=[\n        McpToolset(\n            connection_params=StdioConnectionParams(\n                server_params=StdioServerParameters(\n                    command='npx',\n                    args=['-y', '@modelcontextprotocol/server-filesystem', '/path'],\n                ),\n                timeout=5,\n            ),\n            tool_filter=['read_file', 'list_directory'],\n        )\n    ],\n    ...\n)\n```\n\nConnection classes: `StdioConnectionParams`, `SseConnectionParams`,\n`StreamableHTTPConnectionParams`.\n\nNeeds `pip install mcp`. `StdioServerParameters` comes from that package, not\nfrom ADK. Use `McpToolset`; the all-caps `MCPToolset` still resolves but warns.\n\n## OpenAPI specs\n\n```python\nfrom google.adk.tools.openapi_tool import OpenAPIToolset\n\ntoolset = OpenAPIToolset(spec_str=open('openapi.yaml').read(), spec_str_type='yaml')\nroot_agent = Agent(tools=[toolset], ...)\n```\n\n`spec_str_type` is `'json'` (the default) or `'yaml'`. Pass `spec_dict=` instead\nto skip parsing. `RestApiTool` from the same module wraps a single endpoint.\n\n## Google API toolsets\n\nGenerated from Google's API discovery documents. `BigQueryToolset`,\n`CalendarToolset`, and their siblings all take the same arguments.\n\n```python\nfrom google.adk.tools.google_api_tool.google_api_toolsets import BigQueryToolset\n\nbigquery = BigQueryToolset(\n    client_id='...',\n    client_secret='...',\n    tool_filter=['bigquery_datasets_list'],\n)\n```\n\nAlso accepted: `service_account=` instead of the OAuth pair, and\n`tool_name_prefix=` to namespace the generated tool names.\n\n## Code execution\n\nThe code executor is its own agent field, not a tool.\n\n```python\nfrom google.adk.code_executors.built_in_code_executor import BuiltInCodeExecutor\n\nroot_agent = Agent(code_executor=BuiltInCodeExecutor(), ...)\n```\n\n## Custom `BaseTool`\n\n```python\nfrom google.adk.tools import BaseTool\nfrom google.genai import types\n\n\nclass MyTool(BaseTool):\n\n  def __init__(self):\n    super().__init__(name='my_tool', description='Does something.')\n\n  def _get_declaration(self):\n    return types.FunctionDeclaration(\n        name=self.name,\n        description=self.description,\n        parameters_json_schema={\n            'type': 'object',\n            'properties': {'param': {'type': 'string'}},\n            'required': ['param'],\n        },\n    )\n\n  async def run_async(self, *, args, tool_context):\n    return {'result': args['param']}\n```\n\n## Custom `BaseToolset`\n\nA toolset supplies tools dynamically, so the set can depend on context.\n\n```python\nfrom google.adk.tools.base_toolset import BaseToolset\n\n\nclass MyToolset(BaseToolset):\n\n  def __init__(self):\n    super().__init__(tool_filter=None, tool_name_prefix='my')\n\n  async def get_tools(self, readonly_context=None):\n    return [ToolA(), ToolB()]\n\n  async def process_llm_request(self, *, tool_context, llm_request):\n    llm_request.append_instructions(['Custom instruction'])\n```\n\n`tool_filter` is a list of tool names or a `ToolPredicate` callable;\n`tool_name_prefix` renames every tool the toolset returns, which is how you keep\ntwo toolsets from colliding.\n",".agents/skills/adk-architecture/SKILL.md":"---\nname: adk-architecture\ndescription: >-\n  Explains how the ADK runtime fits together: the node and graph execution\n  model, Context and Event flow, checkpoint and resume, tracing, and the rules\n  governing the public API surface. Use when answering \"how does X work\" about\n  ADK internals, tracing where an event or a piece of state comes from,\n  deciding where a new capability belongs, reviewing a change to BaseNode,\n  Workflow, Runner, Agent, Event or Context, working out why a node re-ran or\n  stayed waiting after a resume, or judging whether a change breaks the public\n  API. Don't use for assembling an agent from existing pieces (use\n  adk-agent-builder), diagnosing one failing run or test (use adk-debug), or\n  formatting and naming conventions (use adk-style).\n---\n\n# ADK Architecture\n\nThe runtime is a graph of nodes. `BaseNode` is the unit of execution.\n`Workflow` is a node that schedules other nodes along declared edges.\n`NodeRunner` executes exactly one node. `Runner` owns the invocation and the\nsession. Agents are nodes too — `BaseAgent` extends `BaseNode`.\n\nA node communicates with its parent through a per-execution `Context`, and\nwith the session through `Event`s it yields. Those are two separate channels:\n`ctx` carries the result upward, events carry persistence and streaming.\n\nRead the source before relying on any signature here. These notes drift; the\ncode does not. Paths below are relative to `src/google/adk/`.\n\n## Pick a reference\n\n| Question | Reference |\n|---|---|\n| What must a node implement? What may it yield? Which config fields exist? | [BaseNode](references/interface-base-node.md) |\n| How does the graph schedule nodes, dedup dynamic children, propagate interrupts? | [Workflow](references/interface-workflow.md) |\n| How does a caller start an invocation? | [Runner](references/interface-runner.md) |\n| What is `Agent`, and which methods do I call on it? | [Agent](references/interface-agent.md) |\n| I am subclassing an agent — what do I override? | [BaseAgent](references/interface-base-agent.md) |\n| What is on an `Event`, and what may I assume about its lifetime? | [Event](references/interface-event.md) |\n| What does a node read and write on `ctx`? | [Context](references/architecture-context.md) |\n| Who creates the child Context, stamps events, retries, catches errors? | [NodeRunner](references/architecture-node-runner.md) |\n| Why are Runner, NodeRunner and Workflow three separate things? | [Runner roles](references/architecture-runner-roles.md) |\n| How does a human-in-the-loop pause and resume work for one node? | [Checkpoint and resume](references/architecture-checkpoint-resume.md) |\n| How does a whole workflow survive a pause, and what does `is_resumable` change? | [Workflow resumability](references/architecture-workflow-resumability.md) |\n| How are spans created, and what attributes do they carry? | [Observability](references/architecture-observability.md) |\n| Why does the model not see the raw event log? | [LLM context orchestration](references/architecture-llm-context-orchestration.md) |\n| Is this change a breaking change? Where does a new export belong? | [API principles](references/api-principles.md) |\n\n## Where the code lives\n\n| Concept | Module |\n|---|---|\n| `BaseNode`, `START` | `workflow/_base_node.py` |\n| `Workflow`, `_LoopState` | `workflow/_workflow.py` |\n| `Graph`, edge compilation | `workflow/_graph.py` |\n| `NodeRunner` | `workflow/_node_runner.py` |\n| `DynamicNodeScheduler`, `DynamicNodeState`, `DynamicNodeRun` | `workflow/_dynamic_node_scheduler.py` |\n| `ReplayManager` (resume scan) | `workflow/utils/_replay_manager.py` |\n| `check_interception` (replay fast-forward) | `workflow/utils/_replay_interceptor.py` |\n| `NodeInterruptedError`, `NodeTimeoutError` | `workflow/_errors.py` |\n| `Context`, `ctx.run_node()` | `agents/context.py` |\n| `ReadonlyContext` | `agents/readonly_context.py` |\n| `InvocationContext` | `agents/invocation_context.py` |\n| `BaseAgent`, `LlmAgent` (aliased `Agent`) | `agents/base_agent.py`, `agents/llm_agent.py` |\n| `BaseLlmFlow`, `SingleFlow`, `AutoFlow`, `flows/llm_flows/` subpackages (`context/`, `prompt/`, `tools/`, `extensions/`, `core/`) | `flows/llm_flows/` |\n| `Event`, `NodeInfo` | `events/event.py` |\n| `EventActions` | `events/event_actions.py` |\n| Branch paths (`parent.child@1`) | `events/_branch_path.py` |\n| Node paths (`wf@1/child@2`) | `events/_node_path_builder.py` |\n| `Runner`, `InMemoryRunner` | `runners.py` |\n| `LiveRequestQueue`, `LiveRequest` | `live/live_request_queue.py` |\n| Node spans, `TelemetryContext` | `telemetry/node_tracing.py` |\n| `ResumabilityConfig` | `apps/_configs.py` |\n\nEverything under `workflow/` is a leading-underscore module. Treat those names\nas internal — they can change without a major version bump, so a change there\nis not automatically a breaking change.\n",".agents/skills/adk-architecture/references/api-principles.md":"# API Principles\n\nGuidelines for designing and maintaining the ADK public API surface.\n\n## Public API Surface\n\nThe public API surface of ADK includes:\n- All public classes, methods, and functions in the `google.adk` namespace.\n- The names, required parameters, and expected behavior of all built-in Tools.\n- The structure and schema of persisted data (Sessions, Memory, Evaluation datasets).\n- The JSON request/response format of the ADK API server.\n- The command-line interface (CLI) commands, arguments, and flags.\n- The expected file structure for agent definitions (e.g., `agent.py` convention loaded by CLI).\n\n## Design Principles\n\n### 1. Stability and Backward Compatibility\n- ADK adheres to Semantic Versioning 2.0.0.\n- Any change that forces a developer to alter their existing code to upgrade is a **breaking change** and necessitates a MAJOR version bump.\n- Avoid breaking changes whenever possible by using optional parameters and deprecation cycles.\n- **Session, Artifact, and Memory Service Changes**:\n  Adding any new field to `Session`, `ArtifactVersion`, `MemoryEntry`, or to their associated service base classes (`BaseSessionService`, `BaseArtifactService`, `BaseMemoryService`) and database schemas is strictly considered a **breaking change** (even if the new field is optional or provides a default value).\n  - *Rationale*: There are numerous downstream implementations and custom subclasses across diverse storage backends (e.g. relational databases, key-value stores, document databases, external cloud backends). Downstream storage implementations do not automatically persist, deserialize, or round-trip fields they were not compiled or migrated to handle, leading to silent state loss or deserialization errors when sessions or memories are reloaded.\n\n### 2. Self-Containment\n- Each package should be as self-contained as possible to reduce coupling.\n- Within the ADK framework, importing from a package's `__init__.py` is **not allowed**. Import from the specific module directly.\n\n### 3. Explicit Exports\n- The public API of a package must be explicitly exported in `__init__.py`.\n- **Only public names** should be imported into `__init__.py`. This keeps `__init__.py` minimal and prevents accidental exposure of internal implementation details.\n\n### 4. Intuitive Naming\n- Public method and class names should be concise and intuitive.\n- Private method names can be longer and more self-explanatory to reduce the need for comments.\n\n#### Examples\n\n**Public Naming**\n- **Good**: `Runner.run()`, `Session.get_events()`\n- **Bad**: `Runner.orchestrate_agent_invocation_loop()`, `Session.retrieve_all_events_from_storage()`\n\n**Private Naming**\n- **Good**: `_prepare_context_for_llm()`, `_should_trim_history()`\n- **Bad**: `_prep()`, `_trim()`\n",".agents/skills/adk-architecture/references/architecture-checkpoint-resume.md":"# Checkpoint and Resume Lifecycle\n\nHITL (Human-in-the-Loop) follows this pattern:\n\n1. **Interrupt**: Node yields an event with `long_running_tool_ids`.\n   Each ancestor propagates the interrupt upward via `ctx.interrupt_ids`.\n2. **Persist**: Only the leaf node's interrupt event is persisted to\n   session. Workflow sets `ctx._interrupt_ids` directly (no internal\n   event needed).\n3. **Resume**: User sends a `FunctionResponse` message. The Runner\n   scans session events to find the matching `invocation_id`, then\n   reconstructs node state from persisted events.\n4. **Continue**: The interrupted node receives the FR and continues\n   execution. Downstream nodes receive the resumed node's output.\n\n## run_id on resume\n\nResumed nodes reuse the same `run_id` from the original\nexecution. From the node's perspective, the execution never paused\n— events before and after the resume share the same run_id.\n\nFresh dispatches (first run, loop re-trigger) get a new run_id.\n\n## Resume behavior by `rerun_on_resume`\n\nA node with multiple interrupt IDs may receive partial FRs (only\nsome resolved). The behavior depends on `rerun_on_resume`:\n\n**`rerun_on_resume=True`** (Workflow, orchestration nodes):\n\n| FRs received | Status | Behavior |\n|---|---|---|\n| Partial | PENDING | Re-execute immediately with partial `resume_inputs`. Node handles remaining interrupts internally (e.g., Workflow dispatches resolved children, keeps unresolved as WAITING). |\n| All | PENDING | Re-execute with all `resume_inputs`. |\n\nThis is critical for Workflow — when one child's FR arrives, it\nre-runs immediately to dispatch that resolved child. It doesn't\nwait for all children's FRs.\n\n**`rerun_on_resume=False`** (leaf nodes, simple HITL):\n\n| FRs received | Status | Behavior |\n|---|---|---|\n| Partial | WAITING | Stay waiting. Need all FRs. |\n| All | COMPLETED | Auto-complete. Output = aggregated `resolved_responses`. No re-execution. |\n\n## Resume with prior output and interrupts\n\nA node can produce output AND interrupt in the same execution (e.g.,\na Workflow where child A completes with output and child B interrupts).\nOn resume:\n\n- Some interrupt IDs are resolved (provided in `resume_inputs`)\n- Remaining interrupt IDs carry forward via `prior_interrupt_ids`\n- Prior output carries forward via `prior_output`\n- NodeRunner pre-populates ctx with these values before re-executing\n\n```python\nrunner = NodeRunner(\n    node=node, parent_ctx=ctx,\n    run_id=prior_run_id,  # reuse\n    prior_output=cached_output,\n    prior_interrupt_ids={'fc-2'},  # still unresolved\n)\nchild_ctx = await runner.run(\n    node_input=input,\n    resume_inputs={'fc-1': response},\n)\n```\n",".agents/skills/adk-architecture/references/architecture-context.md":"# Context\n\n## Two scoping objects\n\n- **InvocationContext** — one per invocation. Holds shared state (session,\n  services, event queue) reachable from every node.\n- **Context** — one per node execution. Holds per-node results (output, route,\n  interrupt IDs) and is the API surface node code actually touches.\n\nEvery Context references the same InvocationContext (`_invocation_context`).\nService access (artifacts, memory, auth) is delegated through it.\n\n```text\nRoot Context                      ← created by Runner from the InvocationContext\n└── Context [runner.node]         ← the root node (e.g., Workflow)\n    ├── Context [child_a]         ← child node A\n    └── Context [child_b]         ← child node B\n        └── Context [grandchild]  ← nested child\n```\n\nThe Runner creates the root Context and passes it as `parent_ctx` to the root\nnode's `NodeRunner`. The root Context exists only to be that parent; it carries\nno node path of its own.\n\nInvocationContext holds:\n\n- `session`, `agent`, `user_content`, `branch`\n- `invocation_id`, `app_name`, `user_id`\n- Services: `artifact_service`, `memory_service`, `credential_service`\n- `run_config`, `live_request_queue`\n- `is_resumable` — derived from the app's `ResumabilityConfig`\n- `_event_queue` — private queue drained by the Runner's main loop. Nodes never\n  touch it directly; they go through `ic._enqueue_event(event)`.\n\n## 1:1 node-context mapping\n\nEvery node execution gets its own Context. The relationship is strictly 1:1,\nso the Context tree mirrors the node execution tree.\n\n`NodeRunner._create_child_context()` builds the child from the parent. The\nchild inherits:\n\n- `_invocation_context` — the same object, so session and services are shared\n  (copied with `model_copy` when the child runs on a sub-branch)\n- `node_path` — parent path plus `name@run_id`\n- `run_id` — a sequential counter string per node path, reused on resume\n- `event_author`\n- the enclosing Workflow's dynamic-node scheduler\n\nThe child does not inherit output, route or interrupt IDs — those are\nper-execution results and start empty, unless a resume carries them forward via\n`prior_output` / `prior_interrupt_ids`.\n\n## Node result properties\n\nThese are how a node hands results back to its parent:\n\n- **`ctx.output`** — the node's result. Set once per execution, either by\n  `yield value` (the framework assigns it) or by `ctx.output = X`. A second\n  write raises `ValueError`.\n- **`ctx.route`** — routing value for conditional edges, `RouteValue` or a\n  list of them. Independent of output. Workflow-specific.\n- **`ctx.interrupt_ids`** — read-only set. The framework fills it when the node\n  yields an Event carrying `long_running_tool_ids`; the getter returns a copy.\n\nOutput and interrupts can coexist in one execution — a Workflow whose child A\nfinished while child B is waiting has both. The orchestrator's `_finalize`\ndecides what to propagate upward.\n\n## Class hierarchy\n\n```text\nReadonlyContext\n  └── Context\n```\n\n**ReadonlyContext** — read-only view handed to callbacks and plugins:\n`user_content`, `invocation_id`, `agent_name`, `session`, `user_id`,\n`run_config`, and `state` as an immutable `MappingProxyType`.\n\n**Context** — full read-write context for node execution.\n\n## Property reference\n\n| Category | Members |\n|---|---|\n| State & actions | `state` (mutable `State`), `actions` (`EventActions`), `custom_metadata` |\n| Node results | `output`, `route`, `interrupt_ids` (read-only) |\n| Node identity | `node`, `parent_ctx`, `node_path`, `run_id`, `attempt_count`, `resume_inputs` |\n| Scoping | `branch`, `isolation_scope` |\n| Errors | `error`, `error_node_path` — set by NodeRunner when the node fails and retries are exhausted |\n| Telemetry | `telemetry_context` |\n| Session | `session`, `get_invocation_context()` |\n| Child execution | `run_node()` |\n| Artifacts | `load_artifact()`, `save_artifact()`, `get_artifact_version()`, `list_artifacts()` |\n| Memory | `search_memory()`, `add_session_to_memory()`, `add_events_to_memory()`, `add_memory()` |\n| Auth | `request_credential()`, `load_credential()`, `save_credential()`, `get_auth_response()` |\n| Tools | `request_confirmation()`, `tool_confirmation`, `function_call_id` |\n| UI | `render_ui_widget()` |\n",".agents/skills/adk-architecture/references/architecture-llm-context-orchestration.md":"# LLM context orchestration from events\n\n## Source versus view\n\nThe event stream and the LLM context are not the same thing:\n\n- **Events are the ground truth.** Immutable records of what happened — user\n  messages, model responses, tool calls, results. They are the audit log and\n  the persistence state.\n- **LLM context is an orchestrated view.** What gets sent to a model is not a\n  dump of the event log. It is filtered and transformed for the role, task and\n  branch of the agent currently running.\n\nTreating the two as interchangeable is the root of most \"why did the model see\nthat?\" bugs.\n\n## Delegation\n\nA coordinator hands work to a sub-agent with the `transfer_to_agent` tool,\nwhich sets `actions.transfer_to_agent` on the event rather than calling the\nsub-agent inline. The alternative is `AgentTool`, which wraps an agent so it is\ninvoked as an ordinary tool and returns its result to the caller.\n\nEither way the sub-agent does not inherit the coordinator's full transcript\nverbatim; it is given the task framing plus whatever history its branch\nexposes.\n\n## Branch isolation\n\nEvents from every node and branch share one session, in chronological order.\nIsolation comes from the `branch` field, a dot-separated path built by\n`_BranchPath`: `create_sub_branch('parent', name='child', run_id='1')` yields\n`'parent.child@1'`. A node running on a sub-branch sees only events on its own\npath, which is what keeps parallel siblings from polluting each other.\n\n`Event.isolation_scope` is a separate, coarser tag — NodeRunner stamps it from\n`ctx.isolation_scope` when the event does not carry one already.\n\n## History trimming and compaction\n\nLong histories overflow the context window and drag stale retry loops back into\nthe prompt. When the app sets `events_compaction_config`, the Runner compacts\nevents after the invocation. Because compaction rewrites aged history, do not\nstore transient status on an event and expect to read it back later.\n",".agents/skills/adk-architecture/references/architecture-node-runner.md":"# NodeRunner\n\n`NodeRunner` is the per-node executor. It creates the child Context, drives\n`BaseNode.run()`, opens the node's span, enriches and enqueues events, retries\non failure, and returns the child Context to the caller.\n\n## Two communication channels\n\n- **Context** — parent ↔ child. Output, route, state, resume inputs and\n  interrupt IDs flow through `ctx`. The orchestrator reads `ctx` after the\n  child finishes to decide what happens next.\n- **Event** — persistence and streaming. Events are appended to the session and\n  streamed to the caller. They carry message content, state deltas, function\n  calls and interrupt markers.\n\nA node writes to `ctx` to talk to its parent. It yields Events to persist data\nand stream to the user.\n\n## Execution flow\n\n```text\nOrchestrator\n  │\n  ├─ NodeRunner(node=child, parent_ctx=ctx)\n  │    │\n  │    ├─ _create_child_context()       → child Context (attempt_count)\n  │    ├─ start_as_current_node_span()  → ctx._telemetry_context\n  │    ├─ _execute_node()               → iterate node.run()\n  │    │    ├─ _track_event_in_context()    → write results to ctx\n  │    │    └─ _enqueue_event()             → enrich + persist\n  │    ├─ _flush_output_and_deltas()    → emit deferred output/route/deltas\n  │    └─ return child ctx\n  │\n  └─ reads ctx.output, ctx.route, ctx.interrupt_ids, ctx.error\n```\n\n1. **Create child Context.** Shares the InvocationContext, builds `node_path`\n   from the parent, assigns `run_id`, records `attempt_count`. If the session\n   already holds events for this node path, resolved responses are rehydrated\n   into `ctx._resume_inputs` before the node runs.\n\n2. **Open the span** via `node_tracing.start_as_current_node_span`, storing the\n   resulting `TelemetryContext` on `ctx`. Opening it inside the `try` is\n   deliberate — exceptions are recorded on the span.\n\n3. **Iterate `node.run()`.** For each yielded Event:\n\n   - **Track in context** — `_track_event_in_context` copies output, route and\n     `long_running_tool_ids` onto `ctx`, which is the source of truth. Route\n     and `transfer_to_agent` are only picked up from *native* events (no\n     author, or authored by this node), so a composite parent does not\n     re-bubble a decision its own sub-agent already handled. The event ID is\n     also registered on the node's span.\n   - **Enrich** — `_enrich_event` stamps `author` (`ctx.event_author` or the\n     node name), `invocation_id`, `node_info.path`, `branch`, and\n     `isolation_scope`. For an event carrying output it also sets\n     `node_info.output_for` to this node path plus its output ancestors.\n     `node_info.run_id` is **not** stamped separately — it is a property\n     derived from the last segment of `node_info.path` (`wf@1/child@2`).\n   - **Flush deltas** — for non-partial events, pending state and artifact\n     deltas move from `ctx.actions` onto the event.\n   - **Enqueue** — `ic._enqueue_event(event)` puts it on the shared queue for\n     session persistence.\n\n4. **Flush deferred output.** If `ctx.output` or `ctx.route` were set directly\n   rather than yielded, `_flush_output_and_deltas` emits one final Event after\n   `_run_impl` returns, bundling any remaining deltas onto it.\n\n5. **Return the child ctx.** The orchestrator reads `output`, `route`,\n   `interrupt_ids` and `error`.\n\n## Timeouts, retries and errors\n\n- `node.timeout` wraps the iteration in `asyncio.wait_for`; a timeout raises\n  `NodeTimeoutError`.\n- On any exception, NodeRunner enqueues an Event with `error_code` and\n  `error_message`, then consults `node.retry_config`. A retry sleeps for the\n  configured delay, increments `attempt_count` and rebuilds the child Context\n  from scratch. Retry count is **not** persisted, so it does not survive a\n  resume.\n- A child started with `ctx.run_node()` that fails reaches the node as\n  `DynamicNodeFailError`. The child's runner already emitted the error event,\n  so NodeRunner only matches the retry policy against the child's error. A\n  node with a `retry_config` copies the run-id counters under it at the start\n  of every attempt (`_snapshot_dynamic_children`), and a retry for such a\n  failure restores that copy (`_reset_dynamic_children_for_retry`). The retry\n  then hands out the same run ids and replays the children the failed attempt\n  completed, at any depth. A retry for the node's own exception keeps the\n  counters, so its children run again.\n- A parallel worker (`_ParallelWorker`, or a `Node` with\n  `parallel_worker=True`) is not retried for an item's `DynamicNodeFailError`\n  (`_delegates_item_retry`): the item node already applied the retry policy\n  to that item.\n- A `Workflow` reports a failing child by setting `ctx.error` rather than\n  raising, and NodeRunner applies the retry policy to that error as well.\n- When retries are exhausted, the error is recorded on `ctx.error` and\n  `ctx.error_node_path` and the Context is returned normally — NodeRunner does\n  not re-raise to the orchestrator.\n- `NodeInterruptedError` from a dynamic child is swallowed here: the child's\n  interrupt IDs are already on `ctx`, so the caller just reads\n  `ctx.interrupt_ids`.\n- When `abort_signal` is triggered (`ic.is_aborted` is `True`), active node\n  tasks are cancelled without retrying. In `_node_runner_utils`, the outer\n  workflow runner loop catches the cancellation, calls\n  `runner._synthesize_abort_events_if_needed(ic)` to emit `INVOCATION_ABORTED`\n  events (and seal any dangling `FunctionCall`s), and finishes the generator\n  cleanly so `after_run` callbacks still run.\n\n## Output delegation (`use_as_output`)\n\nWhen a child is scheduled with `use_as_output=True`, its output Event also\ncounts as the parent's output. NodeRunner sets `ctx._output_delegated`, drops\nthe output field from the parent's own event (keeping the event if it still\ncarries deltas), stamps `node_info.output_for` with the ancestor paths, and\npublishes non-tool output events on the outermost explicit claiming ancestor's\nbranch (leaving tool responses such as `finish_task` paired on the child's\nbranch).\n",".agents/skills/adk-architecture/references/architecture-observability.md":"# Observability\n\nNode tracing lives in `telemetry/node_tracing.py`. Spans are **scope-based**:\n`NodeRunner` opens one around each node execution with an async context\nmanager, so the span closes on the way out even if the node raises.\n\n```python\nasync with node_tracing.start_as_current_node_span(parent_ctx, node) as tel_ctx:\n  ctx._telemetry_context = tel_ctx\n  await self._execute_node(ctx, node_input)\n```\n\n## TelemetryContext, not a raw span\n\nEach `Context` exposes `ctx.telemetry_context`, a frozen dataclass:\n\n| Member | Purpose |\n|---|---|\n| `otel_context` | OTel context holding the current span. Passed to children so their spans parent correctly. |\n| `add_event(event)` | Records an event ID as belonging to this node's span. |\n\nThe parent's `otel_context` is what makes the span tree mirror the node tree —\n`start_as_current_node_span` passes `context.telemetry_context.otel_context` as\nthe explicit parent rather than relying on whatever OTel considers current.\nInside the span the child builds a fresh `TelemetryContext` from\n`context_api.get_current()`.\n\nWhen attaching OTel context by hand (`context_api.attach()`), pair it with\n`detach()` in a `finally` — an unbalanced attach leaks the context into\nwhatever coroutine runs next on the loop.\n\n## Which span a node gets\n\n`start_as_current_node_span` dispatches on node type:\n\n| Node type | Span |\n|---|---|\n| `BaseAgent` subclass | none of its own — passes through, the agent emits its own `invoke_agent {name}` span |\n| `Workflow` | `invoke_workflow {name}` |\n| anything else | `invoke_node {name}` |\n\n`invoke_agent` follows OpenTelemetry semantic conventions v1.36 for backward\ncompatibility; `invoke_workflow` follows v1.41; `invoke_node` is not in any\nsemconv release yet.\n\n## Attributes\n\n| Attribute | Set on | Value |\n|---|---|---|\n| `gen_ai.operation.name` | all | `\"invoke_workflow\"` / `\"invoke_node\"` |\n| `gen_ai.conversation.id` | all | `ctx.session.id` |\n| `gen_ai.workflow.name` | workflow | the workflow's `name`, when non-empty |\n| `gen_ai.workflow.nested` | workflow | `True` only for a nested workflow; the entrypoint workflow omits the attribute entirely |\n| `gcp.vertex.agent.associated_event_ids` | all | IDs collected via `tel_ctx.add_event()`, stamped on span close when non-empty and the span is recording |\n| `adk.experimental.response.source` | `execute_tool`, `call_llm` | Callback provenance when a callback or plugin answered or replaced the result and `ADK_EXPERIMENTAL_TELEMETRY=true` is set (`\"before_tool_callback\"`, `\"after_tool_callback\"`, `\"on_tool_error_callback\"` on `execute_tool`; `\"before_model_callback\"`, `\"on_model_error_callback\"` on `call_llm`); omitted when the tool or model itself answered |\n\nNesting is detected through an OTel context key set by the first workflow in\nthe invocation. Because the key rides on the propagated `otel_context`, an\nagent-as-tool that spins up its own runner still reports `nested=true`.\n\n## Metrics\n\nRecorded from `telemetry/_metrics.py` under the `gcp.vertex.agent` meter:\n\n| Function | Recorded when |\n|---|---|\n| `record_workflow_invocation_duration` | an `invoke_workflow` span closes, tagged with `nested` and any error |\n| `record_agent_invocation_duration` | an agent invocation completes |\n| `record_invoke_agent_inference_calls` | per agent, count of model calls |\n| `record_invoke_agent_tool_calls` | per agent, count of tool calls |\n| `record_tool_execution_duration` | a tool finishes |\n| `record_client_operation_duration`, `record_client_token_usage` | model client calls |\n\n## Python logging\n\nUse the `google_adk` logger namespace so callers can filter ADK output:\n\n```python\nlogger = logging.getLogger(\"google_adk.\" + __name__)\n\nlogger.debug(\"node %s started.\", ctx.node_path)\n```\n\nUse `%`-style arguments, not f-strings — the formatting is then skipped\nentirely when the level is disabled.\n\n`NodeRunner` already logs node start, node end, execute-loop boundaries,\nrehydrated resume inputs, retries (`warning`) and unhandled exceptions\n(`logger.exception`). Do not re-log those from inside a node.\n",".agents/skills/adk-architecture/references/architecture-runner-roles.md":"# Runner vs NodeRunner vs Workflow\n\nThese three are deliberately separate:\n\n- **Runner** = lifecycle orchestrator (InvocationContext, session,\n  plugins, invocation boundaries)\n- **NodeRunner** = task scheduler (asyncio tasks, node execution,\n  completions)\n- **Workflow** = graph engine (edges, traversal, node sequencing)\n\nMerging Runner and NodeRunner would deadlock on nested workflows\n(inner workflow's NodeRunner would block the outer's Runner).\n",".agents/skills/adk-architecture/references/architecture-workflow-resumability.md":"# Workflow resumability: model and direction\n\nHow a `Workflow` node preserves and restores execution state across a\nhuman-in-the-loop pause, how that compares to peer agent frameworks, and where\nit is heading. This is the whole-workflow view; the interrupt/resume lifecycle\nof a single node is covered separately.\n\nThe first thing to be clear about: a `Workflow` reconstructs its progress from\nthe session event stream on every run, so it resumes whether or not resumability\nis configured. The `is_resumable` flag does not switch resume on or off. What it\nswitches on is **durability** — persisting loadable checkpoints and letting an\ninvocation be continued across separate runner calls.\n\n## Current model\n\n### Resume is always on: reconstruction by event replay\n\nOn every run, the workflow scans the current invocation's session events and\nrebuilds its in-memory loop state (which nodes completed, their outputs, which\nare still waiting on interrupts). Completed nodes are fast-forwarded from their\nrecovered output rather than re-executed; the interrupted node re-runs with the\nsupplied responses.\n\nThis path (`_run_impl` -> `ReplayManager.scan_workflow_events`) has no\n`is_resumable` guard — the scan matches events purely by invocation id. The loop\nstate is not persisted and the checkpoint the workflow writes is not read back\non the load path; the session event log is the source of truth. So within an\ninvocation, a workflow is inherently replay-resumable, flag or no flag.\n\n### What `is_resumable` actually adds: durability\n\n`ResumabilityConfig.is_resumable` is a durability switch. When it is set:\n\n1.  **Cross-call resume.** The runner will continue an existing invocation\n    without a fresh user message and set up a \"resumed invocation\" context,\n    rehydrating the recorded agent state and end-of-agent markers from history.\n    With the flag off, the runner requires a new message and starts a fresh\n    invocation instead.\n2.  **Checkpoint markers in the log.** Composite agents (`LlmAgent`,\n    `SequentialAgent`, `LoopAgent`, `ParallelAgent`, and `LlmAgent`s wrapped as\n    workflow nodes) write `agent_state` / `end_of_agent` events into the session\n    only when the invocation is resumable. These make progress loadable across a\n    process boundary.\n3.  **Function-response routing.** Routing an incoming function response back to\n    its originating invocation is enabled only when resumable.\n\nThe config's own definition is durability-shaped: pause an invocation on a\nlong-running call, and resume it from the last event if it paused or failed\nmidway, best-effort and at-least-once, with in-memory state lost. So the\naccurate statement is: the flag decides whether progress is persisted as\nloadable checkpoints and whether an invocation can be resumed across runner\ncalls — not whether the workflow can resume. Resumability here is really\ndurability.\n\n### The `Workflow` node writes a checkpoint, but does not read one\n\n`Workflow._emit_node_checkpoint` does persist a node-status snapshot when\n`ic.is_resumable` — an `agent_state` event carrying `{\"nodes\": {name: {status,\ninterrupts}}}` for each static child (`resume_inputs` is intentionally omitted\nfrom the checkpoint). Two sibling markers are\nwritten under the same flag: `_maybe_reemit_replayed_output` re-surfaces a\nfast-forwarded node's output, and `_emit_end_of_agent` records that the\nworkflow ran to completion.\n\nWhat is missing is the other half. Nothing loads that checkpoint: setup still\nbegins with `ReplayManager.scan_workflow_events` over the whole invocation, and\n`_run_impl` carries a `TODO: resume from checkpoint event`. So the checkpoint\nis currently an observable record of progress rather than the durable state\nresume actually runs off. Resume cost still scales with history length.\n\n## How peer frameworks do it\n\nEvery mainstream agent framework persists a **state snapshot with a position\ncursor** and, on resume, **loads that snapshot** — none reconstruct by replaying\nthe entire history.\n\nFramework                | Durable unit                                                        | Position cursor                                                                    | Resume\n------------------------ | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------\nLangGraph (graph)        | `StateSnapshot` per super-step in a pluggable checkpointer          | `next` nodes + parent-pointer chain + per-task pending writes                      | re-invoke same thread id; load latest checkpoint, re-run only the interrupted node\npydantic-graph (graph)   | `NodeSnapshot{state, node, status}` via a state-persistence backend | the snapshot's `node` = next node to run; `status` created/pending/running/success | `iter_from_persistence()` loads the next `created` snapshot\nOpenAI Agents SDK (loop) | serialized `RunState` blob                                          | run cursor inside the state; correlate by tool-call id                             | deserialize the state, apply approvals, resume the run\nPydantic AI (loop)       | message history + deferred-tool results                             | implicit in the transcript; correlate by tool-call id                              | new run over the prior message history\n\nADK's durable unit today is the event log itself, and resume is by replay over\nit. Two patterns from the peers are worth copying, both consistent across them:\n\n-   **A snapshot is the source of truth for resume.** The runtime writes a\n    snapshot as it advances and reads the latest one to continue. Resume cost is\n    bounded by the snapshot size, not by history length.\n-   **Resume re-runs the paused unit from its start** (LangGraph and\n    pydantic-graph both re-execute the whole node, not a saved program counter),\n    which keeps the durable state small and pushes an idempotency contract onto\n    the node author — the same at-least-once contract ADK already documents.\n\n## Direction: load the checkpoint instead of replaying\n\nThe write half exists. The remaining step — peer-aligned, and the one ADK's own\ncomposite agents already take — is to make that checkpoint the thing resume\nreads:\n\n-   On resume, seed the loop state from the most recent `agent_state`\n    checkpoint instead of scanning the invocation's events, then continue:\n    re-run only the interrupted node and dispatch newly-ready successors.\n-   This makes resume cost independent of history length and unifies the\n    `Workflow` node with composite agents and with LangGraph / pydantic-graph /\n    the OpenAI SDK.\n\nThis only applies when durability is on. Without `is_resumable` nothing is\npersisted, and the workflow continues to resume within an invocation by replay.\n\n## Open considerations\n\n-   **Payload completeness.** A workflow checkpoint must carry (or be able to\n    recover) each completed node's output, run id, and branch — the equivalent\n    of LangGraph's per-task pending writes — to fully replace event replay.\n-   **Partial interrupt resolution.** A node with several interrupts may receive\n    only some responses on a resume. The re-run-vs-wait behavior differs between\n    an orchestrating node (re-run to dispatch the resolved branch) and a leaf\n    node (wait for all), keyed on `rerun_on_resume`. This is decided in the\n    shared replay-interception logic and should be settled before a load path\n    relies on it.\n-   **Versioning.** Long-lived paused runs can outlive a code change. A version\n    marker on the checkpoint lets a resume route to a compatible code path (the\n    OpenAI SDK makes this an explicit recommendation).\n-   **Serialization.** Keep payloads JSON-serializable (Pydantic `model_dump`),\n    so any persistence backend works and no code objects are serialized; node\n    objects are rebound from the in-memory graph definition on resume.\n",".agents/skills/adk-architecture/references/interface-agent.md":"# Agent\n\n`Agent` is a type alias for `LlmAgent` (`Agent: TypeAlias = LlmAgent`), the\nmodel-backed agent most users instantiate. It is not the same thing as\n`BaseAgent`, which is the abstract base every agent derives from.\n\n`BaseAgent` extends `BaseNode`, so an agent is a node: it can stand alone under\na Runner or sit inside a Workflow graph.\n\n## Key fields\n\n- **`name`** — unique identifier within the agent tree. Validated: must be a\n  valid Python identifier, and `\"user\"` is rejected because it is reserved for\n  end-user input.\n- **`description`** — capability description the model uses when choosing\n  which sub-agent to delegate to.\n- **`sub_agents`** — child agents. Names must be unique across the tree;\n  each child's `parent_agent` is wired up automatically.\n- **`before_agent_callback` / `after_agent_callback`** — intercept the agent\n  lifecycle.\n\n## Entrance methods\n\n| Method | Use |\n|---|---|\n| `run_async(parent_context)` | Text conversation. Yields `Event`s. Runs the before/after callbacks, the error callback, and invocation instrumentation around `_run_async_impl`. |\n| `run_live(parent_context)` | Video/audio conversation. Marked `@final` — override `_run_live_impl` instead. |\n| `run(ctx=..., node_input=...)` | Inherited from `BaseNode`, `@final`. This is what a Workflow calls when the agent is a graph node; it routes into `_run_impl`, which for `BaseAgent` delegates to `run_async`. |\n\nWhich one you call depends on the caller, not on age: a Workflow calls `run()`,\ndirect text callers use `run_async()`. Nothing in the source marks `run_async`\ndeprecated, and it remains the path every agent's real logic runs through.\n\n## Other methods\n\n- **`clone(...)`** — copy the agent, detached from its parent.\n- **`find_agent(name)` / `find_sub_agent(name)`** — search the agent tree.\n- **`root_agent`** — walk up to the top of the tree.\n- **`from_config(config, config_abs_path)`** — build an agent from a config\n  object. Both `@deprecated` and `@experimental`; do not build on it.\n",".agents/skills/adk-architecture/references/interface-base-agent.md":"# BaseAgent\n\n`BaseAgent` is the abstract base for every agent. It extends `BaseNode`, so an\nagent is a node with agent-specific lifecycle on top: callbacks, error\nhandling, invocation instrumentation, and an agent tree.\n\n## What to override\n\nOverride **`_run_async_impl(ctx)`** for text conversation, and\n**`_run_live_impl(ctx)`** for live audio/video. Both receive an\n`InvocationContext`. Every built-in composite agent — `LlmAgent`,\n`SequentialAgent`, `LoopAgent`, `ParallelAgent` — implements these two and\nnothing else.\n\nDo **not** override `_run_impl`. `BaseAgent` already overrides it (marked\n`@override`) as the bridge from node execution into `run_async`, which is what\napplies the before/after callbacks, the error callback and the invocation\nmetrics. Replacing it silently drops all of that.\n\n```text\nWorkflow calls node.run()          (BaseNode, @final)\n  └─ BaseAgent._run_impl           (bridge — do not override)\n      └─ BaseAgent.run_async       (callbacks, instrumentation, error handling)\n          └─ your _run_async_impl  ← override point\n```\n\n`LlmAgent` is the exception that proves the rule: it overrides `_run_impl` too,\nin order to run through a dedicated node wrapper. That is framework-internal.\n\n## Key attributes to configure\n\n- **`name`** — must be a valid Python identifier, unique within the agent tree,\n  and cannot be `\"user\"`.\n- **`description`** — capability description used by the model for delegation.\n- **`sub_agents`** — child agents for hierarchical delegation. Duplicate names\n  across the tree are rejected at validation time.\n- **`before_agent_callback` / `after_agent_callback`** — lifecycle hooks. Both\n  accept a list; the canonical forms are exposed as\n  `canonical_before_agent_callbacks` / `canonical_after_agent_callbacks`.\n\n## Author attribution\n\nWhen an agent runs as a workflow node, `_run_impl` copies each event's author\nonto `ctx.event_author` so the enclosing NodeRunner does not overwrite it with\nthe parent workflow's name. Events therefore stay attributed to the agent that\nactually produced them.\n",".agents/skills/adk-architecture/references/interface-base-node.md":"# BaseNode\n\nBaseNode is the primitive unit of execution in the workflow runtime.\nEvery computation — LLM calls, tool execution, orchestration — is\na node. It is a Pydantic `BaseModel` subclass.\n\n## The node contract\n\nEvery node follows a two-method pattern:\n\n- `run()` is `@final` — normalizes yields to Events. Never override.\n- `_run_impl()` is the extension point — subclasses implement their\n  logic here as an async generator.\n\n```python\nclass MyNode(BaseNode):\n    async def _run_impl(self, *, ctx, node_input):\n        result = do_work(node_input)\n        yield result  # becomes Event(output=result)\n```\n\n**Why this split:** `run()` guarantees consistent normalization\nregardless of what the subclass does. The subclass only thinks\nabout its domain logic.\n\n**Normalization rules** (`run()` applies these to each yield):\n\n- `None` → skipped\n- `Event` → pass through\n- `RequestInput` → interrupt Event\n- any other value → `Event(output=value)`\n\n**Generator conventions:**\n\nA node can yield three types of data:\n\n- **Output** — the node's result value. Flows between nodes\n  (parent reads `ctx.output` after child completes). At most one\n  per execution (second raises `ValueError`).\n- **Message** — user-visible content streamed to the end user\n  (e.g., progress text, partial responses). Multiple allowed.\n- **Route** — Workflow-specific concept. Triggers conditional\n  edges in the graph. Set via `ctx.route` or `event.actions.route`.\n\nAdditional rules:\n\n- Yielding nothing produces no output event\n- `yield None` is silently skipped\n\nA custom node interacts with the runtime through two arguments:\n\n- **`ctx`** (Context) — communicate results to the parent node\n- **`node_input`** — data passed by the parent/orchestrator\n\n## Output and streaming\n\nThree ways to produce output (pick one per execution):\n\n```python\n# 1. Yield a value (most common)\nasync def _run_impl(self, *, ctx, node_input):\n    yield compute(node_input)\n\n# 2. Set ctx.output directly\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.output = compute(node_input)\n    return\n    yield  # generator contract\n\n# 3. Yield an Event with output\nasync def _run_impl(self, *, ctx, node_input):\n    yield Event(output=compute(node_input))\n```\n\nA second output raises `ValueError` — at most one per execution.\n\n**Streaming messages** — yield Events with `message` to send\nuser-visible text (`message` is an alias for `content` on Event):\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    yield Event(message='working...')\n    yield final_result  # this is the output\n```\n\n## State and routing\n\n**Mutating state:**\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.state['key'] = 'value'  # recorded as state_delta\n    yield result\n```\n\n**Setting route for conditional edges:**\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.route = 'approve' if score > 0.8 else 'reject'\n    yield node_input\n```\n\n## Advanced: child nodes and HITL\n\n**Running child nodes** via `ctx.run_node()`:\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    child_result = await ctx.run_node(some_node, node_input)\n    yield f'child said: {child_result}'\n```\n\nRequires `rerun_on_resume = True` on the calling node.\n\n**Requesting interrupt (HITL):**\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    if ctx.resume_inputs and 'fc-1' in ctx.resume_inputs:\n        yield f'approved: {ctx.resume_inputs[\"fc-1\"]}'\n        return\n    yield Event(long_running_tool_ids={'fc-1'})\n```\n\n## Configuration reference\n\n| Field | Type | Default | Purpose |\n|---|---|---|---|\n| `name` | `str` | required | Unique identifier. Validated: must be a valid Python identifier. |\n| `description` | `str` | `''` | Human-readable description |\n| `rerun_on_resume` | `bool` | `False` | Re-execute from scratch on resume (required for `ctx.run_node()`). When `False` the node completes immediately using the resume input as its output. |\n| `wait_for_output` | `bool` | `False` | Stay WAITING until output *or* route is yielded, so predecessors can re-trigger the node (join nodes). Never yielding either deadlocks the node. |\n| `retry_config` | `RetryConfig \\| None` | `None` | Retry on failure. Not persisted, so the count restarts after a resume. |\n| `timeout` | `float \\| None` | `None` | Max execution time in seconds; exceeding it raises `NodeTimeoutError`, which `retry_config` can retry. |\n| `input_schema` | `SchemaType \\| None` | `None` | Validate/coerce input data before `run()` |\n| `output_schema` | `SchemaType \\| None` | `None` | Validate/coerce output data |\n| `state_schema` | `type[BaseModel] \\| None` | `None` | Declare expected `ctx.state` keys and types; mutations are validated at runtime. Children inherit it unless they declare their own. Prefixed keys (`app:`, `user:`, `temp:`) bypass validation. |\n\n`START` (in the same module) is a sentinel `BaseNode` marking a graph's entry\npoint. It is never executed — the Workflow seeds triggers for its successors\ndirectly.\n",".agents/skills/adk-architecture/references/interface-event.md":"# Event\n\n`Event` is the record of one thing that happened in a session — a message, a\ntool call, a state change, a node's output. It is the unit of persistence and\nthe substrate every resume path reads. It extends `LlmResponse`, so response\nfields (`content`, `partial`, `error_code`, `error_message`, ...) are Event\nfields too.\n\n## Key fields\n\n| Field | Meaning |\n|---|---|\n| `invocation_id` | Invocation this event belongs to. Non-empty before appending to a session. |\n| `author` | `'user'` or the name of the agent/node that appended it. |\n| `content` | The message payload, inherited from `LlmResponse`. |\n| `actions` | `EventActions` — `state_delta`, `artifact_delta`, `route`, `transfer_to_agent`, `escalate`, `agent_state`, `end_of_agent`, requested auth configs and tool confirmations. Function calls live in `content`, not here. |\n| `output` | Generic data output from a workflow node. |\n| `node_info` | `NodeInfo`: `path` (e.g. `wf@1/child@2`), `output_for`, `message_as_output`. |\n| `long_running_tool_ids` | IDs of long-running calls. Setting these is what makes the framework treat the event as an interrupt. |\n| `branch` | Dot-separated branch path for isolating peer sub-agents' history. |\n| `isolation_scope` | Scope tag for which logical context this event belongs to. |\n| `id`, `timestamp` | Identity and time. |\n\n`NodeInfo.run_id` is a **derived property**, not a stored field: it is parsed\noff the last `name@run_id` segment of `node_info.path`. Nothing stamps it\nseparately, so a path without an `@` yields an empty run id.\n\n## Convenience kwargs\n\nThe constructor routes four shorthand kwargs onto nested fields, so these are\nequivalent to writing the nested form:\n\n| Kwarg | Lands on |\n|---|---|\n| `message=` | `content` (converted via the genai content transformer) |\n| `state=` | `actions.state_delta` |\n| `route=` | `actions.route` |\n| `node_path=` | `node_info.path` |\n\nPassing both `message` and `content` raises `ValueError`.\n\n## Methods\n\n- `get_function_calls()` / `get_function_responses()` — inherited from\n  `LlmResponse`; pull the function calls or responses out of `content`.\n- `is_final_response()` — whether this is an agent's final response.\n- `has_trailing_code_execution_result()` — whether the content ends in a code\n  execution result.\n- `message` — property aliasing `content`, with a matching setter.\n- `node_name` — the node name parsed off `node_info.path`.\n\n## State lifecycle and immutability\n\n- **Events are immutable once saved.** Never assume an event is mutated or\n  cleared after it lands in a session; resume works by reading the log\n  forward, not by rewriting it.\n- **Signals resolve by later events, not by edits.** To decide whether a\n  request is pending or resolved, look for the matching later event (the\n  function response), not a flag flipped on the original.\n- **Aborted invocations seal dangling calls.** When an invocation is cancelled\n  via `abort_signal`, the runner appends a terminal event with\n  `error_code='INVOCATION_ABORTED'` carrying synthetic `FunctionResponse`\n  parts for any unclosed `FunctionCall`s so later turns see a balanced log.\n- **Beware stateful flags on events.** Background compaction may rewrite or\n  drop aged events, so a transient status stored on one can survive or vanish\n  in ways you did not intend.\n",".agents/skills/adk-architecture/references/interface-runner.md":"# Runner\n\n`Runner` is the public entry point for executing agents and workflows. It owns\nthe invocation lifecycle: building the `InvocationContext`, draining the event\nqueue onto the session, and wiring in the artifact, session, memory and\ncredential services plus the plugin manager.\n\n`InMemoryRunner` is the batteries-included subclass that supplies in-memory\nservices; it accepts either an `agent=` or a `node=`.\n\n## Entrance methods\n\n### `run_async`\n\nThe main asynchronous entry point. Use this in production.\n\n- Yields events as they are produced; does not block concurrent calls for other\n  queries.\n- Runs event compaction after the invocation when the app has\n  `events_compaction_config` set.\n\n| Argument | Meaning |\n|---|---|\n| `user_id` | User ID of the session. |\n| `session_id` | Session ID. |\n| `invocation_id` | Set to resume an interrupted invocation. |\n| `new_message` | Message to append to the session. Optional — omit it when resuming. |\n| `state_delta` | State changes to apply to the session. |\n| `run_config` | Run config for the agent. |\n| `yield_user_message` | Yield the user-message event before agent/node events. |\n| `abort_signal` | Optional `asyncio.Event` for cooperative cancellation. Setting it halts the active invocation cleanly. |\n\n### `run`\n\nSynchronous convenience wrapper for local testing: runs the async path on a\nbackground thread and re-yields its events. Takes `user_id`, `session_id`,\n`new_message`, `state_delta` and `run_config` — no `invocation_id`, so it\ncannot resume.\n\n### `run_live`\n\nAudio/video streaming entry point, driven by a `LiveRequestQueue`\n(`from google.adk.live import LiveRequestQueue`) rather than a single\n`new_message`.\n\n### `run_debug`\n\nConvenience harness for local iteration: takes one message or a list of\nmessages, creates the session if needed, and prints the exchange (`quiet` and\n`verbose` control how much).\n\n## Execution cancellation (`abort_signal`)\n\nWhen `abort_signal` is passed to `run_async` and set during execution:\n\n- `InvocationContext.is_aborted` flips to `True` and active agent or workflow\n  tasks are cancelled cleanly without surfacing an unhandled `CancelledError` to\n  the caller.\n- `Runner._synthesize_abort_events_if_needed` appends and yields synthetic\n  abort `Event`s with `error_code='INVOCATION_ABORTED'`. When function calls are\n  dangling, it emits one abort event per `(author, branch, isolation_scope)`\n  tuple sealing those calls with synthetic `FunctionResponse(response={'error':\n  'Invocation was aborted by client.'})` parts so each response pairs with its\n  call in the issuing agent's view. If nothing is dangling, it yields a single\n  root-authored abort event instead. This preserves session log invariants so\n  that subsequent conversational turns or resumes do not fail on dangling tool\n  calls or re-run the cancelled tool.\n- `PluginManager.run_after_run_callback` still executes before `run_async`\n  exits.\n- In the FastAPI server (`/run_sse`), client HTTP disconnects automatically set\n  `abort_signal` on the active runner invocation.\n",".agents/skills/adk-architecture/references/interface-workflow.md":"# Workflow\n\nWorkflow is a graph-based orchestration node. It extends BaseNode\nand implements `_run_impl()` as a scheduling loop that drives static\ngraph nodes and tracks dynamic nodes spawned by `ctx.run_node()`.\n\n## Two kinds of child nodes\n\nWorkflow manages two kinds of child nodes through a unified scheduler (`DynamicNodeScheduler`):\n\n- **Static (graph) nodes** — declared in `edges`, compiled into a\n  `Graph`. Scanned upfront into `_LoopState.recovered_executions` by\n  `ReplayManager.scan_workflow_events(ctx)`. When scheduled by the\n  orchestration loop, `_start_node_task` checks `check_interception` on any\n  recovered execution to mark `_LoopState.replayed_nodes` and pre-populate\n  `_LoopState.runs[node_path]` (`DynamicNodeRun` with `is_static=True`), then\n  delegates to `ctx._run_node_internal` (`DynamicNodeScheduler`). Tracked in\n  `_LoopState.nodes` by node name (`NodeState`).\n- **Dynamic nodes** — spawned at runtime via `ctx.run_node()` from\n  inside a graph node's `_run_impl`. Lazily rehydrated from `ReplayManager`\n  by `DynamicNodeScheduler._rehydrate_from_events` and tracked in\n  `_LoopState.runs` by full `node_path` (`DynamicNodeRun`,\n  which holds `state: NodeState`, `output`, `task`, `transfer_to_agent`,\n  `recovered_state`, and `is_static`). Managed by `DynamicNodeScheduler`.\n\nStatic and dynamic nodes share the same `DynamicNodeScheduler`, replay\ninterception (`check_interception` / `ReplayManager`), and\n`_LoopState.interrupt_ids` set, so the Workflow sees a unified view of\nexecution and all pending interrupts.\n\n## Implementing a graph node\n\nA graph node is a regular BaseNode placed in a Workflow's edges.\nIn `_start_node_task`, the Workflow checks `check_interception` against\n`_LoopState.recovered_executions` (to record `_LoopState.replayed_nodes` and\nseed `_LoopState.runs` with `is_static=True`) and dispatches\n`ctx._run_node_internal` to `DynamicNodeScheduler`, which either returns a\nreplayed mock `Context` (`create_mock_context`) or wraps the node in a\n`NodeRunner` with a child `Context`, reading `ctx.output`, `ctx.route`, and\n`ctx.interrupt_ids` after it completes.\n\n**Output** — two paths. At most one per execution. The Workflow\nreads the output to pass downstream.\n\n```python\n# Yield (persisted immediately)\nasync def _run_impl(self, *, ctx, node_input):\n    yield compute(node_input)\n\n# ctx (deferred until node end)\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.output = compute(node_input)\n    return\n    yield\n```\n\n**Routing** — two paths. The Workflow uses the route to select\nconditional edges.\n\n```python\n# Yield (persisted immediately)\nasync def _run_impl(self, *, ctx, node_input):\n    yield Event(route='approve' if node_input > 0.8 else 'reject')\n\n# ctx (deferred until node end)\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.route = 'approve' if node_input > 0.8 else 'reject'\n    yield node_input\n```\n\n**State** — two paths. `ctx.state` deltas are flushed onto the next\nyielded Event, or a final Event at node end.\n\n```python\n# Yield (persisted immediately)\nasync def _run_impl(self, *, ctx, node_input):\n    yield Event(state={'count': 1})\n\n# ctx (flushed onto next/final Event)\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.state['count'] = 1\n    yield result\n```\n\n**Interrupts** — yield only (`ctx.interrupt_ids` is read-only). The\nWorkflow marks the node WAITING and propagates the interrupt IDs\nupward. On resume, if `rerun_on_resume=True` (default for Workflow),\nthe node is re-executed with `ctx.resume_inputs` populated.\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    if ctx.resume_inputs and 'fc-1' in ctx.resume_inputs:\n        yield f'approved: {ctx.resume_inputs[\"fc-1\"]}'\n        return\n    yield Event(long_running_tool_ids={'fc-1'})\n```\n\n## Dynamic nodes via ctx.run_node()\n\nA graph node can spawn child nodes at runtime:\n\n```python\nclass Orchestrator(BaseNode):\n    rerun_on_resume: bool = True  # required\n\n    async def _run_impl(self, *, ctx, node_input):\n        result = await ctx.run_node(some_node, input_data)\n        yield f'child returned: {result}'\n```\n\n### Requirements\n\n- The calling node **must** have `rerun_on_resume = True`. Without\n  this, the Workflow cannot re-execute the node on resume to let it\n  re-acquire its dynamic children's results.\n\n### Tracking\n\nDynamic nodes are tracked by **full node_path**, not by name alone.\nEach segment is `node_name@run_id`:\n\n```text\nwf@1/graph_node_a@1/dynamic_child@1        ← dynamic node under graph_node_a\nwf@1/graph_node_a@1/dynamic_child@1/inner@1  ← transitive dynamic node\n```\n\nThe node name comes from the node's own `name` field. The run id comes from\nthe `run_id` argument to `ctx.run_node()`, or a generated counter when that\nargument is omitted. There is no `name=` parameter on `ctx.run_node()` — pass\na node whose `name` is what you want, and pass `run_id=` to pin the suffix.\n\nEach unique `node_path` is tracked in `_LoopState.runs`\n(`dict[str, DynamicNodeRun]`). This enables:\n\n- **Dedup** — if the same path is encountered again (after resume),\n  the cached output is returned without re-execution.\n- **Resume** — if the node was interrupted, its state is\n  reconstructed from session events via `ReplayManager` and `check_interception`.\n\n### Unified interception and resume protocol (DynamicNodeScheduler)\n\n`DynamicNodeScheduler.__call__` wraps single-step node execution (`_execute_step`)\nin a sequential `transfer_to_agent` loop: if a completed agent step sets\n`child_ctx.actions.transfer_to_agent`, `__call__` resolves the target agent and\nparent context (`resolve_and_derive_transfer_context`), delegates the next step\nto the target context's owning scheduler, and only preserves `use_as_output`\nwhile `curr_parent_ctx is ctx`.\n\nWithin a single step (`_execute_step`), when replay is enabled:\n- If `node_path` is not in `_state.runs` (for recovered static nodes,\n  `_start_node_task` already inserted a `DynamicNodeRun` with `is_static=True`),\n  `_rehydrate_from_events` scans `ReplayManager` for that `node_path` and\n  populates `_state.runs[node_path]` if historical events exist.\n- If `node_path` is still not in `_state.runs`, `_check_existing_run` returns\n  `(None, False)` and `_execute_step` runs the node fresh via\n  `_run_node_internal(..., is_fresh=True)` (`NodeRunner`).\n- If `node_path` is in `_state.runs`, `_check_existing_run` awaits any in-flight\n  concurrent `run.task`, or calls `check_interception(node=curr_node,\n  recovered=run.recovered_state, current_run=run)`:\n\n1. **Same-turn dedup / failed / waiting (`current_run` present and `not current_run.is_static`)** —\n   if `current_run.state.status == COMPLETED`, returns `should_run=False` with\n   `current_run.output`, `current_run.route`, and\n   `current_run.transfer_to_agent`. If `FAILED`, returns `should_run=True`\n   with `resume_inputs=recovered.resolved_responses` so a dynamic run that\n   failed in the current turn reruns instead of consulting stale recovered\n   events. If `WAITING` with `interrupts`, returns `should_run=False` with\n   those `interrupts`. (Every static dispatch gets a fresh `run_id`, so loop\n   edges never reuse a `node_path`; static runs have `is_static=True` so\n   no-outcome static nodes fast-forward in step 7 via\n   `should_run = (current_run is not None and not current_run.is_static)`.)\n\n2. **Nested `Workflow` (`isinstance(node, Workflow)`)** — before checking\n   completion or interrupts, `check_interception` immediately returns\n   `should_run=True` with `resume_inputs=recovered.resolved_responses`. A child\n   `Workflow` never fast-forwards at the parent boundary; it always re-enters\n   `_run_impl` so its own `ReplayManager` and scheduler can fast-forward or\n   resume its internal children.\n\n3. **Waiting / Partial resume (`recovered.interrupt_ids - recovered.resolved_ids` non-empty)** —\n   - If `node.rerun_on_resume` is `True` and `recovered.resolved_ids` is\n     non-empty (**partial resume**): returns `should_run=True` with\n     `resume_inputs=recovered.resolved_responses`.\n   - Otherwise: returns `should_run=False` with `interrupts=unresolved`,\n     propagating unresolved interrupt IDs to `_state.interrupt_ids` via a\n     mock `Context` (`create_mock_context`).\n\n4. **Prior failure (`recovered.error_code is not None`)** — returns\n   `should_run=True` with `resume_inputs=recovered.resolved_responses` so a\n   node that failed in a prior turn re-executes instead of fast-forwarding with\n   `None`.\n\n5. **Fast-forward (`recovered.route`, `recovered.output`, or `recovered.transfer_to_agent` present)** —\n   non-`Workflow` nodes that recorded an output, route, or agent transfer in a\n   prior turn return `should_run=False` with the rehydrated `output`, `route`,\n   and `transfer_to_agent` via `create_mock_context` (after awaiting\n   `ReplayManager.wait_sequence`) without re-executing `NodeRunner`.\n\n6. **All interrupts resolved, no output yet (`recovered.interrupt_ids` non-empty, `unresolved` empty)** —\n   - `rerun_on_resume=False`: fast-forwards (`should_run=False`), setting\n     `output` to the single value in `recovered.resolved_responses` (when\n     `len == 1`) or `dict(recovered.resolved_responses)`.\n   - `rerun_on_resume=True`: re-executes (`should_run=True`) via\n     `_run_node_internal(..., is_fresh=False)` with\n     `resume_inputs=recovered.resolved_responses`.\n\n7. **No output, route, or interrupts in recovered events** —\n   - If `node.wait_for_output` or `node.rerun_on_resume`: returns\n     `should_run=True` with `resume_inputs=recovered.resolved_responses`.\n   - Otherwise: returns `should_run = (current_run is not None and not current_run.is_static)`\n     (static nodes that completed with `None` output fast-forward with\n     `should_run=False`, while dynamic nodes with no recorded outcome\n     re-execute with `should_run=True`).\n\n### Interrupt propagation\n\nWhen a dynamic child interrupts:\n\n1. `DynamicNodeScheduler._record_result` sets the child's `DynamicNodeRun`\n   status to WAITING and adds its interrupt IDs to\n   `_LoopState.interrupt_ids`.\n2. `ctx.run_node()` checks `child_ctx.interrupt_ids`. If non-empty,\n   it propagates them to the calling node's `ctx._interrupt_ids`\n   and raises `NodeInterruptedError`.\n3. `NodeRunner` catches `NodeInterruptedError` in `_execute_node` and\n   records the interrupt on the calling node's `Context`.\n4. The Workflow's `_handle_completion` sees the interrupt and marks\n   the graph node as WAITING.\n\nOn resume, the Workflow re-executes the graph node (because\n`rerun_on_resume=True`). The graph node calls `ctx.run_node()`\nagain, which hits the scheduler. `_rehydrate_from_events` scans session\nevents for the child's prior state into a `_ChildScanState`, and\n`check_interception` reads that scanned state, finds the resolved FR, and\neither fast-forwards with cached output or re-executes the dynamic child\nwith `resume_inputs`.\n\n### ctx.run_node() options\n\n| Argument | Effect |\n|---|---|\n| `node_input` | Data handed to the child. |\n| `use_as_output` | The child's output becomes the calling node's output. |\n| `run_id` | Pins the `@run_id` suffix on the child's node path. |\n| `use_sub_branch` | Runs the child on a sub-branch so its events are isolated. |\n| `override_branch`, `override_isolation_scope` | Replace the inherited branch / scope tag. |\n| `raise_on_wait` | Defaults to `False`. If `True`, raises `NodeInterruptedError` when a child `Workflow` or `wait_for_output=True` node finishes with `output=None` instead of returning `None`. |\n\n### Output delegation (use_as_output)\n\n`ctx.run_node(node, use_as_output=True)` makes the dynamic child's\noutput count as the calling node's output:\n\n```python\nclass Delegator(BaseNode):\n    rerun_on_resume: bool = True\n\n    async def _run_impl(self, *, ctx, node_input):\n        # child's output becomes this node's output\n        await ctx.run_node(worker, node_input, use_as_output=True)\n```\n\n- Sets `ctx._output_delegated = True` on the parent\n- NodeRunner stamps `event.node_info.output_for` with ancestor paths\n- Only one `use_as_output=True` per execution (second raises\n  `ValueError`)\n\n## Dynamic nodes from dynamic nodes (transitive)\n\nA dynamic node can itself call `ctx.run_node()`, creating a\ntransitive chain:\n\n```python\nclass Outer(BaseNode):\n    rerun_on_resume: bool = True\n\n    async def _run_impl(self, *, ctx, node_input):\n        result = await ctx.run_node(Inner(name='inner'), 'data')\n        yield result\n\nclass Inner(BaseNode):\n    rerun_on_resume: bool = True\n\n    async def _run_impl(self, *, ctx, node_input):\n        sub = await ctx.run_node(Leaf(name='leaf'), node_input)\n        yield f'inner got: {sub}'\n```\n\nThis works because:\n\n- All dynamic nodes in the subtree are tracked by the **same**\n  enclosing Workflow. The scheduler is inherited down the Context\n  tree automatically.\n- Each level gets a unique `node_path`:\n  `wf/graph_node/outer/inner/leaf`\n- Nested interrupts are correctly attributed — the scheduler\n  matches events from any descendant under a given path.\n- Only a nested **orchestration node** (another Workflow) takes over\n  scheduling. Regular nodes inherit the enclosing Workflow's scheduler.\n\n### Scoping\n\nEach Workflow has its own `DynamicNodeScheduler` and `_LoopState`.\nA nested Workflow creates a new scheduler, so dynamic nodes within\nit are scoped to that inner Workflow — not mixed with the outer\nWorkflow's state.\n\n## event_author\n\nWorkflow sets `ctx.event_author = self.name` at the start of\n`_run_impl`. This propagates to all child Contexts via NodeRunner.\nAll events emitted by children carry this author, giving the UI\nconsistent attribution.\n\nA nested Workflow overrides `event_author` with its own name, so events are\nattributed to the nearest orchestration ancestor.\n\n## Orchestration loop lifecycle\n\n```text\n_run_impl\n  ├─ SETUP\n  │    ├─ loop_state.replay_manager.scan_workflow_events(ctx)\n  │    ├─ _seed_start_triggers\n  │    └─ ctx._workflow_scheduler = DynamicNodeScheduler(state=loop_state)\n  ├─ LOOP (_run_loop):\n  │    ├─ _schedule_ready_nodes → pop triggers, dispatch via _start_node_task / ctx._run_node_internal\n  │    ├─ asyncio.wait(FIRST_COMPLETED)\n  │    ├─ _handle_completion → update state, emit checkpoint / replayed output, buffer downstream\n  │    └─ await detached dynamic tasks (loop_state.get_dynamic_tasks()) & _surface_detached_dynamic_outcome\n  ├─ _cleanup_all_tasks  (finally)\n  ├─ _collect_remaining_interrupts\n  ├─ FINALIZE: set ctx.output or ctx._interrupt_ids\n  └─ _emit_end_of_agent  (only when no interrupts remain)\n```\n\nThe event scan is unconditional: `ReplayManager` indexes progress from the\nsession on every run, whether or not the app is configured resumable.\n\nKey behaviors:\n\n- **Concurrency** — `max_concurrency` limits parallel graph nodes.\n  Dynamic nodes are excluded (they run inline, throttling would\n  deadlock).\n- **Terminal output** — nodes with no outgoing edges are terminal.\n  Their output is delegated to the Workflow's own output via\n  `output_for`. Only one terminal node may produce output.\n- **Loop edges** — a completed node can be re-triggered by a\n  downstream edge pointing back to it. Its status resets to PENDING.\n\n## Resume from session events\n\nOn every run, `Workflow` reconstructs progress from session events in three\nstages:\n\n1. **Upfront static scan (`ReplayManager.scan_workflow_events`)** — during\n   `_run_impl` SETUP, `ReplayManager` scans the invocation's events and\n   populates `loop_state.recovered_executions` (keyed by `node_name@run_id`)\n   with outputs, routes, interrupts, resolved function responses, error codes,\n   and transfers for direct static children. `Workflow` then seeds `START`\n   triggers normally (`_seed_start_triggers`).\n2. **Static node interception in `_start_node_task` and `DynamicNodeScheduler`** —\n   when `_schedule_ready_nodes` pops a trigger for a static node,\n   `_start_node_task` looks up `recovered = loop_state.recovered_executions.get(f\"{node_name}@{run_id}\")`:\n   - If `recovered` is present, `_start_node_task` calls\n     `check_interception(node=node, recovered=recovered)` to check whether the\n     run will fast-forward (`not result.should_run` and not a resolved transfer\n     interrupt), adding `node_name` to `loop_state.replayed_nodes` (so\n     checkpoint emission is skipped in `_schedule_ready_nodes` and on the\n     `COMPLETED` path of `_handle_completion`; `_handle_completion` still emits\n     a checkpoint if a replayed node comes back `WAITING`) and seeding\n     `loop_state.runs[node_path]` with\n     `DynamicNodeRun(state=NodeState(run_id=run_id), recovered_state=recovered, is_static=True)`.\n   - `_start_node_task` then dispatches `ctx._run_node_internal` to\n     `DynamicNodeScheduler._check_existing_run`, which runs\n     `check_interception(node=curr_node, recovered=run.recovered_state, current_run=run)`:\n     - **Nested `Workflow`**: `check_interception` always returns\n       `should_run=True` (with `resume_inputs=recovered.resolved_responses`),\n       so a child `Workflow` never fast-forwards at the parent level and instead\n       re-runs `_run_impl` to replay or resume its own children.\n     - **Completed non-`Workflow` node (or `rerun_on_resume=False` with all interrupts resolved)**:\n       returns `should_run=False`; `_check_existing_run` builds a mock\n       `Context` (`create_mock_context`) and awaits\n       `ReplayManager.wait_sequence` so `_handle_completion` can re-surface\n       replayed output (`_maybe_reemit_replayed_output`) and buffer downstream\n       triggers without running `NodeRunner`.\n     - **Interrupted with unresolved interrupts**: if `rerun_on_resume=True`\n       and `recovered.resolved_ids` is non-empty (**partial resume**),\n       re-executes (`should_run=True`) via `NodeRunner` with\n       `resume_inputs=recovered.resolved_responses`; otherwise stays WAITING\n       (`should_run=False` with `interrupts` populated) and records unresolved\n       IDs in `_LoopState.interrupt_ids`.\n     - **Interrupted with all interrupts resolved (`rerun_on_resume=True`) or prior failure (`error_code`)**:\n       re-executes (`should_run=True`) via `NodeRunner` with\n       `resume_inputs=recovered.resolved_responses`.\n3. **Dynamic node lazy rehydration (`_rehydrate_from_events`)** — dynamic\n   children are not scanned upfront into `recovered_executions`. When a\n   re-executing parent calls `ctx.run_node()`,\n   `DynamicNodeScheduler._execute_step` lazily populates\n   `_LoopState.runs[node_path]` via `_rehydrate_from_events` and applies the\n   same `check_interception` rules in `_check_existing_run` (with\n   `current_run=run` also deduplicating same-turn completed or waiting runs).\n\n## Key design rules for node authors\n\n1. **Set `rerun_on_resume = True`** if your node calls\n   `ctx.run_node()`. The Workflow must be able to re-execute your\n   node so it can re-acquire dynamic children's results.\n\n2. **Use deterministic names** for dynamic children. The child node's `name`\n   (plus the optional `run_id=`) determines the `node_path`, which is the\n   dedup/resume key. A name derived from a timestamp, a UUID or model output\n   produces a different path on every run, so resume never finds the prior\n   execution and the child re-runs.\n\n3. **Always `await ctx.run_node()` (or `asyncio.gather`). Never detach tasks.**\n   A dynamic child's interrupt must propagate through the awaiting caller so the\n   caller can pause and resume. If a dynamic task is left detached\n   (`asyncio.create_task(ctx.run_node(...))` left unawaited), its interrupt\n   cannot be handled by the parent and the Workflow turns it into a\n   `RuntimeError` that shuts down the workflow.\n\n4. **Yield output after all dynamic children complete.** If your\n   node calls `ctx.run_node()` and then yields, the output is\n   emitted only after all children finish. This is the expected\n   pattern.\n\n5. **Handle `NodeInterruptedError` only if you need custom logic.**\n   Normally, `ctx.run_node()` raises `NodeInterruptedError` when a\n   child interrupts. NodeRunner catches it automatically. Only\n   catch it yourself if you need to clean up or adjust state before\n   the interrupt propagates.\n\n6. **Don't set `ctx.event_author`** unless your node is an orchestration node\n   like Workflow. The Workflow sets it for you and it propagates to all\n   descendants.\n",".agents/skills/adk-debug/SKILL.md":"---\nname: adk-debug\ndescription: >-\n  Diagnoses misbehaving ADK agents by inspecting sessions, events, tool calls,\n  and the exact request that reached the model. Covers the `adk run` CLI and the\n  `adk web` dev server with its session, trace, and debug HTTP endpoints. Use\n  when an agent returns the wrong answer, ignores a tool or swallows a tool\n  error, hangs, loops, emits raw JSON instead of calling tools, is not\n  discovered by `adk web`, when a sub-agent cannot see the parent conversation,\n  or when you need the LLM request/response, token counts, or logs for a run.\n  Don't use for how ADK is designed internally (use `adk-architecture`), for\n  building a new agent or workflow (use `adk-agent-builder`), for environment or\n  dependency setup failures (use `adk-setup`), or for lint and style nits (use\n  `adk-style`).\n---\n\n# Debugging ADK agents\n\nTwo entry points. Default to `adk run`: one process, no server, and `--jsonl`\noutput that pipes straight into `grep` or `python3`. Switch to `adk web` when\nyou need the browser UI, a persisted session you can click through, or the\ntrace endpoints that expose the exact LLM request.\n\n## First moves\n\n1. Reproduce headlessly: `adk run --jsonl {agent_dir} \"{query}\"`. Without\n   `--jsonl`, `adk run` prints only text parts — tool calls and tool errors are\n   invisible.\n2. Read the log file. `adk run` writes to `/tmp/agents_log/agent.latest.log` and\n   nothing to the terminal; `adk web` does the opposite. See\n   [logs-and-traces.md](references/logs-and-traces.md).\n3. Match the symptom in [failure-modes.md](references/failure-modes.md) before\n   reading source — most reports are one of a handful of known shapes.\n4. If the text is fine but the routing is not, dump the events and read\n   `author`, `branch`, `nodeInfo.path`, and `actions` —\n   [event-flow.md](references/event-flow.md).\n5. If the model itself misbehaved, read what it actually received from the\n   `call_llm` span rather than guessing from the agent definition —\n   [logs-and-traces.md](references/logs-and-traces.md).\n\n## References\n\n- [cli-run.md](references/cli-run.md) — `adk run` flags, the JSONL event shape,\n  multi-turn and human-in-the-loop resume, exit codes, driving a `Runner` from\n  Python.\n- [web-api.md](references/web-api.md) — starting `adk web`, listing and reading\n  sessions over HTTP, posting test messages to `/run_sse`.\n- [logs-and-traces.md](references/logs-and-traces.md) — log levels and where\n  each command writes them, the trace endpoints, span attributes, and the env\n  vars that control whether prompts appear in spans.\n- [failure-modes.md](references/failure-modes.md) — ADK-specific symptoms with\n  the cause and a concrete check for each.\n- [event-flow.md](references/event-flow.md) — how an invocation becomes events,\n  callback order, the event fields that matter, and where each stage lives in\n  the source.\n\n## Ground rules\n\n- Leave sessions in place when you finish. The user may still want to open them\n  in the web UI, and `adk web` has no undelete.\n- Delete any throwaway agent you created for a repro, unless the user asked to\n  keep it.\n- Reach for a unit test in `tests/unittests/` when the bug is inside one\n  component, and for a sample under `contributing/samples/` (see\n  `adk-sample-creator`) when it only reproduces with runner, agent, and workflow\n  wired together.\n",".agents/skills/adk-debug/references/cli-run.md":"# Debugging with `adk run`\n\n`adk run {agent_dir}` with a trailing query argument runs one turn and exits;\nwithout a query it drops into an interactive prompt. Prefer the query form —\nit needs no human in the loop and composes with shell tooling.\n\n```bash\nadk run --jsonl {agent_dir} \"{query}\"\nadk run --jsonl --in_memory {agent_dir} \"{query}\"    # no persisted session\n```\n\n## Flags\n\nFlag | Default | Why you'd use it\n--- | --- | ---\n`--jsonl` | off | One JSON object per event on stdout. Without it only text parts are printed, so tool calls, tool errors, and actions are invisible.\n`--in_memory` | off | Skip the local session store, so repeated runs cannot contaminate each other.\n`--session_id {id}` | new session | In query mode, reuse that session (creating it if absent) — this is how you carry state across separate `adk run` invocations. In interactive mode it only names the file `--save_session` writes.\n`--state '{json}'` | none | Seed session state for a run that only misbehaves with particular state.\n`--replay {file.json}` | none | Replay a saved state + query list into a fresh session. Mutually exclusive with a query argument.\n`--resume {file.json}` | none | Reopen a session saved by `--save_session` and keep interacting (interactive mode only).\n`--timeout 30s` | none | Bound a hanging turn instead of waiting forever.\n`-v` / `--log_level DEBUG` | `INFO` | Raise log verbosity. Output goes to the log file, not the terminal — see `references/logs-and-traces.md`.\n`--default_llm_model {model}` | none | Override the model for agents that do not set one, e.g. to test whether the model is the problem.\n\nFull list: `adk run --help`.\n\n## JSONL event shape\n\nEach line is `Event.model_dump(mode='json', by_alias=True, exclude_none=True)`,\nso keys are camelCase (`invocationId`, `functionCall`, `longRunningToolIds`),\nwith `session_id` and `node_path` injected and `author` first. Empty `actions`\nentries are dropped, so an absent `actions` key means \"no actions\", not\n\"unknown\".\n\nIn `--jsonl` mode stdout is pure JSONL; the human-readable session banner only\nprints when `--jsonl` is off, and goes to stderr either way. So this is safe:\n\n```bash\nadk run --jsonl {agent_dir} \"{query}\" 2>/dev/null > /tmp/events.jsonl\nhead -1 /tmp/events.jsonl | python3 -m json.tool     # inspect the real shape\n```\n\nRead one event before writing a parser — the schema changes. Then filter on\nwhatever you actually saw, for example every tool call:\n\n```python\nimport json\n\nfor line in open(\"/tmp/events.jsonl\"):\n  event = json.loads(line)\n  for part in (event.get(\"content\") or {}).get(\"parts\", []):\n    if \"functionCall\" in part:\n      print(event[\"author\"], part[\"functionCall\"][\"name\"], part[\"functionCall\"].get(\"args\"))\n```\n\n## Exit codes\n\nCode | Meaning\n--- | ---\n`0` | The turn completed.\n`1` | Error — bad `--state` JSON, both a query and `--replay`, no query and no stdin, timeout, or an exception during the run.\n`2` | Paused. The run emitted an event with `longRunningToolIds`, i.e. a human-in-the-loop tool is waiting.\n\nOn exit 2 the run prints the session id. Resume by re-running with that\n`--session_id` and the answer as the query — ADK maps the query onto the\npending `adk_request_confirmation` / `adk_request_input` function response\nautomatically, so do not try to hand-craft a `FunctionResponse`. For a\nconfirmation, a plain `yes`/`no` works; pass a JSON object to supply a custom\npayload.\n\n## Driving a Runner from Python\n\nUse this when you need to assert on events rather than eyeball them. Two\nthings that bite:\n\n- `new_message` must be a `types.Content`, not a string.\n- `Runner` takes keyword arguments only, and `auto_create_session` defaults to\n  `False`, so the session must exist before you run.\n\n```python\nimport asyncio\n\nfrom google.adk import Agent\nfrom google.adk.runners import InMemoryRunner\nfrom google.genai import types\n\nagent = Agent(name=\"test\", model=\"gemini-2.5-flash\", instruction=\"...\")\nrunner = InMemoryRunner(agent=agent, app_name=\"test\")\n\n\nasync def main():\n  session = await runner.session_service.create_session(\n      app_name=\"test\", user_id=\"u\"\n  )\n  async for event in runner.run_async(\n      user_id=\"u\",\n      session_id=session.id,\n      new_message=types.Content(role=\"user\", parts=[types.Part(text=\"hello\")]),\n  ):\n    print(event.author, event.content)\n    if event.actions.transfer_to_agent:\n      print(\"  -> transfer to\", event.actions.transfer_to_agent)\n    if event.output is not None:\n      print(\"  -> output:\", event.output)\n\n\nasyncio.run(main())\n```\n\n`InMemorySessionService.create_session_sync` still exists but logs a\ndeprecation warning; use the async `create_session`.\n\nTo print events the way the CLI does, without reimplementing the formatting:\n\n```python\nfrom google.adk.utils._debug_output import print_event\n\nprint_event(event)                # text parts only\nprint_event(event, verbose=True)  # plus tool calls, tool results, code, blobs\n```\n\n`verbose` is keyword-only. Source: `src/google/adk/utils/_debug_output.py`.\n",".agents/skills/adk-debug/references/event-flow.md":"# Event flow and where to look\n\n## One user message becomes events\n\n```text\nRunner.run_async()\n  Runner._exec_with_plugin()          # plugin hooks + persisting events\n    agent.run_async()                 # BaseAgent: before/after agent callbacks\n      LlmAgent._run_async_impl()      # yields events\n        BaseLlmFlow.run_async()\n          SingleFlow | AutoFlow       # AutoFlow adds agent transfer\n            call_llm                  # request build + model call\n            handle_function_calls_async()   # tool dispatch\n```\n\n`LlmAgent._llm_flow` picks `SingleFlow` only when\n`disallow_transfer_to_parent` and `disallow_transfer_to_peers` are both set and\nthe agent has no sub-agents; otherwise it is `AutoFlow`. If an agent refuses to\ntransfer, check those two fields before suspecting the prompt.\n\nWorkflow-graph execution takes a different path: `LlmAgent._run_impl` runs the\nagent as a node via `src/google/adk/workflow/`.\n\n## Callback order\n\nBoth the plugin manager and the agent get a turn, plugins first:\n\nPoint | Plugin manager | Agent\n--- | --- | ---\nBefore model | `run_before_model_callback` | `canonical_before_model_callbacks`\nAfter model | `run_after_model_callback` | `canonical_after_model_callbacks`\nModel error | `run_on_model_error_callback` | `canonical_on_model_error_callbacks`\nBefore tool | `run_before_tool_callback` | `canonical_before_tool_callbacks`\nAfter tool | `run_after_tool_callback` | `canonical_after_tool_callbacks`\nTool error | `run_on_tool_error_callback` | `canonical_on_tool_error_callbacks`\n\nThe manager also exposes run-level hooks with no agent counterpart:\n`run_on_user_message_callback`, `run_before_run_callback`,\n`run_after_run_callback`, `run_on_event_callback`, and the agent/run error\nhooks. Source: `src/google/adk/plugins/plugin_manager.py`.\n\nA plugin callback that returns a value short-circuits the step, so an agent\nthat \"ignores\" its own callback is often a plugin that already answered.\n\n## Event fields worth reading\n\n`Event` serializes with a camelCase alias generator, so JSON from the HTTP API\nor `adk run --jsonl` uses `invocationId`, `functionCall`, `nodeInfo`,\n`longRunningToolIds`, while Python attribute access stays snake_case.\n\nField | Why it matters\n--- | ---\n`author` | `user` or the agent name — the fastest way to see which agent actually spoke.\n`branch` | `agent_1.agent_2` path. Drives which history the agent can see.\n`nodeInfo.path` | Node path inside a workflow, e.g. `wf/A@1/B@1`.\n`content.parts` | `text`, `functionCall`, `functionResponse` — a turn with no `text` part is not a bug, it is a tool round trip.\n`output` | Generic node output value. Absent on ordinary chat events.\n`longRunningToolIds` | Present means the run is parked on a human-in-the-loop tool.\n`actions.transferToAgent` | The agent handed control to a named agent.\n`actions.escalate` | The agent gave up to its parent, typically ending a loop.\n`actions.endOfAgent` | The agent finished.\n`actions.stateDelta` / `artifactDelta` | State and artifact writes made by this event.\n\n`isolationScope` also appears on task-agent events; it is internal, so read it\nfor orientation but do not build on it. Source:\n`src/google/adk/events/event.py`, `src/google/adk/events/event_actions.py`.\n\n## Source map\n\nArea | File\n--- | ---\nRunner and event persistence | `src/google/adk/runners.py`\nFlow driver, LLM call, callbacks | `src/google/adk/flows/llm_flows/base_llm_flow.py`\nRequest assembly (model, tools, schema) | `src/google/adk/flows/llm_flows/basic.py`\nWhich history reaches the model | `src/google/adk/flows/llm_flows/contents.py`\nTool dispatch and tool errors | `src/google/adk/flows/llm_flows/functions.py`\nAgent transfer | `src/google/adk/flows/llm_flows/agent_transfer.py`\nAgent config and validation | `src/google/adk/agents/llm_agent.py`\nInvocation state and call limits | `src/google/adk/agents/invocation_context.py`\nTask agents | `src/google/adk/agents/llm/task/`\nGraph orchestration | `src/google/adk/workflow/`\nEvent model | `src/google/adk/events/event.py`\nSession services | `src/google/adk/sessions/`\nPlugin hook ordering | `src/google/adk/plugins/plugin_manager.py`\nHTTP API (production-safe routes) | `src/google/adk/cli/api_server.py`\nDev-only routes, including traces | `src/google/adk/cli/dev_server.py`\nAgent discovery | `src/google/adk/cli/utils/agent_loader.py`\nLog setup | `src/google/adk/cli/utils/logs.py`\nTracing and span attributes | `src/google/adk/telemetry/tracing.py`\nEvent printer used by the CLI | `src/google/adk/utils/_debug_output.py`\n\n`src/google/adk/cli/adk_web_server.py` is a deprecated shim; `AdkWebServer` now\njust subclasses `DevServer`. Read `api_server.py` / `dev_server.py` instead.\n",".agents/skills/adk-debug/references/failure-modes.md":"# ADK failure modes\n\nMatch the symptom first; each entry names the mechanism and a check you can\nactually run.\n\n## The agent emits raw JSON instead of calling tools\n\n`output_schema` puts the model into controlled generation: it sets\n`config.response_schema` and `config.response_mime_type =\n\"application/json\"` on the request, and a model in JSON mode returns JSON, not\ntool calls.\n\nCheck the `call_llm` span's `gcp.vertex.agent.llm_request` for\n`response_mime_type` (`references/logs-and-traces.md`). ADK only\napplies the schema when the agent has no tools, or when the model can do\nschemas and tools together — so the opposite symptom, a schema that seems to be\nignored, means you landed on the other branch.\n\nSource: `src/google/adk/flows/llm_flows/basic.py`,\n`src/google/adk/utils/output_schema_utils.py`.\n\n## `ValueError` when constructing the agent\n\nThree messages come from the same validator, all meaning \"you set this on\n`generate_content_config` instead of on the agent\":\n\n- `All tools must be set via LlmAgent.tools.`\n- `System instruction must be set via LlmAgent.instruction.`\n- `Response schema must be set via LlmAgent.output_schema.`\n\nSource: `LlmAgent.validate_generate_content_config` in\n`src/google/adk/agents/llm_agent.py`.\n\n## `LlmCallsLimitExceededError: Max number of llm calls limit of N exceeded`\n\n`run_config.max_llm_calls` was hit. Treat the limit as a loop detector before\nraising it — dump the events and look for the same tool being called with the\nsame arguments turn after turn. Source:\n`src/google/adk/agents/invocation_context.py`.\n\n## A tool \"fails\" but the agent carries on\n\nTool failures are converted into a function response carrying the error, so the\nmodel sees a result and keeps going. Look for a `functionResponse` whose payload\nhas an `error` key. `FunctionTool` produces the same shape for two non-exception\ncases: missing mandatory arguments, and a confirmation-required tool that was\nnot confirmed or was rejected.\n\nTo intervene, register `on_tool_error_callback` on a plugin or\n`on_tool_error_callbacks` on the agent. Source:\n`src/google/adk/flows/llm_flows/functions.py`,\n`src/google/adk/tools/function_tool.py`.\n\n## `adk web` does not list the agent, or returns 404\n\n```bash\ncurl -s http://localhost:8000/list-apps | python3 -m json.tool\n```\n\nThe loader accepts four layouts under `{agents_dir}`, checking for a top-level\n`app` before `root_agent`:\n\n```text\n{name}/agent.py          # defines root_agent (or app)\n{name}.py                # defines root_agent (or app)\n{name}/__init__.py       # defines root_agent (or app) in the package\n{name}/root_agent.yaml   # config-defined agent\n```\n\n`__init__.py` does not need `from . import agent` — the loader imports the\n`agent` submodule itself. Pointing `adk web` at a directory that itself contains\n`agent.py` or `root_agent.yaml` runs that single agent instead of treating the\ndirectory as a collection. Source:\n`src/google/adk/cli/utils/agent_loader.py`.\n\n## A sub-agent cannot see the parent conversation\n\nEvents carry a `branch` (`agent_1.agent_2.agent_3`), and the content builder\ndrops events that do not belong to the current agent's branch — that isolation\nis deliberate, so peers do not read each other's history. Delegated task agents\nare isolated further by `isolation_scope`.\n\nThere is no flag to switch it off. Put whatever the sub-agent needs into the\ndelegation input; the sub-agent's `description` is what steers the parent into\nincluding it. Source: `_is_event_belongs_to_branch` in\n`src/google/adk/flows/llm_flows/contents.py`.\n\n## The whole agent stalls while one tool runs\n\nA synchronous tool function is awaited inline on the event loop, so anything\nblocking inside it — a `requests` call, `time.sleep`, a large file read —\nfreezes the entire run, not just that tool.\n\nMake the callable `async`, or hand synchronous `FunctionTool` and `FunctionNode`\ncallables (and all tools in live mode) to a thread pool:\n\n```python\nfrom google.adk.agents.run_config import RunConfig, ToolThreadPoolConfig\n\nrun_config = RunConfig(tool_thread_pool_config=ToolThreadPoolConfig())  # 4 workers\n```\n\nSource: `_resolve_sync_callable_runner` in\n`src/google/adk/utils/_sync_runner.py`, `FunctionTool._invoke_callable` in\n`src/google/adk/tools/function_tool.py`, and `_call_tool_in_thread_pool` in\n`src/google/adk/flows/llm_flows/tools/_thread_pool.py`.\n\n## The run stops early and nothing looks wrong\n\n`adk run` exits 2 when an event carries `longRunningToolIds`: a\nhuman-in-the-loop tool is waiting for an answer. See\n`references/cli-run.md` for how to resume.\n\n## The answer is cut off, empty, or blocked\n\nRead `gen_ai.response.finish_reasons` on the `call_llm` span rather than\ninferring from the text — `max_tokens` means raise `max_output_tokens`,\n`safety` and `recitation` mean the model refused. See\n`references/logs-and-traces.md`.\n\n## The run ends with `error_code=\"INVOCATION_ABORTED\"`\n\n`abort_signal` was set on `runner.run_async`, or the HTTP client disconnected\nfrom `/run_sse` before the stream finished. The runner synthesizes a terminal\nevent with `error_code=\"INVOCATION_ABORTED\"` and seals any in-flight\n`FunctionCall` with a `FunctionResponse` carrying `{\"error\": \"Invocation was\naborted by client.\"}` so the session remains valid for the next turn. Source: `Runner._synthesize_abort_events_if_needed` in\n`src/google/adk/runners.py`, `src/google/adk/cli/api_server.py`.\n",".agents/skills/adk-debug/references/logs-and-traces.md":"# Logs and traces\n\n## Where the logs actually go\n\nThis differs per command and is the most common reason \"I turned on `-v` and\nsaw nothing\".\n\nCommand | Destination\n--- | ---\n`adk run` | `{tempdir}/agents_log/agent.{timestamp}.log`, i.e. `/tmp/agents_log/...` on Linux, with an `agent.latest.log` symlink. It clears the root logger's handlers, so **nothing** is logged to the terminal.\n`adk web`, `adk api_server`, `adk eval`, everything else | stderr, via `logging.basicConfig`. No log file is created.\n\n```bash\nadk run -v {agent_dir} \"{query}\"\ntail -F /tmp/agents_log/agent.latest.log\n```\n\nFor `adk web`, tee stderr into a file so both you and the user can read it:\n\n```bash\nadk web -v {agents_dir} 2>&1 | tee {readable_path}/adk_web.log\n```\n\n`-v` is a shortcut for `--log_level DEBUG`; the levels are `DEBUG`, `INFO`,\n`WARNING`, `ERROR`, `CRITICAL`. ADK's own records go to the `google_adk`\nlogger, so filter with `grep google_adk` or raise only that logger in-process.\nSetup lives in `src/google/adk/cli/utils/logs.py`.\n\n## Trace endpoints\n\nOnly `adk web` registers these. `adk api_server` runs the production-safe\n`ApiServer`, which has no `/dev/...` routes, so trace lookups there 404.\n\n```bash\n# Every span for a session\ncurl -s http://localhost:8000/dev/apps/{app_name}/debug/trace/session/{session_id} \\\n  | python3 -m json.tool\n\n# The single trace recorded against one event id\ncurl -s http://localhost:8000/dev/apps/{app_name}/debug/trace/{event_id} \\\n  | python3 -m json.tool\n```\n\nThe session response is a list of spans, each with `name`, `span_id`,\n`trace_id`, `parent_span_id`, `start_time`, `end_time`, and `attributes`.\nSpans are kept in memory by the running server, so restarting it loses them.\n\nSpan name | What it covers\n--- | ---\n`call_llm` | One model call, including the `before_model` / `after_model` callbacks.\n`execute_tool (merged)` | A batch of tool calls dispatched from one model response.\n`generate_content {model}` | The underlying GenAI SDK call, when the OTel GenAI instrumentation is active.\n\n## Reading what the model actually received\n\nPull the `call_llm` spans and decode `gcp.vertex.agent.llm_request` — it is a\nJSON *string* holding `contents`, `config` (tools, `response_schema`,\n`response_mime_type`, `system_instruction`), and `model`. This is the ground\ntruth for \"why did the model do that\": compare it against what you believe the\nagent is configured to send.\n\nAttribute | Meaning\n--- | ---\n`gcp.vertex.agent.llm_request` | Full request as a JSON string.\n`gcp.vertex.agent.llm_response` | Full response as a JSON string.\n`gcp.vertex.agent.tool_call_args` / `.tool_response` | Tool arguments and result.\n`gcp.vertex.agent.event_id` | Correlates the span with an event in the session.\n`gcp.vertex.agent.invocation_id` / `.session_id` | Correlates spans across one turn.\n`gen_ai.request.model` | Model name as sent.\n`gen_ai.usage.input_tokens` / `.output_tokens` | Token counts — check these before blaming a prompt for being ignored.\n`gen_ai.response.finish_reasons` | List of lowercased reasons, e.g. `[\"max_tokens\"]` for a truncated answer or `[\"safety\"]` for a filtered one.\n\nIf the content-bearing attributes come back as `\"{}\"`, content capture is off —\nsee the env vars below, not a bug in the agent.\n\n## Environment variables\n\nVariable | Effect\n--- | ---\n`ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS` | Whether prompts and responses are written onto the legacy `gcp.vertex.agent.*` span attributes. Defaults to `true`; set `false` to strip content.\n`OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` | OTel-spec content capture for the GenAI semantic-convention spans and log records.\n`ADK_TELEMETRY_SCHEMA_VERSION_OPT_IN` | Pins the telemetry schema to `1` (default off Agent Engine) or `2`. Under `2` the `invocation` span becomes `invoke_workflow` and `call_llm` goes away, so check this first if the span names above are missing.\n`GOOGLE_CLOUD_PROJECT` | Required by `adk web --trace_to_cloud`; without it the server logs a warning and exports nothing.\n\n`--trace_to_cloud` only exports traces. `--otel_to_cloud` is the newer flag and\ncovers Cloud Trace plus Cloud Logging; `adk deploy agent_engine` already warns\nthat `--trace_to_cloud` is being replaced by it.\n\nSource: `src/google/adk/telemetry/tracing.py`,\n`src/google/adk/telemetry/context.py`,\n`src/google/adk/telemetry/_schema_version.py`.\n",".agents/skills/adk-debug/references/web-api.md":"# Debugging with `adk web`\n\n`adk web` is a FastAPI server: a browser UI at `http://localhost:8000/dev-ui/`\nplus the HTTP API below. Reach for it when you need to click through a\npersisted session, or when you need the trace endpoints\n(`references/logs-and-traces.md`).\n\n## Starting the server\n\nCheck for an existing server before starting your own — a second one will fail\nto bind port 8000, and the user may already have one with the sessions you care\nabout:\n\n```bash\ncurl -s http://localhost:8000/health     # {\"status\":\"ok\"} if one is running\n```\n\nIf none is running, start it in the background and shut it down when you are\ndone:\n\n```bash\nadk web {agents_dir}                     # http://127.0.0.1:8000\nadk web -v --reload_agents {agents_dir}\n```\n\n`{agents_dir}` is a directory of agent subdirectories, or a single agent folder\n(one containing `agent.py` or `root_agent.yaml`). It defaults to the current\ndirectory.\n\nFlag | Default | Note\n--- | --- | ---\n`--port` | `8000` | Use a second port to run two servers side by side.\n`--host` | `127.0.0.1` | Endpoints are unauthenticated; keep it on loopback.\n`--reload_agents` | off | Re-import agent modules when their files change. This is the one you want while editing an agent.\n`--reload` | **on** | Uvicorn's own source autoreload. Pass `--no-reload` when a restart mid-run is confusing you.\n`-v` / `--log_level` | `INFO` | Logs go to the terminal, not to a file — see `references/logs-and-traces.md`.\n\n`adk api_server` takes the same flags but serves only the production-safe\nroutes — no UI and no `/dev/...` debug or trace endpoints. Use `adk web` when\ndebugging.\n\n## Inspecting sessions\n\n```bash\ncurl -s http://localhost:8000/list-apps | python3 -m json.tool\n\ncurl -s http://localhost:8000/apps/{app_name}/users/{user_id}/sessions \\\n  | python3 -m json.tool\n\ncurl -s http://localhost:8000/apps/{app_name}/users/{user_id}/sessions/{session_id} \\\n  | python3 -m json.tool\n```\n\nThe session response holds the full event list. Fetch the raw JSON and write a\nsummarizer against the structure you actually see rather than a remembered\nschema. Fields worth pulling per event: `author`, `branch`, `nodeInfo.path`,\n`content.parts` (`text`, `functionCall`, `functionResponse`), `output`, and\n`actions` (`transferToAgent`, `escalate`, `endOfAgent`). Keys are camelCase.\n\n`DELETE .../sessions/{session_id}` exists; do not use it to tidy up after\nyourself, because the user may still want the session in the UI.\n\n## Sending a test message\n\nCreate a session, then post one turn. `/run` returns the whole event list as\nJSON, which is far easier to assert on than a stream:\n\n```bash\nSESSION=$(curl -s -X POST http://localhost:8000/apps/{app_name}/users/test/sessions \\\n  -H \"Content-Type: application/json\" -d '{}' \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)['id'])\")\n\ncurl -s -X POST http://localhost:8000/run \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"app_name\\\":\\\"{app_name}\\\",\\\"user_id\\\":\\\"test\\\",\\\"session_id\\\":\\\"$SESSION\\\",\n       \\\"new_message\\\":{\\\"role\\\":\\\"user\\\",\\\"parts\\\":[{\\\"text\\\":\\\"{query}\\\"}]}}\" \\\n  | python3 -m json.tool\n```\n\nUse `/run_sse` with `\"streaming\":true` and `curl -N` only when the bug is in\nstreaming itself — partial events, chunk ordering, or a stream that never\nterminates.\n\nA `POST` to a session id that does not exist returns 404 rather than creating\nit, so create the session first. Supply your own id by passing\n`{\"session_id\": \"...\"}` in the create body when you want a stable id across\nruns.\n",".agents/skills/adk-git/SKILL.md":"---\nname: adk-git\ndescription: >-\n  Writes commit messages and pull request descriptions for the adk-python\n  repository: Conventional Commits types and scopes, subject lines that say why\n  a change was made, and the linked-issue and testing-plan sections the PR\n  template requires. Use when writing or rewording a commit message, squashing\n  commits before a pull request, drafting a PR description, or checking that a\n  change is shaped to land. Don't use for generic git mechanics such as\n  rebasing, resolving conflicts, cherry-picking, or branch surgery; those need\n  no skill. Don't use to judge the content of a change (use adk-review) or for\n  code style and naming (use adk-style).\n---\n\n# Commit and Pull Request Conventions\n\n## Commit message format\n\nConventional Commits:\n\n```text\n<type>(<scope>): <description>\n```\n\nThe type decides where the commit lands in `CHANGELOG.md`. `release-please`\ngenerates the changelog from merged commit subjects, so the wrong type either\nfiles the change under the wrong heading or drops it from the release notes\nentirely.\n\n| Type                                               | Changelog section        |\n| -------------------------------------------------- | ------------------------ |\n| `feat`                                             | Features                 |\n| `fix`                                              | Bug Fixes                |\n| `perf`                                             | Performance Improvements |\n| `docs`                                             | Documentation            |\n| `refactor`, `test`, `build`, `ci`, `style`, `chore` | hidden, no entry         |\n\nThe mapping lives in `.github/release-please-config.json`. A type that is not\nlisted there produces no changelog entry at all.\n\nScope is optional. Use a short module name with no underscores\n(`fix(cli):`, `feat(a2a):`, `fix(sessions):`) or leave it off.\n\n## Subject line\n\nSay why the change exists, not which lines moved. A reviewer who reads only the\nsubject should understand the motivation.\n\n| Write                                                        | Not                                                            |\n| ------------------------------------------------------------ | -------------------------------------------------------------- |\n| `fix(sessions): prevent duplicate events when resuming HITL` | `fix(sessions): check interrupt_id before appending`           |\n| `feat(workflow): support parallel tool execution`            | `feat(workflow): add asyncio.gather call in execute_tools_node` |\n| `refactor: make graph public for dev UI serialization`       | `refactor: make graph a public field on Workflow`              |\n\nRules:\n\n1. Imperative mood: `add`, not `added`.\n2. Lowercase the first word after the colon. `release-please` copies the\n   subject into `CHANGELOG.md` verbatim, and the great majority of merged\n   commits are lowercase, so capitalizing makes one line stand out.\n3. No trailing period.\n4. Keep the subject under about 72 characters. Nothing enforces this, but each\n   commit renders as one changelog line.\n5. Reference the issue in the body, not the subject: `Fixes #1234` or\n   `Closes #1234`, or the full issue URL when the issue lives in another\n   repository.\n\nSelf-check: read the subject back and ask whether it says *why* someone made\nthe change. If it only names the edit, rewrite it.\n\n## Commit body\n\nAdd a blank line, then a short concrete explanation. For a feature, show the\nnew capability or a usage line. For a fix, say what caused the failure and how\nthe change addresses it.\n\n```text\nfeat(workflow): support JSON string parsing in schema validation\n\nParse JSON strings into dicts or Pydantic models when input_schema or\noutput_schema is defined on a node.\n```\n\n```text\nfix(sessions): prevent duplicate events when resuming HITL\n\ninterrupt_id was not checked before appending, so resuming twice appended the\nsame event twice. Ignore interrupts that were already processed.\n\nFixes #1234\n```\n\n## Before committing\n\n`pre-commit` reformats and checks staged files, and the same hooks run again in\nCI on every pull request, so a commit made with hooks skipped fails there.\n\n```bash\npre-commit install                  # once per clone\npre-commit run --files {paths}      # check only what changed\n```\n\nThe hooks include `isort`, `pyink`, `addlicense`, `mdformat`, `ruff`,\n`codespell`, and repository-local compliance checks; see\n`.pre-commit-config.yaml`. If `pre-commit` is not installed, point the user at\nthe `adk-setup` skill rather than committing unformatted code.\n\n## Pull requests\n\n- Every PR except a small documentation or typo fix needs a linked issue.\n  Put `Closes: #{issue_number}` in the PR description, or describe the problem\n  and solution inline following the issue templates.\n- Fill in the Testing Plan section of `.github/pull_request_template.md`,\n  including a summary of passing `pytest` results.\n- Do not merge on GitHub. The `Do Not Merge on GitHub` check fails on every PR\n  to `main` by design; a maintainer lands the change and it is synced back to\n  the repository. GitHub then shows the PR as closed with a `merged` label\n  rather than merged, and the landed commit carries the original authorship.\n  That red check is expected and is not something to fix.\n",".agents/skills/adk-review/SKILL.md":"---\nname: adk-review\ndescription: >-\n  Reviews the uncommitted changes in an adk-python working tree and reports\n  correctness, design, public-API stability, test, sample and documentation\n  gaps as a prioritized findings report, fixing them only when asked. Use when\n  the user asks to review local changes, wants a self-review before opening a\n  pull request, asks whether a change breaks the public API or needs tests,\n  samples or docs, or asks what is wrong with the current diff. Required for\n  changes to public APIs, core architecture (Runner, Workflow, BaseNode), new\n  features and major refactors. Don't use for a single style nit (use\n  adk-style), for diagnosing a failing test or a misbehaving agent at runtime\n  (use adk-debug), or for wording a commit message or PR description (use\n  adk-git).\n---\n\n# ADK Change Reviewer\n\nReview the working-tree diff against the seven dimensions below, report what is\nwrong, and stop. Fix only what the user then asks you to fix.\n\n## Workflow\n\n1. `git status` and `git diff` (add `--staged` for staged work) to get the exact\n   set of added, modified, and deleted files.\n2. Review the diff file by file against the checklist.\n3. Emit the report in the format below.\n4. Stop. Do not edit any file, and do not offer to. Wait for the user to ask.\n5. If the user asks for fixes, apply them, then re-run the affected tests\n   (`pytest tests/unittests/{path}`) and `pre-commit run --files {paths}`.\n\nStep 4 is the part that is easy to get wrong: an unrequested fix buries the\nfindings the user asked for and mixes review output with new, unreviewed edits.\n\n## Checklist\n\n### 1. Correctness\n\n- **Types**: no new `mypy` errors. CI diffs `mypy` output against the base\n  branch and fails only on *newly introduced* errors, so a pre-existing error in\n  a file you touched is not a blocker but a new one is.\n- **Imports**: no circular imports; absolute imports where the module already\n  uses them.\n- **Exceptions**: no bare `except:`; catch a specific type and log with enough\n  context to identify the caller.\n- **Type discrimination**: check an object's type before reading a\n  type-specific attribute (for example confirm a node is an `LlmAgent` before\n  inspecting `mode`), so an unexpected node type raises nothing.\n- **Boundaries**: `None`, empty collections, zero, and empty strings are\n  handled by validation or a fallback default.\n- **Preconditions**: state invariants are checked before the core logic runs.\n\n### 2. Design\n\n- **Complexity**: functions or classes that would be clearer split up.\n- **Coupling**: high cohesion, low coupling; no anti-patterns introduced.\n- **Performance**: redundant computation, repeated I/O in a loop, or\n  allocations that scale with input where they need not.\n- **Security**: inputs validated, sensitive data not logged, no injection,\n  resource exhaustion, or exposure of internal state.\n\n### 3. Style\n\nCross-reference the diff against the `adk-style` skill rather than restating\nits rules here: visibility and `_` prefixes, typing, Pydantic v2 patterns, lazy\nlogging, imports, async, and file organization.\n\nConfirm the changed files pass `pre-commit run --files {paths}`.\n\n### 4. Architecture and unintended outcomes\n\n- **Public API stability**: does the change modify, remove, or narrow a\n  public interface, class, method, argument list, or CLI surface under\n  `src/google/adk/`? A breaking change needs a deprecation cycle first, because\n  the package is released under Semantic Versioning and users pin minor\n  versions.\n- **Session, Artifact, and Memory service changes**: adding any new field to\n  `Session`, `ArtifactVersion`, `MemoryEntry`, or to `BaseSessionService`,\n  `BaseArtifactService`, `BaseMemoryService` and their schemas is a **breaking\n  change** (even if optional or defaulted), because existing downstream storage\n  subclasses and external backends will not persist or deserialize unknown\n  fields, causing silent state loss upon reload.\n- **Execution and resumption**: changes to workflows, nodes, or state must stay\n  compatible with the event execution lifecycle and with session resumption\n  (human-in-the-loop steps and checkpoints). See the `adk-architecture` skill.\n- **Concurrency and lifetime**: no race conditions; plugins, exporters, and\n  connections are closed on every path, including the error path.\n- **Database and runtime DDL safety**: runtime startup paths (e.g.,\n  `DatabaseSessionService.prepare_tables()` and\n  `_ensure_schema_indexes_exist()`) must never execute destructive DDL such as\n  `DROP INDEX` (which takes `ACCESS EXCLUSIVE` locks and breaks rolling\n  deployments/rollbacks). Any additive runtime DDL must account for\n  multi-container startup races (process-local `asyncio.Lock` does not prevent\n  cross-container TOCTOU races on `checkfirst=True`) by wrapping creation in a\n  `SAVEPOINT` (`connection.begin_nested()`) and tolerating concurrent creation.\n- **Scope**: a new public name, parameter or flag the fix did not need; a flag\n  that keeps the buggy behavior as the default; a fix in a shared layer when\n  one caller needed it; an existing test assertion edited to match the new\n  behavior.\n\n### 5. Documentation impact\n\n- User-facing documentation lives in the separate `adk-docs` repository, so a\n  user-visible change needs a PR there as well; note it in the report.\n- Guides under `docs/guides/` may need an update when a public API or workflow\n  pattern changes.\n- If the change alters a code unit's design contract, the `adk-unit-design`\n  skill owns the design document for that unit.\n\n### 6. Samples\n\n- Do existing samples under `contributing/samples/` still run against the\n  change?\n- Does a new capability warrant a new sample? Follow the `adk-sample-creator`\n  conventions if so.\n\n### 7. Tests\n\n- Every new or modified code path has a test under `tests/unittests/`.\n- A new test belongs in the existing `test_{module}*.py` file for its unit, not\n  in a file named after the change, which fragments that unit's coverage.\n- Tests follow the rules in the `adk-style` testing reference: one behavior per\n  test, behavior-named tests, no assertions on private attributes, minimal\n  fixtures, arrange/act/assert structure.\n\n## Ground each finding\n\nBefore reporting a finding, resolve the symbol through base classes (`Event`\nextends `LlmResponse`). For a correctness finding, name the input that makes it\nfail; for any other finding, quote the rule or convention it breaks. A finding\nthat is neither is a guess; leave it out.\n\n## Report format\n\nGroup findings by priority and give a file path and line for each. Do not\nreport a dimension with nothing to say.\n\n- 🔴 **Critical**: bugs, type errors, race conditions, resource leaks, security\n  issues.\n- 🟠 **Design**: complexity, readability, performance, architectural\n  misalignment.\n- 🟡 **Style**: lint, formatting, non-lazy logging, typing mismatches.\n- 🔵 **Docs, tests, samples**: missing or stale coverage, guides, samples.\n",".agents/skills/adk-sample-creator/SKILL.md":"---\nname: adk-sample-creator\ndescription: >-\n  Creates a new sample agent in the ADK Python repository — the sample\n  directory, its `agent.py`, and its `README.md` — following the conventions\n  the existing samples already use. Use when the user wants to add a sample or\n  example demonstrating a feature or agent pattern (dynamic nodes,\n  fan-out/fan-in, a standalone tool-using agent), asks where a new sample\n  belongs under `contributing/samples/`, or wants an existing sample's README\n  brought up to the standard structure. Don't use for building a real working\n  agent for the user's own project (use `adk-agent-builder`), or for checking\n  whether the Python blocks in a Markdown file run (use `adk-verify-snippets`).\n---\n\n# ADK Sample Creator\n\nCreates samples under `contributing/samples/`. These are deliberately minimal\nagents that each exercise one or two features — distinct from the `adk-samples`\nrepository, which hosts full end-to-end applications.\n\nRead the `adk-style` skill first for ADK 2.0 conventions if you have not\nalready.\n\n## 1. Pick the category directory\n\nAlmost every sample lives at\n`contributing/samples/{category}/{sample_name}/`. List the categories and\nconfirm with the user which one the sample belongs in before creating\nanything — a workflow sample landing outside `workflows/` is the usual mistake.\n\n```bash\nls contributing/samples/\n```\n\nCategories include `workflows`, `patterns`, `core`, `multi_agent`, `tools`,\n`models`, `live`, `mcp`, `a2a`, `evaluation`, and `plugins`. A handful of\nsamples nest one level further when a single feature needs several variants, as\n`plugins/plugin_reflect_tool_retry/basic/` does.\n\nName the sample directory in `snake_case` after the feature it demonstrates:\n`dynamic_nodes`, `fan_out_fan_in`, `streaming_tool_events`.\n\nDo not add an `_agent` suffix, and do not repeat the category as a prefix —\nevery sample is an agent, and the category is already in the path. Many existing\ndirectories still carry both; do not copy them.\n\n## 2. Write `agent.py`\n\nContents of a sample directory:\n\n| File | Required | Purpose |\n| --- | --- | --- |\n| `agent.py` | yes | The agent or workflow. Must expose `root_agent`. |\n| `README.md` | yes | See [readme-template.md](references/readme-template.md). |\n| `__init__.py` | sometimes | Present when the sample is imported as a package. |\n| `tests/*.json` | no | Recorded sessions used as eval sets. |\n\nUse absolute imports so the file can be run and imported directly.\n\nDo not set `model=` on `Agent` instances. Samples inherit the\nsystem-configured model, which keeps them working when the default model\nchanges; hardcoding `model=\"gemini-2.5-flash\"` pins the sample to a model that\nwill be retired. Set it only when the user explicitly asks for a specific model.\n\nThen pick one of the two shapes.\n\n### Pattern A — Workflow, for multi-step graphs\n\nUse when the sample needs multiple nodes, routing, or parallel execution.\n\n```python\nfrom google.adk import Agent\nfrom google.adk import Context\nfrom google.adk import Event\nfrom google.adk import Workflow\nfrom google.adk.workflow import JoinNode\nfrom google.adk.workflow import node\n```\n\nImport `Workflow` from `google.adk`, not from a private\n`google.adk.workflow._*` module.\n\n```python\nmy_agent = Agent(name=\"my_agent\", instruction=\"...\")\n\n\n@node()\nasync def my_node(node_input: str) -> str:\n  return \"result\"\n\n\nroot_agent = Workflow(\n    name=\"root_agent\",\n    edges=[(\"START\", my_node)],\n)\n```\n\nA plain function can be used as a node directly in `edges`; reach for the\n`@node(...)` decorator when you need one of its options, such as\n`rerun_on_resume=True` for a node that calls `ctx.run_node`.\n\n### Pattern B — Standalone agent, for single-agent or simple tool use\n\nUse when there is no graph and the agent drives its own loop.\n\n```python\nfrom google.adk import Agent\nfrom google.adk.tools import google_search\n\nroot_agent = Agent(\n    name=\"standalone_assistant\",\n    instruction=\"You are a helpful assistant.\",\n    description=\"An assistant that can help with queries.\",\n    tools=[google_search],\n)\n```\n\n## 3. Write `README.md`\n\nFollow [readme-template.md](references/readme-template.md) — section order,\nprompt formatting, the Mermaid topology rules, and the relative link depth for\n`docs/guides/`.\n\n## Worked examples\n\nRead these two before writing a new Pattern A sample — one dynamic graph, one\nstatic one.\n\n-   `contributing/samples/workflows/dynamic_nodes/agent.py` — a Python node\n    driving a `while` loop with `ctx.run_node`, so the number of agent calls is\n    decided at runtime rather than by the edges.\n\n    ```python\n    @node(rerun_on_resume=True)\n    async def orchestrate(ctx: Context, node_input: str) -> str:\n      yield Event(state={\"topic\": node_input})\n\n      while True:\n        headline = await ctx.run_node(generate_headline)\n        # ...\n    ```\n\n-   `contributing/samples/workflows/fan_out_fan_in/agent.py` — three functions\n    run in parallel from `START`, collected by a `JoinNode`, then aggregated.\n\n    ```python\n    join_node = JoinNode(name=\"join_for_results\")\n\n    root_agent = Workflow(\n        name=\"root_agent\",\n        edges=[(\n            \"START\",\n            (make_uppercase, count_characters, reverse_string),\n            join_node,\n            aggregate,\n        )],\n    )\n    ```\n",".agents/skills/adk-sample-creator/references/readme-template.md":"# Sample README structure\n\nEvery sample gets a `README.md` with these sections, in this order.\n\n## Overview\n\nWhat the sample does and which feature or pattern it exists to demonstrate.\n\n## Sample Inputs\n\nPrompts a reader can paste in to exercise the sample. Wrap each prompt in\nbackticks. If a prompt needs an explanation, leave a blank line between the\nprompt and the explanation and indent the explanation by two spaces — without\nthe blank line Markdown folds them into one list item.\n\n## Graph\n\nA Mermaid diagram of the structure, not of the request/response flow.\n\n-   For a `Workflow` root agent, draw the graph of nodes and edges.\n-   For an agent that orchestrates tools or sub-agents (`LlmAgent`,\n    `ManagedAgent`), draw the topology of the agent and its tools/sub-agents\n    instead of internal workflow nodes.\n\nKeep it to a few nodes and edges. A `user -> agent -> API -> tool -> user`\nsequence diagram is noise: it says nothing the topology does not.\n\n## How To\n\nThe key techniques the sample uses (for example `ctx.run_node`), with the few\nlines of code that show each one.\n\n## Related Guides\n\nLinks to the guides under `docs/guides/` that explain the classes used, each\nwith a one-line summary. From a sample at\n`contributing/samples/{category}/{sample_name}/` the guides are four levels up:\n\n```markdown\n- [Workflow](../../../../docs/guides/workflow/workflow/index.md) - Explains building complex multi-step graphs.\n```\n\n## Template\n\n````markdown\n# ADK Sample Name\n\n## Overview\n\nBrief description.\n\n## Sample Inputs\n\n- `Prompt example 1`\n\n- `Prompt example 2`\n\n  *Explanation or expected behavior*\n\n## Graph\n\nFor a Workflow root agent:\n\n```mermaid\ngraph TD\n    START --> MyNode\n```\n\nFor an agent orchestrating tools or sub-agents:\n\n```mermaid\ngraph TD\n    MyAgent[my_agent] -->|calls| MyTool(my_tool)\n```\n\n## How To\n\nExplain the details.\n\n## Related Guides\n\n- [Guide Title](../../../../docs/guides/path/to/guide.md) - Brief description of what the guide covers.\n````\n",".agents/skills/adk-setup/SKILL.md":"---\nname: adk-setup\ndescription: >-\n  Sets up a local ADK Python development environment in a git clone of the\n  open-source adk-python repository: a uv virtual environment, all dependency\n  extras, pre-commit hooks, and a first unit-test run. Runs only when\n  explicitly requested, never on its own. Use when asked to set up, bootstrap,\n  or repair a development checkout, install project dependencies, fix a\n  missing or broken .venv, or prepare a machine for contributing a pull\n  request. Don't use for debugging a running agent (use adk-debug), for commit\n  and pull-request mechanics (use adk-git), or for re-running formatters on a\n  checkout that is already set up (pre-commit run --all-files).\ndisable-model-invocation: true\n---\n\n# ADK Python Development Setup\n\nSet up a working `adk-python` checkout: a uv-managed virtual environment with\nevery dependency extra, pre-commit hooks, and a green unit-test run.\n\n## Prerequisites\n\n1. **Python.** ADK supports 3.11 through 3.15 (`requires-python = \">=3.11\"` in\n   `pyproject.toml`). These steps use 3.11, the version the repo's own tooling\n   defaults to.\n\n   ```bash\n   python3 --version\n   ```\n\n2. **uv.** Dependencies are declared in `pyproject.toml`. The `uv sync` step\n   below creates a local `uv.lock`, which this repository ignores. Run that step\n   before `tox`, whose lock runner requires the file.\n\n   ```bash\n   uv --version\n   ```\n\n   Install it if missing:\n\n   ```bash\n   curl -LsSf https://astral.sh/uv/install.sh | sh\n   ```\n\n## Setup\n\nRun these from the repository root.\n\n1. **Create and activate the virtual environment:**\n\n   ```bash\n   uv venv --python \"python3.11\" \".venv\"\n   source .venv/bin/activate\n   ```\n\n2. **Install every dependency extra:**\n\n   ```bash\n   uv sync --all-extras\n   ```\n\n   `--all-extras` is what pulls in the `test` and `dev` extras. Syncing only\n   the default dependencies leaves `pytest` and the linters uninstalled.\n\n3. **Install pre-commit and tox as standalone tools:**\n\n   ```bash\n   uv tool install pre-commit\n   uv tool install tox --with tox-uv\n   ```\n\n   Both are also in the `dev` extra; installing them as uv tools puts them on\n   PATH so the git hook and multi-version test runs work without an activated\n   venv.\n\n4. **Install `addlicense` (optional, requires Go):**\n\n   ```bash\n   go install github.com/google/addlicense@latest\n   ```\n\n   Without it the pre-commit hook prints `Warning: addlicense not installed,\n   skipping` and passes, so a missing Go toolchain does not block setup — CI\n   catches missing license headers instead.\n\n5. **Install the git hooks:**\n\n   ```bash\n   pre-commit install\n   ```\n\n   On each commit the hooks auto-format with `isort`, `pyink`, and `mdformat`,\n   then run `ruff`, `addlicense`, `codespell`, and the repo's own compliance\n   checks.\n\n6. **Verify the environment:**\n\n   ```bash\n   pytest tests/unittests -n auto\n   ```\n\n   A green run here is the success criterion for setup. `-n auto` needs\n   `pytest-xdist`, which arrives with the `test` extra in step 2.\n\n## Key commands\n\n| Task                                 | Command                                            |\n| :----------------------------------- | :------------------------------------------------- |\n| Run unit tests                       | `pytest tests/unittests`                           |\n| Run unit tests in parallel           | `pytest tests/unittests -n auto`                   |\n| Run one test file                    | `pytest tests/unittests/agents/test_base_agent.py` |\n| Run tests on every supported Python  | `tox` (uv downloads any missing interpreters)      |\n| Format and lint everything           | `pre-commit run --all-files`                       |\n| Launch the web UI                    | `adk web {agents_dir}`                             |\n| Run an agent from the CLI            | `adk run {agent_dir}`                              |\n| Build the wheel                      | `uv build`                                         |\n",".agents/skills/adk-style/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2FSKILL.md -->",".agents/skills/adk-style/references/async.md":"# Async and Concurrency Style Guide\n\n- **I/O belongs in `async def`**: network calls, file system access, database\n  queries, and subprocess waits all go in async functions. ADK runs everything\n  on one event loop, so a synchronous call inside it stalls every concurrent\n  agent, not just the caller.\n- **Don't block the event loop**: no synchronous HTTP clients, `time.sleep`,\n  or blocking file reads inside async code.\n- **Wrap synchronous I/O in `asyncio.to_thread`** when a library offers no\n  async API (`open()`, `pathlib`, most cloud SDK clients):\n\n```python\nasync def save_data(path: Path, data: bytes) -> None:\n  # Wrap the blocking write so the event loop stays free.\n  await asyncio.to_thread(path.write_bytes, data)\n```\n\nNo hook checks this — a blocking call passes CI and shows up later as\nunexplained latency under concurrency, so it is worth catching in review.\n",".agents/skills/adk-style/references/documentation.md":"# Documentation and Comments\n\n## Public API Documentation\n\n- **Classes**: explain the intended usage, with a concise example when the\n  usage is not obvious from the signature. Document every public attribute.\n  For Pydantic models, document fields with an attribute docstring under the\n  field — see the Pydantic reference.\n- **Methods and functions**: document every argument, the return value, and\n  each exception raised.\n- **Docstrings say what the API does.** Design history and rejected\n  alternatives belong in the pull request.\n\n## Internal Implementation Comments\n\n- **Explain why, not what.** The code says what it does; a comment earns its\n  place by saying why it does it that way — the constraint, the bug, or the\n  ordering requirement that is not visible from the code.\n- **No links to RFCs, design docs, issues, or pull requests.** They rot faster\n  than the code, and a reader who cannot open the link is left with nothing.\n  Put the reasoning in the comment itself and the link in the pull request.\n  The same applies to issue numbers, and to warnings, log lines and error\n  messages.\n",".agents/skills/adk-style/references/file-organization.md":"# File Organization\n\n- One class per file in `workflow/`.\n- New modules under `src/google/adk/` are private by default — see\n  the visibility reference for the naming rules and how to expose a\n  public symbol.\n\n## File Headers\n\nEvery module under `src/google/adk/` starts with:\n\n1. The Apache 2.0 license header (added by the `addlicense` hook).\n2. `from __future__ import annotations`.\n3. Imports: standard library, third party, then relative.\n\n`from __future__ import annotations` is checked by\n`scripts/compliance_checks.py`, which exempts `__init__.py`, `version.py`,\n`tests/`, and `contributing/samples/`.\n\n## Where tests go\n\nMirror the source path under `tests/unittests/`:\n\n```text\nsrc/google/adk/tools/environment/_edit_file_tool.py\ntests/unittests/tools/environment/test_edit_file_tool.py\n```\n\nWhen one source file needs several test files, use the source file name —\nwithout the leading underscore or extension — as a shared prefix:\n\n```text\nsrc/google/adk/workflow/_workflow.py\ntests/unittests/workflow/test_workflow.py\ntests/unittests/workflow/test_workflow_hitl.py\ntests/unittests/workflow/test_workflow_nested.py\n```\n",".agents/skills/adk-style/references/formatting.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Fformatting.md -->",".agents/skills/adk-style/references/imports.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Fimports.md -->",".agents/skills/adk-style/references/logging.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Flogging.md -->",".agents/skills/adk-style/references/pydantic.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Fpydantic.md -->",".agents/skills/adk-style/references/testing.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Ftesting.md -->",".agents/skills/adk-style/references/typing.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Ftyping.md -->",".agents/skills/adk-style/references/visibility.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Fvisibility.md -->",".agents/skills/adk-unit-design/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-unit-design%2FSKILL.md -->",".agents/skills/adk-unit-design/references/design-template.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-unit-design%2Freferences%2Fdesign-template.md -->",".agents/skills/adk-unit-guide/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-unit-guide%2FSKILL.md -->",".agents/skills/adk-unit-guide/references/guide-template.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-unit-guide%2Freferences%2Fguide-template.md -->",".agents/skills/adk-verify-snippets/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-verify-snippets%2FSKILL.md -->","AGENTS.md":"## Project Overview\n\nThe Agent Development Kit (ADK) is an open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents.\n\n### Key Components\n\n- **Agent**: Blueprint defining identity, instructions, and tools.\n- **Runner**: Stateless execution engine that orchestrates agent execution.\n- **Tool**: Functions/capabilities agents can call.\n- **Session**: Conversation state management.\n- **Memory**: Long-term recall across sessions.\n- **Workflow** (ADK 2.0): Graph-based orchestration of complex, multi-step agent interactions.\n- **BaseNode** (ADK 2.0): Contract for all nodes, supporting output streaming and human-in-the-loop steps.\n- **Context** (ADK 2.0): Holds execution state and telemetry context mapped 1:1 to nodes.\n\nFor details on how the Runner works and the invocation lifecycle, please refer to the `adk-architecture` skill and the referenced documentation therein.\n\n## ADK Knowledge, Architecture, and Style\n\nSkills related to ADK development are in `.agents/skills/`.\n\n## Project Architecture\n\nFor detailed architecture patterns, component descriptions, and core interfaces, please refer to the **`adk-architecture`** skill at `.agents/skills/adk-architecture/SKILL.md`.\n\n## Development Setup\n\nThe project uses `uv` for package management and Python 3.11+. Please refer to\nthe **`adk-setup`** skill at `.agents/skills/adk-setup/SKILL.md` for detailed\ninstructions.\n","contributing/samples/environment_and_skills/e2b_env_skill_toolset/skills/calc-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fe2b_env_skill_toolset%2Fskills%2Fcalc-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/e2b_env_skill_toolset/skills/text-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fe2b_env_skill_toolset%2Fskills%2Ftext-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/local_env_skill_toolset/skills/calc-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Flocal_env_skill_toolset%2Fskills%2Fcalc-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/local_env_skill_toolset/skills/text-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Flocal_env_skill_toolset%2Fskills%2Ftext-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/local_environment_skill/skills/weather-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Flocal_environment_skill%2Fskills%2Fweather-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/local_environment_skill/skills/weather-skill/references/weather_info.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Flocal_environment_skill%2Fskills%2Fweather-skill%2Freferences%2Fweather_info.md -->","contributing/samples/environment_and_skills/skills/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2FREADME.md -->","contributing/samples/environment_and_skills/skills/skills/weather-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Fskills%2Fweather-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/skills/skills/weather-skill/references/weather_info.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Fskills%2Fweather-skill%2Freferences%2Fweather_info.md -->","contributing/samples/environment_and_skills/skills/tests/current_humidity.json":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Ftests%2Fcurrent_humidity.json -->","contributing/samples/environment_and_skills/skills/tests/current_weather.json":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Ftests%2Fcurrent_weather.json -->","contributing/samples/environment_and_skills/skills/tests/support_hours.json":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Ftests%2Fsupport_hours.json -->","contributing/samples/environment_and_skills/skills/tests/wind_speed.json":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Ftests%2Fwind_speed.json -->","contributing/samples/environment_and_skills/skills_agent/skills/weather-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills_agent%2Fskills%2Fweather-skill%2FSKILL.md -->","contributing/samples/environment_and_skills/skills_agent/skills/weather-skill/references/weather_info.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills_agent%2Fskills%2Fweather-skill%2Freferences%2Fweather_info.md -->","contributing/samples/environment_and_skills/skills_inject_state/skills/code-review-skill/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills_inject_state%2Fskills%2Fcode-review-skill%2FSKILL.md -->","contributing/samples/plugins/plugin_basic/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fplugins%2Fplugin_basic%2FREADME.md -->","contributing/samples/plugins/plugin_reflect_tool_retry/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fplugins%2Fplugin_reflect_tool_retry%2FREADME.md -->","contributing/samples/tools/built_in_multi_tools/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Fbuilt_in_multi_tools%2FREADME.md -->","contributing/samples/tools/function_tools/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Ffunction_tools%2FREADME.md -->","contributing/samples/tools/long_running_functions/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Flong_running_functions%2FREADME.md -->","contributing/samples/tools/model_consult/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Fmodel_consult%2FREADME.md -->","contributing/samples/tools/output_schema_with_tools/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Foutput_schema_with_tools%2FREADME.md -->","contributing/samples/tools/parallel_functions/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Fparallel_functions%2FREADME.md -->","contributing/samples/tools/pydantic_argument/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Fpydantic_argument%2FREADME.md -->","docs/guides/agents/base_agent/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fbase_agent%2Findex.md -->","docs/guides/agents/config/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fconfig%2Findex.md -->","docs/guides/agents/context/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fcontext%2Findex.md -->","docs/guides/agents/invocation_context/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Finvocation_context%2Findex.md -->","docs/guides/agents/llm_agent/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fllm_agent%2Findex.md -->","docs/guides/agents/llm_agent/single_turn.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fllm_agent%2Fsingle_turn.md -->","docs/guides/agents/llm_agent/task.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fllm_agent%2Ftask.md -->","docs/guides/agents/managed_agent/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fmanaged_agent%2Findex.md -->","docs/guides/agents/remote_a2a_agent/task.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fremote_a2a_agent%2Ftask.md -->","docs/guides/agents/run_config/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Frun_config%2Findex.md -->","docs/guides/live/tools/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Flive%2Ftools%2Findex.md -->","docs/guides/plugins/reflect_retry_model_plugin/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fplugins%2Freflect_retry_model_plugin%2Findex.md -->","docs/guides/plugins/reflect_retry_tool_plugin/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fplugins%2Freflect_retry_tool_plugin%2Findex.md -->","docs/guides/plugins/tool_call_integrity_plugin/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fplugins%2Ftool_call_integrity_plugin%2Findex.md -->","docs/guides/skills/skill/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fskills%2Fskill%2Findex.md -->","docs/guides/skills/skill_registry/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fskills%2Fskill_registry%2Findex.md -->","docs/guides/tools/agent_tool/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fagent_tool%2Findex.md -->","docs/guides/tools/base_tool/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fbase_tool%2Findex.md -->","docs/guides/tools/base_toolset/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fbase_toolset%2Findex.md -->","docs/guides/tools/function_tool/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Ffunction_tool%2Findex.md -->","docs/guides/tools/long_running_tool/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Flong_running_tool%2Findex.md -->","docs/guides/tools/mcp_tool/agent_to_mcp/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmcp_tool%2Fagent_to_mcp%2Findex.md -->","docs/guides/tools/mcp_tool/mcp_toolset/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmcp_tool%2Fmcp_toolset%2Findex.md -->","docs/guides/tools/model_consult/force_escalation_plugin/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmodel_consult%2Fforce_escalation_plugin%2Findex.md -->","docs/guides/tools/model_consult/model_consult_tool/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmodel_consult%2Fmodel_consult_tool%2Findex.md -->","docs/guides/tools/model_consult/observability_plugin/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmodel_consult%2Fobservability_plugin%2Findex.md -->","docs/guides/tools/node_tool/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fnode_tool%2Findex.md -->","docs/guides/tools/skill_toolset/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fskill_toolset%2Findex.md -->","docs/guides/tools/tool_args_matcher/tool_args_matcher/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Ftool_args_matcher%2Ftool_args_matcher%2Findex.md -->","docs/guides/tools/tool_confirmation/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Ftool_confirmation%2Findex.md -->","docs/guides/tools/tool_context/index.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Ftool_context%2Findex.md -->","llms-full.txt":"# Agent Development Kit (ADK) - New llms-full.txt location\n\n> The machine-readable llms.txt documentation for Agent Development Kit (ADK) is\n> no longer hosted statically in this repository. It is now automatically\n> generated and hosted on the ADK documentation site. Please use the links below\n> to access the source of truth.\n\n- [llms.txt](https://adk.dev/llms.txt)\n- [llms-full.txt](https://adk.dev/llms-full.txt)\n- [ADK Documentation](https://adk.dev/)\n- [ADK - Coding with AI](https://adk.dev/tutorials/coding-with-ai/)\n","llms.txt":"# Agent Development Kit (ADK) - New llms.txt location\n\n> The machine-readable llms.txt documentation for Agent Development Kit (ADK) is\n> no longer hosted statically in this repository. It is now automatically\n> generated and hosted on the ADK documentation site. Please use the links below\n> to access the source of truth.\n\n- [llms.txt](https://adk.dev/llms.txt)\n- [llms-full.txt](https://adk.dev/llms-full.txt)\n- [ADK Documentation](https://adk.dev/)\n- [ADK - Coding with AI](https://adk.dev/tutorials/coding-with-ai/)\n","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2FSKILL.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_agg.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_agg.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_causal_effect.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_causal_effect.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_classify.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_classify.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_detect_anomalies.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_detect_anomalies.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_evaluate.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_evaluate.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_forecast.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_forecast.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_generate.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate_embedding.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_generate_embedding.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate_table.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_generate_table.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_if.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_if.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_key_drivers.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_key_drivers.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_score.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_score.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_search.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_search.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_similarity.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_similarity.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_contribution_analysis.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_contribution_analysis.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_correlation.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_correlation.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_detect_change_points.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_detect_change_points.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_seasonality.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_seasonality.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_trend.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_trend.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/remote_models.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fremote_models.md -->","src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/vector_search.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fvector_search.md -->","src/google/adk/integrations/bigquery/skills/bigquery-graph/SKILL.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2FSKILL.md -->","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/best_practices.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fgraph_schema%2Fbest_practices.md -->","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/ddl_reference.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fgraph_schema%2Fddl_reference.md -->","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/feature_parity.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fgraph_schema%2Ffeature_parity.md -->","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/graph_schema_ddl_advisor.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fgraph_schema%2Fgraph_schema_ddl_advisor.md -->","src/google/adk/integrations/bigquery/skills/bigquery-graph/references/semantic_queries.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fsemantic_queries.md -->","src/google/adk/skills/README.md":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fskills%2FREADME.md -->"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"## Project Overview\n\nThe Agent Development Kit (ADK) is an open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents.\n\n### Key Components\n\n- **Agent**: Blueprint defining identity, instructions, and tools.\n- **Runner**: Stateless execution engine that orchestrates agent execution.\n- **Tool**: Functions/capabilities agents can call.\n- **Session**: Conversation state management.\n- **Memory**: Long-term recall across sessions.\n- **Workflow** (ADK 2.0): Graph-based orchestration of complex, multi-step agent interactions.\n- **BaseNode** (ADK 2.0): Contract for all nodes, supporting output streaming and human-in-the-loop steps.\n- **Context** (ADK 2.0): Holds execution state and telemetry context mapped 1:1 to nodes.\n\nFor details on how the Runner works and the invocation lifecycle, please refer to the `adk-architecture` skill and the referenced documentation therein.\n\n## ADK Knowledge, Architecture, and Style\n\nSkills related to ADK development are in `.agents/skills/`.\n\n## Project Architecture\n\nFor detailed architecture patterns, component descriptions, and core interfaces, please refer to the **`adk-architecture`** skill at `.agents/skills/adk-architecture/SKILL.md`.\n\n## Development Setup\n\nThe project uses `uv` for package management and Python 3.11+. Please refer to\nthe **`adk-setup`** skill at `.agents/skills/adk-setup/SKILL.md` for detailed\ninstructions.\n","isInternal":false,"tokens":317,"sizeBytes":1420},{"name":"llms-full.txt","path":"llms-full.txt","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/llms-full.txt","title":"LLM Full Documentation Context","category":"root-instruction","format":"text","content":"# Agent Development Kit (ADK) - New llms-full.txt location\n\n> The machine-readable llms.txt documentation for Agent Development Kit (ADK) is\n> no longer hosted statically in this repository. It is now automatically\n> generated and hosted on the ADK documentation site. Please use the links below\n> to access the source of truth.\n\n- [llms.txt](https://adk.dev/llms.txt)\n- [llms-full.txt](https://adk.dev/llms-full.txt)\n- [ADK Documentation](https://adk.dev/)\n- [ADK - Coding with AI](https://adk.dev/tutorials/coding-with-ai/)\n","isInternal":false,"tokens":141,"sizeBytes":526},{"name":"llms.txt","path":"llms.txt","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/llms.txt","title":"LLM Index & Context Digest","category":"root-instruction","format":"text","content":"# Agent Development Kit (ADK) - New llms.txt location\n\n> The machine-readable llms.txt documentation for Agent Development Kit (ADK) is\n> no longer hosted statically in this repository. It is now automatically\n> generated and hosted on the ADK documentation site. Please use the links below\n> to access the source of truth.\n\n- [llms.txt](https://adk.dev/llms.txt)\n- [llms-full.txt](https://adk.dev/llms-full.txt)\n- [ADK Documentation](https://adk.dev/)\n- [ADK - Coding with AI](https://adk.dev/tutorials/coding-with-ai/)\n","isInternal":false,"tokens":140,"sizeBytes":521},{"name":"advanced-patterns.md","path":".agents/skills/adk-agent-builder/references/advanced-patterns.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/advanced-patterns.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Advanced Workflow Patterns\n\nNested workflows, retries, timeouts, custom node classes, and the graph\nvalidation rules that reject a malformed graph at construction time.\n\n```python\nfrom google.adk import Context, Event, Workflow\nfrom google.adk.workflow import BaseNode, Edge, FunctionNode, RetryConfig, START\n```\n\n## Nested workflows\n\nA `Workflow` is both an agent and a node, so one can sit inside another. The\ninner workflow takes the predecessor's output as its `START` input, and its\nterminal output flows on to the next node outside.\n\n```python\ninner = Workflow(name='inner_pipeline', edges=[('START', step_a, step_b)])\n\nouter = Workflow(\n    name='outer_pipeline',\n    edges=[('START', pre_process, inner, post_process)],\n)\n```\n\n## Retries\n\nEvery `RetryConfig` field defaults to `None`, which means \"use the built-in\nfallback\" rather than \"disabled\":\n\n| Field | Fallback | Meaning |\n|---|---|---|\n| `max_attempts` | 5 | Total attempts including the first; 0 or 1 disables retrying |\n| `initial_delay` | 1.0 | Seconds before the first retry |\n| `max_delay` | 60.0 | Ceiling on the computed delay |\n| `backoff_factor` | 2.0 | Multiplier applied per attempt |\n| `jitter` | 1.0 | Randomness factor; 0.0 removes it |\n| `exceptions` | all | Exception classes or class-name strings to retry on |\n\n```python\napi_node = FunctionNode(\n    func=flaky_api_call,\n    name='api_call',\n    retry_config=RetryConfig(max_attempts=5, exceptions=[TimeoutError]),\n)\n```\n\nDelay for attempt *n* is\n`min(initial_delay * backoff_factor ** n, max_delay) * (1 + random(0, jitter))`.\n\nRead the current try from the context — it is 1 on the first attempt:\n\n```python\ndef my_node(ctx: Context, node_input: str) -> str:\n  if ctx.attempt_count > 1:\n    logger.warning('retry %d', ctx.attempt_count)\n  return 'result'\n```\n\n## Timeouts\n\n`timeout` is a per-node wall-clock limit in seconds. Exceeding it raises\n`NodeTimeoutError` (importable from `google.adk.workflow`), which the retry\nmachinery treats like any other exception.\n\n## Execution cancellation\n\nPass an `asyncio.Event` as `abort_signal` to `runner.run_async` to halt an\nin-flight workflow or agent cleanly. Setting the signal cancels active tasks,\nemits a terminal `Event(error_code='INVOCATION_ABORTED')`, seals any dangling\n`FunctionCall` events with a synthetic `FunctionResponse`, and still runs\n`after_run` plugin callbacks.\n\n```python\nimport asyncio\n\nabort_signal = asyncio.Event()\n\nasync for event in runner.run_async(\n    user_id='u1',\n    session_id='s1',\n    new_message=user_content,\n    abort_signal=abort_signal,\n):\n  if should_stop(event):\n    abort_signal.set()\n```\n\nInside a node or tool, `ctx.is_aborted` reflects whether the signal has been\nset.\n\n## Custom node classes\n\n`BaseNode` is a Pydantic model. Declare fields as fields, and override\n`_run_impl` — **not** `run`, which is `@final` and does the normalization of\nyielded values into events.\n\n```python\nfrom typing import Any, AsyncGenerator\n\nfrom typing_extensions import override\n\n\nclass BatchProcessorNode(BaseNode):\n  \"\"\"Processes a list of items in fixed-size batches.\"\"\"\n\n  batch_size: int = 10\n\n  @override\n  async def _run_impl(\n      self, *, ctx: Context, node_input: Any\n  ) -> AsyncGenerator[Any, None]:\n    items = node_input if isinstance(node_input, list) else [node_input]\n    results = []\n    for i in range(0, len(items), self.batch_size):\n      results.extend(await process_batch(items[i:i + self.batch_size]))\n    yield Event(output=results)\n\n\nbatcher = BatchProcessorNode(name='batch_processor', batch_size=25)\n```\n\n`_run_impl` may yield an `Event`, a `RequestInput`, a bare value (wrapped as\n`Event(output=...)`), or `None` (skipped). There is no `get_name()` to override\n— the node's name is the `name` field.\n\n### `BaseNode` fields\n\n| Field | Default | Purpose |\n|---|---|---|\n| `name` | required | Node identity within the graph; must be unique |\n| `description` | `''` | Human-readable label |\n| `rerun_on_resume` | `False` | Re-run after an interrupt instead of taking the answer as output |\n| `wait_for_output` | `False` | Finishing without an output event leaves the node WAITING, not COMPLETED |\n| `retry_config` | `None` | Retry policy |\n| `timeout` | `None` | Seconds before `NodeTimeoutError` |\n| `input_schema` | `None` | Validates and coerces `node_input` |\n| `output_schema` | `None` | Validates and coerces `event.output` |\n| `state_schema` | `None` | Validates `ctx.state` writes; `app:`, `user:`, `temp:` keys bypass it |\n\n### `wait_for_output`\n\nWith `wait_for_output=True`, a node that completes without emitting an output\nevent moves to WAITING rather than COMPLETED, and no downstream node fires. An\nupstream predecessor can trigger it again later — useful for a node that\naccumulates across several triggers before producing one answer.\n\n```python\nclass CollectorNode(BaseNode):\n  wait_for_output: bool = True\n\n  @override\n  async def _run_impl(self, *, ctx, node_input):\n    collected = ctx.state.get('collected', []) + [node_input]\n    yield Event(state={'collected': collected})\n    if len(collected) >= 3:\n      yield Event(output=collected)  # now COMPLETED, downstream fires\n```\n\n`JoinNode` reaches a similar result by a different mechanism — it sets\n`_requires_all_predecessors`, so the orchestrator holds it until every\npredecessor has run and then hands it all their outputs at once.\n\n## Wrapping a tool as a node\n\n`_ToolNode` is private and keyword-only. Its input must be a dict of tool\narguments, or `None`.\n\n```python\nfrom google.adk.tools import FunctionTool\nfrom google.adk.workflow._tool_node import _ToolNode\n\n\ndef search(query: str) -> str:\n  \"\"\"Search for information.\"\"\"\n  return f'Results for: {query}'\n\n\ntool_node = _ToolNode(tool=FunctionTool(search), name='search_node')\n\nagent = Workflow(\n    name='with_tool',\n    edges=[('START', prepare_query, tool_node, process_results)],\n)\n```\n\n## Graph validation\n\n`Workflow` validates the graph when it is constructed, in this order. Each check\nraises `ValueError` naming the offending node or edge.\n\n1. No duplicate node names.\n2. A `START` node exists.\n3. No edge leaving `START` carries a route.\n4. Every node is reachable from `START`, and `START` has no incoming edges.\n5. No two edges share both a source and a target — routes are not part of edge\n   identity.\n6.  A `route` list on one edge never mixes `__DEFAULT__` with named routes.\n7. No unconditional cycle — a cycle needs at least one routed edge.\n8. Where a source declares `output_schema` and its target declares\n   `input_schema`, the two must be the same schema.\n9. No edge into a `mode='chat'` `LlmAgent` from anything but `START`, because a\n   chat agent reads conversation history rather than a node input.\n\nNodes with no outgoing edges are the graph's terminals; their outputs become the\nworkflow's own output.\n\n## Ways to declare edges\n\n```python\nedges = [\n    ('START', node_a),                 # simple\n    (node_a, node_b, 'route'),         # routed\n    (node_a, (node_b, node_c)),        # fan-out\n    ((node_b, node_c), join_node),     # fan-in\n    ('START', node_a, node_b, node_c), # chain of three edges\n    (classifier, {'ok': handler_a, 'err': handler_b}),  # routing map\n]\n```\n\n`Edge` objects are the explicit form. It is a Pydantic model, so its fields are\nkeyword-only:\n\n```python\nedges = [\n    Edge(from_node=START, to_node=node_a),\n    Edge(from_node=node_a, to_node=node_b, route='success'),\n]\n```\n\nTo build the graph yourself, pass `graph=` instead of `edges=`:\n\n```python\nfrom google.adk.workflow._graph import Graph\n\ngraph = Graph.from_edge_items([('START', node_a), (node_a, node_b)])\nagent = Workflow(name='my_workflow', graph=graph)\n```\n\n## Where the code lives\n\n| Component | File |\n|---|---|\n| `Workflow` | `src/google/adk/workflow/_workflow.py` |\n| `Graph`, `Edge`, `DEFAULT_ROUTE` | `src/google/adk/workflow/_graph.py` |\n| graph validation rules | `src/google/adk/workflow/utils/_graph_validation.py` |\n| `BaseNode`, `START` | `src/google/adk/workflow/_base_node.py` |\n| `FunctionNode` | `src/google/adk/workflow/_function_node.py` |\n| `@node`, `Node` | `src/google/adk/workflow/_node.py` |\n| `JoinNode` | `src/google/adk/workflow/_join_node.py` |\n| `_ParallelWorker` | `src/google/adk/workflow/_parallel_worker.py` |\n| `_ToolNode` | `src/google/adk/workflow/_tool_node.py` |\n| `RetryConfig` | `src/google/adk/workflow/_retry_config.py` |\n| running an `LlmAgent` as a node | `src/google/adk/workflow/_llm_agent_wrapper.py` |\n| dynamic node scheduling | `src/google/adk/workflow/_dynamic_node_scheduler.py` |\n| `Context` | `src/google/adk/agents/context.py` |\n| `Event` | `src/google/adk/events/event.py` |\n| `RequestInput` | `src/google/adk/events/request_input.py` |\n","isInternal":false,"tokens":2265,"sizeBytes":8699},{"name":"best-practices.md","path":".agents/skills/adk-agent-builder/references/best-practices.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/best-practices.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# ADK Workflow Rules That Bite\n\nThe failure modes that account for most broken ADK workflows. Each one is a\nsilent or confusing failure, not a clear error — that is why they are collected\nhere rather than left to be discovered.\n\n## Type everything with Pydantic models\n\nUse a `BaseModel` for node inputs, node outputs, LLM `output_schema`,\n`RequestInput.response_schema`, and structured state values. `dict[str, Any]`\ngives up validation, IDE completion, and the automatic dict-to-model conversion\nthat `FunctionNode` performs from type hints.\n\n```python\n# Loses validation and downstream typing\ndef lookup_flights(node_input: dict[str, Any]) -> dict[str, Any]:\n  return {'flight_cost': 500, 'details': 'Economy'}\n\n\n# Validated on the way in and on the way out\nclass FlightInfo(BaseModel):\n  flight_cost: int\n  details: str\n\n\ndef lookup_flights(node_input: Itinerary) -> FlightInfo:\n  return FlightInfo(flight_cost=500, details='Economy')\n```\n\n## `event.output` is plumbing; `event.content` is the UI\n\nThe web UI renders `event.content` only. A node that produces something a human\nshould read must emit it as a message, in addition to the output the next node\nconsumes.\n\n```python\ndef final_output(node_input: str):\n  yield Event(message=node_input)  # rendered in the web UI\n  yield Event(output=node_input)   # passed to the downstream node\n```\n\n`Event(message=...)` is a constructor convenience that writes `event.content`.\nStream partial text with `Event(message='chunk', partial=True)`. LLM agents emit\ntheir content events automatically; function nodes do not.\n\n## Write state through `Event(state=...)`, not `ctx.state[key] = ...`\n\nState passed to the `Event` constructor lands in `event.actions.state_delta`, so\nit is part of event history and survives the replay that non-resumable\nhuman-in-the-loop performs. A direct `ctx.state` mutation is a side effect that\nreplay does not reproduce.\n\n```python\n# Recorded in event history\ndef save(node_input: str):\n  return Event(output=node_input, state={'user_request': node_input})\n\n\n# Lost on replay\ndef save(ctx: Context, node_input: str) -> str:\n  ctx.state['user_request'] = node_input\n  return node_input\n```\n\nReading is always `ctx.state[...]`.\n\n## At most one output event per node execution\n\nA node may yield many events, but only one of them may carry `output`. Two\noutput events are merged into a list, which silently changes the downstream\nnode's `node_input` type from `str` to `list[str]`. The same limit applies to\n`route`, except there a second routed event raises `ValueError`.\n\n```python\ndef my_node(node_input: str):\n  yield Event(message='Processing...')   # display only\n  yield Event(state={'status': 'done'})  # state only\n  yield Event(output='final result')     # the one output\n```\n\nThis holds for function nodes, LLM agent nodes, and nested workflows alike.\n\n## A function either yields or returns — never both\n\nPython turns any function containing `yield` into a generator and discards its\n`return value`. Mixing the two loses the output with no error.\n\n```python\n# Generator: every event is yielded\ndef my_node(node_input: str):\n  yield Event(state={'key': 'value'})\n  yield Event(output='result')\n\n\n# Plain function: one return, which may be an Event or a bare value\ndef my_node(node_input: str):\n  return Event(output='result', state={'key': 'value'})\n\n\n# Broken: the return is silently ignored\ndef my_node(node_input: str):\n  yield Event(state={'key': 'value'})\n  return Event(output='result')\n```\n\n## `{var}` in an instruction reads state, never `node_input`\n\nInstruction placeholders resolve against `ctx.state` only. `node_input` is\ndelivered to the model as the user message, so `{node_input}` is just a missing\nstate key and raises `KeyError` at call time.\n\n```python\n# KeyError: 'node_input' is not a state key\nAgent(name='summarizer', instruction='Summarize this: {node_input}')\n\n# The predecessor's output is already the user message\nAgent(name='summarizer', instruction='Summarize the text in one sentence.')\n\n# Anything else the instruction needs must be in state first\nAgent(name='writer', instruction='Write about \"{topic}\". Feedback: {feedback?}')\n```\n\n`{var?}` substitutes an empty string when the key is absent; `{var}` raises.\n\n## A chat-mode agent can only follow `START`\n\nGraph validation rejects an edge into an `LlmAgent` with `mode='chat'` from any\nnode other than `START`, because a chat agent reads conversational history\nrather than a node input. Set `mode='single_turn'` (the default for an\nauto-wrapped agent) or `mode='task'` on agents used mid-graph.\n\n## Everything in `event.output` must be JSON-serializable\n\n- `FunctionNode` converts a returned `BaseModel` with `model_dump()`, so\n  returning a model is safe. A `types.Content` or any other non-serializable\n  object is not.\n- An LLM agent with `output_schema` validates then dumps, so\n  `ctx.state[output_key]` is a plain `dict`, never a model instance. Read it\n  with `data['field']`, or rebuild the model with `MyModel(**data)`.\n- This bites hardest at a `JoinNode`, which parks partial inputs in session\n  state while waiting. If a predecessor is an LLM agent without `output_schema`,\n  the parked value is a `types.Content` and `DatabaseSessionService` raises\n  `TypeError` on write.\n\n## Give every loop iteration its own `interrupt_id`\n\nA node that yields `RequestInput` inside a loop must vary the `interrupt_id` per\niteration. Reusing one id makes event-based state reconstruction match an\nearlier iteration's answer to the current interrupt, which restarts the loop\nforever.\n\n```python\nreview_count = ctx.state.get('review_count', 0)\nyield RequestInput(\n    interrupt_id=f'review_{review_count}',\n    message='Approve?',\n)\n```\n\nIncrement the counter through `Event(state={'review_count': review_count + 1})`\nwhen the response arrives.\n","isInternal":false,"tokens":1399,"sizeBytes":5813},{"name":"callbacks-and-plugins.md","path":".agents/skills/adk-agent-builder/references/callbacks-and-plugins.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/callbacks-and-plugins.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Callbacks and Plugins\n\nCallbacks hook one agent; plugins hook every agent under an `App`. Both follow\nthe same contract: **return `None` to let the normal thing happen, return a\nvalue to replace it.**\n\n```python\nfrom google.adk.agents.callback_context import CallbackContext\nfrom google.adk.models.llm_request import LlmRequest\nfrom google.adk.models.llm_response import LlmResponse\nfrom google.adk.tools import BaseTool, ToolContext\n```\n\n`CallbackContext` and `ToolContext` are both aliases for `Context`.\n\n## The eight agent callbacks\n\n| Field | Arguments | Return to override |\n|---|---|---|\n| `before_agent_callback` | `(CallbackContext)` | `types.Content` — skips the agent entirely |\n| `after_agent_callback` | `(CallbackContext)` | `types.Content` — replaces the agent's output |\n| `before_model_callback` | `(CallbackContext, LlmRequest)` | `LlmResponse` — skips the model call |\n| `after_model_callback` | `(CallbackContext, LlmResponse)` | `LlmResponse` — replaces the response |\n| `on_model_error_callback` | `(CallbackContext, LlmRequest, Exception)` | `LlmResponse` — suppresses the error |\n| `before_tool_callback` | `(BaseTool, dict, ToolContext)` | `dict` — skips the tool call |\n| `after_tool_callback` | `(BaseTool, dict, ToolContext, dict)` | `dict` — replaces the tool result |\n| `on_tool_error_callback` | `(BaseTool, dict, ToolContext, Exception)` | `dict` — suppresses the error |\n\nEvery one may be sync or async, and every one accepts either a single callable\nor a list. A list runs in order and stops at the first callback that returns\nsomething other than `None`.\n\n## Examples\n\nBlocking a request before it reaches the model:\n\n```python\ndef guard(\n    callback_context: CallbackContext, llm_request: LlmRequest\n) -> LlmResponse | None:\n  for content in llm_request.contents:\n    for part in content.parts or []:\n      if part.text and 'unsafe' in part.text:\n        return LlmResponse(content=types.ModelContent('I cannot process that.'))\n  return None\n\n\nagent = LlmAgent(\n    name='guarded', model='gemini-3.5-flash', before_model_callback=guard\n)\n```\n\nObserving without changing anything — note the explicit `return None`:\n\n```python\ndef log_response(\n    callback_context: CallbackContext, llm_response: LlmResponse\n) -> LlmResponse | None:\n  logger.info('model said: %s', llm_response.content)\n  return None\n```\n\nAuditing and repairing tool calls:\n\n```python\ndef audit(tool: BaseTool, args: dict, tool_context: ToolContext) -> dict | None:\n  logger.info('calling %s with %s', tool.name, args)\n  return None\n\n\ndef repair(\n    tool: BaseTool, args: dict, tool_context: ToolContext, tool_response: dict\n) -> dict | None:\n  if 'error' in tool_response:\n    return {'result': 'Tool execution failed, please try again.'}\n  return None\n\n\nagent = LlmAgent(\n    name='audited',\n    model='gemini-3.5-flash',\n    tools=[my_tool],\n    before_tool_callback=audit,\n    after_tool_callback=repair,\n)\n```\n\nDegrading gracefully on failure:\n\n```python\ndef handle_model_error(\n    callback_context: CallbackContext,\n    llm_request: LlmRequest,\n    error: Exception,\n) -> LlmResponse | None:\n  return LlmResponse(content=types.ModelContent('Service unavailable.'))\n\n\nagent = LlmAgent(\n    name='resilient',\n    model='gemini-3.5-flash',\n    on_model_error_callback=handle_model_error,\n)\n```\n\n## Plugins\n\nA plugin is the same set of hooks applied to every agent, tool, and model call\nin an app, plus a few that only make sense at app scope. All hooks are async and\nkeyword-only.\n\n```python\nfrom google.adk.plugins.base_plugin import BasePlugin\n\n\nclass MyPlugin(BasePlugin):\n\n  def __init__(self):\n    super().__init__(name='my_plugin')\n\n  async def before_agent_callback(self, *, agent, callback_context):\n    return None\n\n  async def before_model_callback(self, *, callback_context, llm_request):\n    return None\n```\n\nBeyond the eight agent-level hooks, `BasePlugin` adds\n`on_user_message_callback`, `before_run_callback`, `on_event_callback`,\n`after_run_callback`, `on_agent_error_callback`, and `on_run_error_callback`.\n\nRegister plugins on the `App`:\n\n```python\nfrom google.adk.apps import App\nfrom google.adk.plugins.context_filter_plugin import ContextFilterPlugin\n\napp = App(\n    name='my_app',\n    root_agent=root_agent,\n    plugins=[ContextFilterPlugin(num_invocations_to_keep=3)],\n)\n```\n\n## Built-in plugins\n\n| Plugin | Module under `google.adk.plugins` | Purpose |\n|---|---|---|\n| `ContextFilterPlugin` | `context_filter_plugin` | Trims history to the last N invocations |\n| `SaveFilesAsArtifactsPlugin` | `save_files_as_artifacts_plugin` | Stores file outputs as session artifacts |\n| `GlobalInstructionPlugin` | `global_instruction_plugin` | Prepends an instruction to every agent |\n| `LoggingPlugin` | `logging_plugin` | Logs the invocation lifecycle |\n| `DebugLoggingPlugin` | `debug_logging_plugin` | Verbose request and response logging |\n| `ReflectAndRetryToolPlugin` | `reflect_retry_tool_plugin` | Retries a failed tool call after letting the model reflect |\n| `MultimodalToolResultsPlugin` | `multimodal_tool_results_plugin` | Routes non-text tool results into content |\n| `AutoTracingPlugin` | `auto_tracing_plugin` | Emits tracing spans automatically |\n| `BigQueryAgentAnalyticsPlugin` | `bigquery_agent_analytics_plugin` | Exports invocation analytics to BigQuery |\n","isInternal":false,"tokens":1300,"sizeBytes":5321},{"name":"dynamic-nodes.md","path":".agents/skills/adk-agent-builder/references/dynamic-nodes.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/dynamic-nodes.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Dynamic Node Scheduling\n\n`await ctx.run_node(...)` runs another node from inside a node and returns its\noutput. It turns graph control flow into ordinary Python: loops, conditionals,\nand early exits, written as loops, conditionals, and early exits.\n\n```python\nfrom google.adk import Agent, Context, Event, Workflow\nfrom google.adk.workflow import FunctionNode, node\n```\n\n## Example\n\n```python\nclass Feedback(BaseModel):\n  grade: str\n\n\ngenerate_headline = Agent(\n    name='generate_headline',\n    instruction='Write a headline about the topic \"{topic}\".',\n)\n\nevaluate_headline = Agent(\n    name='evaluate_headline',\n    mode='single_turn',\n    instruction='Grade whether the headline is tech-related.',\n    output_schema=Feedback,\n)\n\n\n@node(rerun_on_resume=True)\nasync def orchestrate(ctx: Context, node_input: str) -> str:\n  yield Event(state={'topic': node_input})\n  while True:\n    headline = await ctx.run_node(generate_headline)\n    feedback = Feedback.model_validate(\n        await ctx.run_node(evaluate_headline, node_input=headline)\n    )\n    if feedback.grade == 'tech-related':\n      yield headline\n      break\n\n\nroot_agent = Workflow(name='root_agent', edges=[('START', orchestrate)])\n```\n\n## `ctx.run_node` arguments\n\n```python\nawait ctx.run_node(\n    node,                     # a function, Agent, BaseTool, or BaseNode\n    node_input=None,\n    *,\n    use_as_output=False,\n    run_id=None,\n    use_sub_branch=False,\n    override_branch=None,\n)\n```\n\n| Argument | Effect |\n|---|---|\n| `use_as_output` | The child's output becomes the parent's output; the parent's own output events are suppressed |\n| `run_id` | Names this execution instead of auto-numbering it |\n| `use_sub_branch` | Appends `node_name@run_id` to the branch, isolating events from sibling runs |\n| `override_branch` | Uses a specific branch instead of the parent's |\n\n## Rules the framework enforces\n\n**The calling node needs `rerun_on_resume=True`.** Calling `run_node` without it\nraises immediately. The reason is resumption: a dynamically scheduled child may\ninterrupt for user input, and the only way the parent can receive the answer is\nto be re-run from the top.\n\n**An explicit `run_id` must contain a non-digit.** Auto-generated ids are plain\nnumbers (`\"1\"`, `\"2\"`, ...), so an all-digit custom id would collide with one.\n`ValueError` names the offending id.\n\n**`use_as_output=True` at most once per parent execution.** A second call raises\n`Node {path} already has a use_as_output delegate.` (A `Workflow` calling\n`run_node` is exempt.)\n\n**`await` the call directly.** Wrapping it in `asyncio.create_task()` leaves the\nchild unsupervised: its errors are swallowed and it is not cancelled when the\nparent is interrupted.\n\n## Imperative workflows\n\nStandard Python replaces routed edges entirely:\n\n```python\nasync def orchestrator(ctx: Context, node_input: str):\n  res_a = await ctx.run_node(step_a, node_input=node_input)\n  if 'success' in res_a:\n    return await ctx.run_node(step_b, node_input=res_a)\n  return await ctx.run_node(step_c, node_input=res_a)\n```\n\n### Three traps in this style\n\n**A raw function's parameters bind from state, not from `node_input`.** Node\nparameter binding defaults to `'state'`, so a value passed as\n`run_node(fn, node_input=x)` reaches the function only through a parameter\nliterally named `node_input`.\n\n```python\ndef my_worker(node_input: str):  # this name, or the value never arrives\n  return f'Done: {node_input}'\n```\n\n**A child that itself calls `run_node` is a parent too**, so it also needs\n`rerun_on_resume=True`. Raw functions default to `False`, so wrap it:\n\n```python\ninner = FunctionNode(func=inner_orchestrator, rerun_on_resume=True)\n```\n\n**A generator cannot `return` a value.** In a node that uses `yield`, produce\nthe result with `yield Event(output=...)`; `return value` is a syntax error in\nan async generator and silently ignored in a sync one.\n","isInternal":false,"tokens":938,"sizeBytes":3884},{"name":"function-nodes.md","path":".agents/skills/adk-agent-builder/references/function-nodes.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/function-nodes.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Function Nodes\n\nAny Python function can be a workflow node. Put the callable straight into\n`edges` and the framework wraps it in a `FunctionNode`.\n\n```python\nfrom google.adk import Context, Event\nfrom google.adk.workflow import FunctionNode, RetryConfig, node\n```\n\n## Parameter resolution\n\n`FunctionNode` inspects the signature and fills each parameter by name:\n\n| Parameter name | Bound to |\n|---|---|\n| `ctx` | the workflow `Context` |\n| `node_input` | the predecessor node's output |\n| anything else | `ctx.state[param_name]`, falling back to the default |\n\n```python\ndef with_context(ctx: Context, node_input: str) -> str:\n  return f'Session {ctx.session.id}: {node_input}'\n\n\ndef input_only(node_input: str) -> str:\n  return node_input.upper()\n\n\ndef from_state(node_input: str, user_name: str) -> str:\n  # user_name comes from ctx.state['user_name']\n  return f'{user_name}: {node_input}'\n\n\ndef no_params() -> str:\n  return 'hello'\n```\n\nPass `parameter_binding='node_input'` to `FunctionNode` or `@node` to bind every\nparameter from a `node_input` dict instead of from state. That mode also infers\n`input_schema` and `output_schema` from the signature, and is what the framework\nuses when a node is exposed as an agent's tool.\n\n## Return values\n\nA plain return is wrapped in `Event(output=...)`. Returning `None` emits no\nevent, so no downstream node fires.\n\n```python\ndef process(node_input: str) -> str:\n  return f'Processed: {node_input}'\n\n\nasync def fetch_data(node_input: str) -> dict:\n  return {'data': await some_api_call(node_input)}\n\n\ndef maybe_output(node_input: str) -> str | None:\n  if not node_input:\n    return None  # downstream stays idle\n  return f'Got: {node_input}'\n```\n\nGenerators may yield `Event` objects or bare values — a bare yield is wrapped\nthe same way a return is.\n\n```python\nasync def multi(ctx: Context):\n  yield Event(message='working...')\n  yield 'output value'  # becomes Event(output='output value')\n```\n\n## Input coercion\n\n`FunctionNode` runs each argument through a Pydantic `TypeAdapter` built from\nthe annotation, so the annotation is both documentation and a coercion rule:\n\n| Annotation | Incoming value | Result |\n|---|---|---|\n| a `BaseModel` subclass | `dict` | validated model instance |\n| `list[Model]`, `dict[K, Model]` | nested dicts | recursively converted |\n| `str` (including `str \\| None`) | `types.Content` | concatenated text of the text parts |\n| anything else | any | validated by `TypeAdapter`, `TypeError` on mismatch |\n\nThe `types.Content` to `str` rule drops non-text parts (inline data, file data,\nexecutable code) and logs a warning when it does.\n\n```python\nclass Order(BaseModel):\n  item: str\n  quantity: int\n\n\ndef process_order(node_input: Order) -> str:\n  # {'item': 'widget', 'quantity': 3} arrives as Order(item='widget', quantity=3)\n  return f'Order: {node_input.quantity}x {node_input.item}'\n```\n\nA union annotation (`list | dict`) accepts anything — the adapter is satisfied\nby any member, so wrong types reach the body. Use `isinstance` checks inside the\nfunction when a union is unavoidable.\n\n## What `node_input` will actually be\n\n| Predecessor | `node_input` |\n|---|---|\n| function returning `str` / `dict` | that value |\n| function returning `Event(output=X)` | `X` |\n| `LlmAgent` without `output_schema` | `str` — the model's concatenated text |\n| `LlmAgent` with `output_schema` | `dict` — the validated model, dumped |\n| `JoinNode` | `dict[str, Any]` keyed by predecessor node name |\n| a `parallel_worker=True` node | `list` of per-item results |\n| `START` without `input_schema` | `types.Content` (the user's message) |\n| `START` with `input_schema` | the parsed schema type |\n\n## Explicit `FunctionNode`\n\nConstruct one directly when you need to override its properties. Every argument\nis keyword-only, including `func`.\n\n```python\napi_node = FunctionNode(\n    func=flaky_api_call,\n    name='api_call',             # defaults to func.__name__\n    rerun_on_resume=True,        # re-run after a human-in-the-loop interrupt\n    retry_config=RetryConfig(max_attempts=3, initial_delay=1.0),\n    timeout=30.0,\n)\n```\n\n## The `@node` decorator\n\n`@node` is the same thing with less ceremony, and it also accepts an already-made\nnode, an agent, or a tool.\n\n```python\n@node\ndef plain(node_input: str) -> str:\n  return node_input\n\n\n@node(name='custom_name', rerun_on_resume=True)\nasync def renamed(node_input: str) -> str:\n  return node_input\n\n\n# Called as a function on an existing callable\nmy_node = node(some_func, name='renamed')\n\n# Fan a list out across parallel workers\nworker = node(some_func, parallel_worker=True)\n```\n\nAccepted keywords: `name`, `rerun_on_resume`, `retry_config`, `timeout`,\n`parallel_worker`, `max_parallel_workers`, `auth_config`,\n`require_confirmation`, `parameter_binding`.\n\n## Routing and state from a node\n\n```python\ndef classify(node_input: str):\n  route = 'urgent' if 'urgent' in node_input else 'normal'\n  return Event(output=node_input, route=route)\n\n\ndef update_counter(node_input: str):\n  return Event(output=node_input, state={'last_input': node_input})\n```\n\n## Requiring confirmation before a node runs\n\n`require_confirmation` pauses the workflow with an `adk_request_input`\ninterrupt (`wf_tool_confirmation:{node_path}`) before the function runs. It\naccepts a `bool` or a sync/async predicate callable receiving the function's\nbound arguments (plus `ctx` or `node_input` if declared). `@node` automatically\ndefaults `rerun_on_resume=True` when `require_confirmation` is set.\n\n```python\n@node(require_confirmation=True)\ndef delete_file(path: str) -> str:\n  return f'Deleted {path}'\n\n\n@node(require_confirmation=lambda amount: amount > 1000)\ndef transfer_funds(amount: int) -> str:\n  return f'Transferred {amount}'\n```\n\n## Requiring authentication before a node runs\n\n`auth_config` makes the framework request user credentials before the node's\nfirst execution. It requires `rerun_on_resume=True`, because the node runs again\nonce the credential arrives.\n\n```python\nsecured = FunctionNode(\n    func=call_private_api,\n    auth_config=my_auth_config,\n    rerun_on_resume=True,\n)\n```\n\nInside the node, read the credential with\n`AuthHandler(auth_config).get_auth_response(ctx.state)`\n(`google.adk.auth.auth_handler.AuthHandler`).\n","isInternal":false,"tokens":1555,"sizeBytes":6245},{"name":"getting-started.md","path":".agents/skills/adk-agent-builder/references/getting-started.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/getting-started.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Getting Started: Creating ADK Agents\n\nEnvironment, the agent directory convention, a first LLM agent, and the jump to\ngraph workflows.\n\n## CLI commands\n\n| Command | What it does |\n|---|---|\n| `adk create {agent_name}` | Scaffolds an agent directory |\n| `adk run {agent_dir}` | Runs the agent in the terminal |\n| `adk web {agent_dir}` | Dev server on `http://localhost:8000` (development only) |\n| `adk api_server {agent_dir}` | HTTP API for the agent |\n\n## 1. Environment\n\n```bash\nuv venv --python python3.11 .venv\nsource .venv/bin/activate\nuv pip install google-adk\n```\n\n`pip install google-adk` in a `python -m venv` works too. ADK requires Python\n3.10 or newer.\n\n## 2. API keys\n\nPut a `.env` file in the **agent directory**, not its parent — the loader looks\nbeside `agent.py`. Do not commit it.\n\nGoogle AI Studio (get a key at https://aistudio.google.com/app/apikey):\n\n```bash\nGOOGLE_GENAI_USE_ENTERPRISE=FALSE\nGOOGLE_API_KEY=YOUR_API_KEY\n```\n\nVertex AI, after `gcloud auth application-default login`:\n\n```bash\nGOOGLE_GENAI_USE_ENTERPRISE=TRUE\nGOOGLE_CLOUD_PROJECT=your-project-id\nGOOGLE_CLOUD_LOCATION=us-central1\n```\n\nVertex AI express mode swaps the project/location pair for an API key:\n\n```bash\nGOOGLE_GENAI_USE_ENTERPRISE=TRUE\nGOOGLE_API_KEY=YOUR_EXPRESS_MODE_KEY\n```\n\n`GOOGLE_GENAI_USE_VERTEXAI` is the old name for the same switch. It still works\nbut emits a `DeprecationWarning`; `GOOGLE_GENAI_USE_ENTERPRISE` wins when both\nare set.\n\n## 3. Directory layout\n\nThe CLI discovers agents by convention:\n\n```text\nmy_agent/\n├── __init__.py    # from . import agent\n├── agent.py       # defines root_agent (and optionally app)\n└── .env\n```\n\n`__init__.py` must re-export the module, or the agent will not appear in\n`adk web`:\n\n```python\nfrom . import agent\n```\n\n## 4. A basic LLM agent\n\n`LlmAgent` (aliased as `Agent`) binds a model, an instruction, and tools.\n\n```python\nfrom google.adk import Agent\n\n\ndef get_weather(city: str) -> dict:\n  \"\"\"Returns the current weather for a specified city.\"\"\"\n  return {\n      'status': 'success',\n      'city': city,\n      'weather': 'sunny',\n      'temperature': '72F',\n  }\n\n\nroot_agent = Agent(\n    model='gemini-3.5-flash',\n    name='root_agent',\n    description='An assistant that reports the weather.',\n    instruction=(\n        'You are a helpful assistant. Use get_weather to look up the'\n        ' weather in any city. Be concise.'\n    ),\n    tools=[get_weather],\n)\n```\n\n| Field | Purpose |\n|---|---|\n| `model` | Model id, e.g. `'gemini-3.5-flash'`, `'gemini-3.1-pro-preview'` |\n| `instruction` | System prompt; `{var}` placeholders resolve from session state |\n| `tools` | Python callables; name, docstring, and type hints become the tool schema |\n| `description` | How a parent agent decides to route to this one |\n| `output_key` | Session-state key to store the agent's final text under |\n\nA tool function needs a docstring and type hints on every parameter — the LLM\nsees only those. Return a `dict` or a `str`.\n\n## 5. Running it programmatically\n\n```python\nimport asyncio\n\nfrom google.adk.runners import InMemoryRunner\nfrom google.genai import types\n\nfrom my_agent import agent\n\n\nasync def main():\n  runner = InMemoryRunner(app_name='my_app', agent=agent.root_agent)\n  session = await runner.session_service.create_session(\n      app_name='my_app', user_id='user1'\n  )\n  message = types.Content(\n      role='user',\n      parts=[types.Part.from_text(text=\"What's the weather in Paris?\")],\n  )\n  async for event in runner.run_async(\n      user_id='user1', session_id=session.id, new_message=message\n  ):\n    if event.content and event.content.parts:\n      text = event.content.parts[0].text\n      if text:\n        print(f'{event.author}: {text}')\n\n\nasyncio.run(main())\n```\n\n## 6. From one agent to a workflow\n\nA `Workflow` replaces \"one LLM decides everything\" with an explicit graph. The\nsmallest one has a single edge from `START`:\n\n```python\nfrom google.adk import Workflow\n\n\ndef greet(node_input: str) -> str:\n  return f'Hello! You said: {node_input}'\n\n\nroot_agent = Workflow(name='my_workflow', edges=[('START', greet)])\n```\n\n### Sequential pipeline of LLM agents\n\n> **Deprecated.** `SequentialAgent` is deprecated in favour of `Workflow`.\n> Prefer the explicit edge list above for new code; this form is documented\n> because existing agents still use it.\n\n`SequentialAgent` generates `START -> a -> b -> c` for you. Each agent's\n`output_key` publishes to session state, and the next agent reads it through an\ninstruction placeholder.\n\n```python\nfrom google.adk.agents import LlmAgent, SequentialAgent\n\nwriter = LlmAgent(\n    name='CodeWriterAgent',\n    model='gemini-3.5-flash',\n    instruction=(\n        'Write Python code that fulfills the user request. Output only the'\n        ' code block.'\n    ),\n    description='Writes initial Python code from a specification.',\n    output_key='generated_code',\n)\n\nreviewer = LlmAgent(\n    name='CodeReviewerAgent',\n    model='gemini-3.5-flash',\n    instruction=(\n        'Review this code and reply with a bulleted list of issues, or \"No'\n        ' major issues found.\" if it is clean:\\n\\n{generated_code}'\n    ),\n    description='Reviews code and provides feedback.',\n    output_key='review_comments',\n)\n\nrefactorer = LlmAgent(\n    name='CodeRefactorerAgent',\n    model='gemini-3.5-flash',\n    instruction=(\n        'Improve this code:\\n\\n{generated_code}\\n\\nAddressing these'\n        ' comments:\\n\\n{review_comments}\\n\\nOutput only the final code'\n        ' block.'\n    ),\n    description='Refactors code based on review comments.',\n    output_key='refactored_code',\n)\n\nroot_agent = SequentialAgent(\n    name='CodePipelineAgent',\n    sub_agents=[writer, reviewer, refactorer],\n    description='Writes, reviews, and refactors Python code.',\n)\n```\n\n### Graph with conditional routing\n\nA node returns `Event(route=...)` and the edge dict picks the branch.\n\n```python\nfrom google.adk import Event, Workflow\n\n\ndef parse_input(node_input: str) -> dict:\n  return {'text': node_input, 'word_count': len(node_input.split())}\n\n\ndef classify(node_input: dict):\n  route = 'long' if node_input['word_count'] > 10 else 'short'\n  return Event(output=node_input, route=route)\n\n\ndef handle_short(node_input: dict) -> str:\n  return f\"Short ({node_input['word_count']} words): {node_input['text']}\"\n\n\ndef handle_long(node_input: dict) -> str:\n  return f\"Long ({node_input['word_count']} words): {node_input['text'][:50]}...\"\n\n\nroot_agent = Workflow(\n    name='classifier_workflow',\n    input_schema=str,\n    edges=[\n        ('START', parse_input, classify),\n        (classify, {'short': handle_short, 'long': handle_long}),\n    ],\n)\n```\n\n### Parallel list processing\n\n`parallel_worker=True` makes a node run once per item of a list input and\nreturn a list of results.\n\n```python\nfrom google.adk import Workflow\nfrom google.adk.workflow import node\n\n\ndef split_input(node_input: str) -> list:\n  return [item.strip() for item in node_input.split(',')]\n\n\n@node(parallel_worker=True)\ndef process_item(node_input: str) -> dict:\n  return {'item': node_input, 'upper': node_input.upper()}\n\n\ndef format_results(node_input: list) -> str:\n  return '\\n'.join(f\"- {r['item']} -> {r['upper']}\" for r in node_input)\n\n\nroot_agent = Workflow(\n    name='parallel_processor',\n    input_schema=str,\n    edges=[('START', split_input, process_item, format_results)],\n)\n```\n\n### Mixing function nodes and an LLM agent\n\n```python\nfrom google.adk import Workflow\nfrom google.adk.agents import LlmAgent\n\n\ndef get_weather(city: str) -> dict:\n  \"\"\"Get the current weather for a city.\"\"\"\n  return {'city': city, 'temp': '72F', 'condition': 'sunny'}\n\n\ndef extract_city(node_input: str) -> str:\n  return node_input.strip()\n\n\nweather_agent = LlmAgent(\n    name='weather_reporter',\n    model='gemini-3.5-flash',\n    instruction='Use get_weather, then give a natural-language report.',\n    tools=[get_weather],\n)\n\n\ndef sign_off(node_input: str) -> str:\n  return f'{node_input}\\n\\nHave a great day!'\n\n\nroot_agent = Workflow(\n    name='weather_workflow',\n    input_schema=str,\n    edges=[('START', extract_city, weather_agent, sign_off)],\n)\n```\n\n## Troubleshooting\n\n| Symptom | Cause |\n|---|---|\n| `No module named 'google.adk'` | Virtual environment not activated, or `google-adk` not installed in it |\n| Agent missing from the `adk web` dropdown | `__init__.py` lacks `from . import agent`, or `agent.py` defines no `root_agent` |\n| API key errors | `.env` sits in the parent directory instead of the agent directory |\n| Model not found | Typo in the model id; non-Google models (Anthropic, LiteLLM) need extra dependencies |\n","isInternal":false,"tokens":2218,"sizeBytes":8603},{"name":"human-in-the-loop.md","path":".agents/skills/adk-agent-builder/references/human-in-the-loop.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/human-in-the-loop.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Human-in-the-Loop\n\nPause a workflow to ask the user something, then continue with their answer.\n\n```python\nfrom google.adk import Context, Event, Workflow\nfrom google.adk.apps import App, ResumabilityConfig\nfrom google.adk.events import RequestInput\nfrom google.adk.workflow import FunctionNode\n```\n\n## Asking\n\nYield or return a `RequestInput`. The node's output stream is normalized, so\neither works — a plain function can return one directly without becoming a\ngenerator.\n\n```python\nasync def approval_gate(ctx: Context, node_input: str):\n  yield RequestInput(message='Please approve this action:')\n\n\ndef evaluate_request(request: TimeOffRequest):\n  if request.days <= 1:\n    return TimeOffDecision(approved=True)  # no interrupt at all\n  return RequestInput(\n      interrupt_id='manager_approval',\n      message='Please review this time off request.',\n      payload=request,\n      response_schema=TimeOffDecision,\n  )\n```\n\nThe workflow emits an `adk_request_input` function call and stops. Responding to\nthat function call resumes it.\n\n| Field | Type | Notes |\n|---|---|---|\n| `interrupt_id` | `str` | Auto-generated UUID when omitted |\n| `message` | `str \\| None` | Shown to the user |\n| `payload` | `Any` | Arbitrary data carried along with the request |\n| `response_schema` | Pydantic class, Python type, or JSON-schema dict | Expected response shape |\n\n## Resuming: `rerun_on_resume`\n\nThe flag on the interrupted node decides what happens when the answer arrives.\n\n**`rerun_on_resume=False`** (the default for `FunctionNode`) — the node is *not*\nre-executed; the user's response becomes its output and flows downstream.\n\n```python\napproval_node = FunctionNode(func=ask_approval, rerun_on_resume=False)\n```\n\n**`rerun_on_resume=True`** (the default for an `LlmAgent` used as a node) — the\nnode runs again from the top, with answers available in `ctx.resume_inputs`,\nkeyed by `interrupt_id`.\n\n```python\nasync def interactive_node(ctx: Context, node_input: str):\n  if ctx.resume_inputs:\n    answer = list(ctx.resume_inputs.values())[0]\n    yield Event(output=f'User said: {answer}')\n  else:\n    yield RequestInput(message='What should I do?')\n```\n\n## Several questions from one node\n\nBecause a re-run node sees every answer so far, one node can walk a form:\n\n```python\nasync def multi_step_form(ctx: Context, node_input: str):\n  if not ctx.resume_inputs:\n    yield RequestInput(interrupt_id='ask_name', message='What is your name?')\n    return\n\n  if 'ask_email' not in ctx.resume_inputs:\n    yield RequestInput(interrupt_id='ask_email', message='What is your email?')\n    return\n\n  yield Event(output={\n      'name': ctx.resume_inputs['ask_name'],\n      'email': ctx.resume_inputs['ask_email'],\n  })\n```\n\n## Feeding the answer back into a loop\n\nA review-and-revise cycle turns the answer into a route. Vary the\n`interrupt_id` per iteration (see the best-practices reference for why) and read\n`ctx.resume_inputs` with the same id:\n\n```python\nasync def review(ctx: Context, node_input: Any):\n  review_count = ctx.state.get('review_count', 0)\n  interrupt_id = f'review_{review_count}'\n\n  response = ctx.resume_inputs.get(interrupt_id)\n  if response:\n    yield Event(\n        output=response,\n        route='approved' if response.get('approved') else 'rejected',\n        state={'review_count': review_count + 1},\n    )\n    return\n\n  yield RequestInput(\n      interrupt_id=interrupt_id,\n      message='Approve this plan?',\n      response_schema=ApprovalSchema,\n  )\n```\n\nWire the routes back with `(review, {'rejected': revise, 'approved': publish})`.\n\n## Resumable versus replayed\n\n**Replay (the default).** With no `App`, or with `is_resumable=False`, each\nresponse replays the workflow from `START`. Completed nodes are skipped and\nstate is rebuilt from event history, so only the interrupted node actually runs\nagain. Fine for a single interrupt; the replay cost grows with the graph.\n\n**Resumable.** Export an `App` with `is_resumable=True` and the workflow\ncheckpoints its progress into `event.actions.agent_state` and resumes at the\ninterrupted node instead of replaying.\n\n```python\nroot_agent = Workflow(name='my_workflow', edges=[...])\n\napp = App(\n    name='my_app',\n    root_agent=root_agent,\n    resumability_config=ResumabilityConfig(is_resumable=True),\n)\n```\n\nExport both `root_agent` and `app` from `agent.py` — the loader prefers `app`\nbut other tooling looks for `root_agent`. Use resumable mode for multi-step\ninterrupts, for `LongRunningFunctionTool`, and for any graph large enough that\nreplaying it is wasteful.\n\n## Human-in-the-loop from an LLM agent\n\nAn `LlmAgent` pauses through a `LongRunningFunctionTool` rather than\n`RequestInput`:\n\n```python\nfrom google.adk.tools import LongRunningFunctionTool\n\n\ndef approval_tool(request: str) -> str:\n  \"\"\"Request human approval for an action.\"\"\"\n  return f'Approved: {request}'\n\n\nllm_agent = LlmAgent(\n    name='agent_with_approval',\n    model='gemini-3.5-flash',\n    instruction='When you need approval, use approval_tool.',\n    tools=[LongRunningFunctionTool(func=approval_tool)],\n)\n```\n\nThe agent node already defaults to `rerun_on_resume=True`, so it picks the\nconversation back up on its own.\n\n## Answering from client code\n\n```python\nfrom google.genai import types\n\nfunction_call_id = interrupt_event.content.parts[0].function_call.id\n\nresponse = types.Content(\n    role='user',\n    parts=[types.Part(\n        function_response=types.FunctionResponse(\n            id=function_call_id,\n            name='adk_request_input',\n            response={'result': \"User's answer here\"},\n        )\n    )],\n)\n\nasync for event in runner.run_async(\n    user_id=user_id, session_id=session_id, new_message=response\n):\n  ...\n```\n\nA non-dict answer is carried under the `result` key as shown; a dict response\nmatching `response_schema` is passed through as-is.\n","isInternal":false,"tokens":1397,"sizeBytes":5822},{"name":"import-paths.md","path":".agents/skills/adk-agent-builder/references/import-paths.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/import-paths.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# ADK Import Paths\n\nPrefer the canonical short import. The verbose module path is listed only where\nthere is no short form, or where the short form does not exist yet.\n\n## Canonical imports\n\nThese cover most agent code:\n\n```python\nfrom google.adk import Agent, Context, Event, Runner, Workflow\nfrom google.adk.events import Event, EventActions, RequestInput\nfrom google.adk.workflow import BaseNode, Edge, FunctionNode, JoinNode, node\nfrom google.adk.workflow import DEFAULT_ROUTE, RetryConfig, START\n```\n\n`google.adk` and `google.adk.workflow` load their members lazily, so importing\nfrom them does not pull in the whole package.\n\n## Agents\n\n| Component | Import |\n|---|---|\n| `Agent` (alias of `LlmAgent`) | `from google.adk import Agent` |\n| `LlmAgent` | `from google.adk.agents import LlmAgent` |\n| `BaseAgent` | `from google.adk.agents import BaseAgent` |\n| `SequentialAgent` (deprecated — use `Workflow`) | `from google.adk.agents import SequentialAgent` |\n| `ParallelAgent` (deprecated — use `Workflow`) | `from google.adk.agents import ParallelAgent` |\n| `LoopAgent` (deprecated — use `Workflow`) | `from google.adk.agents import LoopAgent` |\n| `RunConfig` | `from google.adk.agents import RunConfig` |\n\n## Workflow graph\n\n| Component | Import |\n|---|---|\n| `Workflow` | `from google.adk.workflow import Workflow` |\n| `Edge` | `from google.adk.workflow import Edge` |\n| `DEFAULT_ROUTE` (`\"__DEFAULT__\"`) | `from google.adk.workflow import DEFAULT_ROUTE` |\n| `START` | `from google.adk.workflow import START` |\n| `Graph` | `from google.adk.workflow._graph import Graph` |\n\n`Graph` is private; construct workflows from `edges=[...]` unless you need\n`Graph.from_edge_items(...)` directly.\n\n## Nodes\n\n| Component | Import |\n|---|---|\n| `BaseNode` | `from google.adk.workflow import BaseNode` |\n| `FunctionNode` | `from google.adk.workflow import FunctionNode` |\n| `Node` | `from google.adk.workflow import Node` |\n| `@node` decorator | `from google.adk.workflow import node` |\n| `JoinNode` | `from google.adk.workflow import JoinNode` |\n| `RetryConfig` | `from google.adk.workflow import RetryConfig` |\n| `NodeTimeoutError` | `from google.adk.workflow import NodeTimeoutError` |\n| `_ToolNode` (private) | `from google.adk.workflow._tool_node import _ToolNode` |\n\nParallel-worker behavior has no importable class. Set `parallel_worker=True` on\n`@node` or on an `LlmAgent`; the framework wraps it with an internal\n`_ParallelWorker`.\n\n## Events and context\n\n| Component | Import |\n|---|---|\n| `Event` | `from google.adk import Event` |\n| `EventActions` | `from google.adk.events import EventActions` |\n| `RequestInput` | `from google.adk.events import RequestInput` |\n| `Context` | `from google.adk import Context` |\n| `CallbackContext` (alias of `Context`) | `from google.adk.agents.callback_context import CallbackContext` |\n| `ReadonlyContext` | `from google.adk.agents.readonly_context import ReadonlyContext` |\n| `ToolContext` (alias of `Context`) | `from google.adk.tools import ToolContext` |\n\n## Task delegation\n\n| Component | Import |\n|---|---|\n| `FinishTaskTool` | `from google.adk.agents.llm.task._finish_task_tool import FinishTaskTool` |\n| `TaskRequest`, `TaskResult` | `from google.adk.agents.llm.task._task_models import TaskRequest, TaskResult` |\n\nAll three are private. Setting `mode='task'` attaches `FinishTaskTool`\nautomatically — there is no reason to import it in agent code.\n\n## Tools\n\n| Component | Import |\n|---|---|\n| `FunctionTool` | `from google.adk.tools import FunctionTool` |\n| `BaseTool` | `from google.adk.tools import BaseTool` |\n| `BaseToolset` | `from google.adk.tools.base_toolset import BaseToolset` |\n| `LongRunningFunctionTool` | `from google.adk.tools import LongRunningFunctionTool` |\n| `AgentTool` | `from google.adk.tools import AgentTool` |\n| `McpToolset` | `from google.adk.tools.mcp_tool import McpToolset` |\n| `StdioConnectionParams` | `from google.adk.tools.mcp_tool import StdioConnectionParams` |\n| `SseConnectionParams` | `from google.adk.tools.mcp_tool import SseConnectionParams` |\n| `StreamableHTTPConnectionParams` | `from google.adk.tools.mcp_tool import StreamableHTTPConnectionParams` |\n| `OpenAPIToolset`, `RestApiTool` | `from google.adk.tools.openapi_tool import OpenAPIToolset, RestApiTool` |\n\n`MCPToolset` (all caps) still resolves but raises a deprecation warning — use\n`McpToolset`.\n\n## Runner, sessions, app\n\n| Component | Import |\n|---|---|\n| `Runner` | `from google.adk import Runner` |\n| `InMemoryRunner` | `from google.adk.runners import InMemoryRunner` |\n| `InMemorySessionService` | `from google.adk.sessions import InMemorySessionService` |\n| `DatabaseSessionService` | `from google.adk.sessions import DatabaseSessionService` |\n| `VertexAiSessionService` | `from google.adk.sessions import VertexAiSessionService` |\n| `App`, `ResumabilityConfig` | `from google.adk.apps import App, ResumabilityConfig` |\n| `BasePlugin` | `from google.adk.plugins.base_plugin import BasePlugin` |\n\n`DatabaseSessionService` needs the `db` extra; importing it without\n`sqlalchemy` installed raises a \"missing extra\" error rather than\n`ImportError`.\n\n## Live streaming\n\n| Component | Import |\n|---|---|\n| `LiveRequestQueue` | `from google.adk.live import LiveRequestQueue` |\n| `LiveRequest` | `from google.adk.live import LiveRequest` |\n\n## Models\n\n| Component | Import |\n|---|---|\n| `BaseLlm` | `from google.adk.models.base_llm import BaseLlm` |\n| `LiteLlm` | `from google.adk.models.lite_llm import LiteLlm` |\n| `LlmRequest` | `from google.adk.models.llm_request import LlmRequest` |\n| `LlmResponse` | `from google.adk.models.llm_response import LlmResponse` |\n\n## Code executors\n\n| Component | Import |\n|---|---|\n| `BuiltInCodeExecutor` | `from google.adk.code_executors.built_in_code_executor import BuiltInCodeExecutor` |\n\n## google-genai types\n\n| Component | Import |\n|---|---|\n| `types` | `from google.genai import types` |\n| `Content`, `ModelContent`, `Part` | `from google.genai.types import Content, ModelContent, Part` |\n| `GenerateContentConfig` | `from google.genai.types import GenerateContentConfig` |\n","isInternal":false,"tokens":1617,"sizeBytes":6082},{"name":"llm-agent-nodes.md","path":".agents/skills/adk-agent-builder/references/llm-agent-nodes.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/llm-agent-nodes.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# LLM Agents as Workflow Nodes\n\nPut an `LlmAgent` straight into `edges` and the framework runs it as a node,\nconverting the model's answer into the next node's `node_input`.\n\n```python\nfrom google.adk import Workflow\nfrom google.adk.agents import LlmAgent\n```\n\nThere is no wrapper class to import or subclass — the wrapping is internal.\n\n## Basic usage\n\n```python\nwriter = LlmAgent(\n    name='writer',\n    model='gemini-3.5-flash',\n    instruction=\"Write a short story based on the user's prompt.\",\n)\n\nreviewer = LlmAgent(\n    name='reviewer',\n    model='gemini-3.5-flash',\n    instruction='Review the following story and provide feedback.',\n)\n\nagent = Workflow(name='story_pipeline', edges=[('START', writer, reviewer)])\n```\n\n## What the next node receives\n\n| Agent config | `node_input` for the next node |\n|---|---|\n| no `output_schema` | `str` — the model's text parts, concatenated, thoughts excluded |\n| `output_schema=MyModel` | `dict` — the validated model, `model_dump(exclude_none=True)` |\n\n```python\nclass CodeOutput(BaseModel):\n  code: str\n  language: str\n\n\nwriter = LlmAgent(\n    name='writer',\n    model='gemini-3.5-flash',\n    instruction=\"Write code. Return JSON with 'code' and 'language'.\",\n    output_schema=CodeOutput,\n)\n\n\ndef process_code(node_input: dict) -> str:\n  return node_input['code']\n```\n\nSet `output_schema` whenever the downstream node needs fields rather than prose,\nand always when the agent feeds a `JoinNode` — the join parks partial results in\nsession state, and a raw `types.Content` there breaks a database-backed session\nservice.\n\n### Do not assert on `event.output` for an LLM agent's own event\n\nThe wrapper sets `event.output` internally, but the runner clears it on a copy\nbefore the event reaches your loop, so the same text is not rendered twice.\n`event.output` is therefore `None` when you read the agent's own event out of\n`runner.run_async(...)`. Assert on the downstream node's output, on\n`session.state[output_key]`, or on `event.content.parts[*].text` instead.\n\n## Auto-wrapping defaults\n\nAn `LlmAgent` placed in a workflow gets `mode='single_turn'` if `mode` is unset,\n`rerun_on_resume=True`, and its own content branch so parallel agents do not see\neach other's turns. Change the behavior on the agent, not on the wrapper:\n\n```python\n# single_turn (the default here): isolated, no session history\nclassifier = LlmAgent(\n    name='classifier',\n    model='gemini-3.5-flash',\n    instruction='Classify the input as positive, negative, or neutral.',\n    output_schema=ClassificationResult,\n)\n\n# task: multi-turn within the delegated task, supports human-in-the-loop\ntask_agent = LlmAgent(\n    name='task_agent',\n    model='gemini-3.5-flash',\n    mode='task',\n    instruction='Process the request.',\n)\n```\n\n`mode='chat'` is only legal directly after `START` — see the graph validation\nrules in the advanced-patterns reference.\n\n## Instruction as a function\n\nFor an instruction that depends on more than placeholder substitution, pass a\ncallable taking a `ReadonlyContext`:\n\n```python\nfrom google.adk.agents.readonly_context import ReadonlyContext\n\n\ndef build_instruction(ctx: ReadonlyContext) -> str:\n  agents = ctx.state.get('active_agents', [])\n  return f\"Coordinate these agents: {', '.join(agents)}\"\n\n\nagent = LlmAgent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    instruction=build_instruction,\n)\n```\n\n## Storing output in state\n\n`output_key` writes the agent's output into session state, where a later\ninstruction template or a state-bound function parameter can read it:\n\n```python\nagent = LlmAgent(\n    name='writer',\n    model='gemini-3.5-flash',\n    instruction='Write a draft.',\n    output_key='draft',  # lands in state['draft']\n)\n```\n\n## Controlling history\n\n`include_contents='none'` runs the agent without session history, which is what\nyou want for a classifier or extractor that should judge only the current input:\n\n```python\nagent = LlmAgent(\n    name='stateless',\n    model='gemini-3.5-flash',\n    instruction='Process this input independently.',\n    include_contents='none',\n)\n```\n\n## Tools\n\n```python\ndef search_database(query: str) -> str:\n  \"\"\"Search the database for relevant records.\"\"\"\n  return f'Results for: {query}'\n\n\nagent = LlmAgent(\n    name='assistant',\n    model='gemini-3.5-flash',\n    instruction='Help the user with their request.',\n    tools=[search_database],\n)\n```\n\n`tools` accepts plain callables (wrapped as `FunctionTool`), `BaseTool`\ninstances, and `BaseToolset` instances.\n\n## Generation config\n\nModel-level knobs go in `generate_content_config`. Instructions, tools, and\nresponse schema do **not** — set those as agent fields, or they are ignored.\n\n```python\nfrom google.genai import types\n\nagent = LlmAgent(\n    name='creative',\n    model='gemini-3.5-flash',\n    instruction='Write creative stories.',\n    generate_content_config=types.GenerateContentConfig(\n        temperature=0.9,\n        top_p=0.95,\n        max_output_tokens=2048,\n    ),\n)\n```\n\n## Transfer between agents\n\nAn `LlmAgent` with `sub_agents` can hand control to one of them by reasoning\nabout their `description` fields:\n\n```python\nspecialist = LlmAgent(\n    name='specialist',\n    model='gemini-3.5-flash',\n    description='Handles specialized requests.',\n    instruction='Answer specialized questions.',\n)\n\ncoordinator = LlmAgent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    instruction='Route requests to the specialist when needed.',\n    sub_agents=[specialist],\n)\n```\n\n`disallow_transfer_to_parent=True` and `disallow_transfer_to_peers=True` close\noff the return path and sideways moves respectively.\n","isInternal":false,"tokens":1367,"sizeBytes":5609},{"name":"multi-agent.md","path":".agents/skills/adk-agent-builder/references/multi-agent.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/multi-agent.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Multi-Agent Hierarchies\n\nComposing agents when the composition is a tree of agents rather than a graph of\nnodes.\n\n## Chat transfer\n\nGive a coordinator `sub_agents` and the model decides, from their `description`\nfields, when to hand over. Control passes to the sub-agent and comes back the\nsame way.\n\n```python\nfrom google.adk import Agent\n\nresearcher = Agent(\n    name='researcher',\n    description='Researches topics and reports findings.',\n    instruction='You research topics and provide findings.',\n    tools=[search_tool],\n)\n\nwriter = Agent(\n    name='writer',\n    description='Writes prose from research findings.',\n    instruction='You write content based on research.',\n)\n\nroot_agent = Agent(\n    model='gemini-3.5-flash',\n    name='coordinator',\n    instruction='Delegate research to the researcher and writing to the writer.',\n    sub_agents=[researcher, writer],\n)\n```\n\n- Only the root needs `model=`; a sub-agent without one resolves to the nearest\n  `LlmAgent` ancestor's model.\n- The `description` is the only thing the routing model sees, so make each one\n  say what the agent is *for* and how it differs from its peers. Ambiguous\n  descriptions are the usual cause of the wrong agent picking up a request.\n- `disallow_transfer_to_parent=True` blocks the way back;\n  `disallow_transfer_to_peers=True` blocks sideways moves. Both default to\n  `False`, so a sub-agent can normally return control on its own.\n\nFor schema-validated delegation rather than free-form transfer, set\n`mode='task'` or `mode='single_turn'` on the sub-agent — that is a different\nmechanism with its own tool and completion protocol.\n\n## Orchestration agents\n\n> **Deprecated.** `SequentialAgent`, `ParallelAgent`, and `LoopAgent` are all\n> deprecated in favour of `Workflow` and will be removed in a future version.\n> Build new orchestration as a `Workflow` graph instead — the getting-started\n> reference shows the equivalent edge lists. The one thing they still do that\n> `Workflow` cannot: a `Workflow` cannot yet be used as an `LlmAgent`\n> sub-agent, so reach for these only when you need model-driven transfer into\n> an orchestrated block.\n\nThese three run their `sub_agents` without asking a model what to do next.\n\n```python\nfrom google.adk.agents import LoopAgent, ParallelAgent, SequentialAgent\n\n# One after another\nroot_agent = SequentialAgent(\n    name='pipeline',\n    sub_agents=[step1_agent, step2_agent, step3_agent],\n)\n\n# All at once\nroot_agent = ParallelAgent(\n    name='fan_out',\n    sub_agents=[task_a, task_b, task_c],\n)\n```\n\n`LoopAgent` repeats its sub-agents until one calls `exit_loop` or escalates.\n`max_iterations` is optional; without it the only way out is `exit_loop`, so set\none unless a sub-agent reliably calls the tool.\n\n```python\nfrom google.adk.agents import LoopAgent\nfrom google.adk.tools import exit_loop\n\nchecker = Agent(\n    name='checker',\n    tools=[exit_loop],\n    instruction='Check the result and call exit_loop when it is good enough.',\n)\n\nroot_agent = LoopAgent(\n    name='retry_loop',\n    sub_agents=[worker_agent, checker],\n    max_iterations=5,\n)\n```\n\n## Models\n\nThe built-in default when no agent in the chain sets `model=` is\n`LlmAgent.DEFAULT_MODEL`, currently `'gemini-3.5-flash'`. Override the default\nprocess-wide with `LlmAgent.set_default_model('gemini-3.1-pro-preview')`.\n\nNon-Gemini models go through LiteLLM, with the provider as a prefix:\n\n```python\nfrom google.adk.models.lite_llm import LiteLlm\n\nroot_agent = Agent(model=LiteLlm(model='openai/gpt-4o'), ...)\n```\n\n## Common failures\n\n| Symptom | Cause |\n|---|---|\n| A sub-agent takes over and never gives control back | It has no path home; check `disallow_transfer_to_parent` and say in its instruction when to return |\n| The wrong agent answers | Two `description` fields overlap; sharpen the boundary between them |\n| `ImportError` on agent definitions | Circular imports between per-agent modules; define the tree in one `agent.py` or put shared sub-agents in their own module |\n","isInternal":false,"tokens":954,"sizeBytes":3987},{"name":"parallel-and-fanout.md","path":".agents/skills/adk-agent-builder/references/parallel-and-fanout.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/parallel-and-fanout.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Parallel Execution, Fan-Out, and Fan-In\n\nTwo different things share the word \"parallel\": **fan-out** runs several\n*different* nodes on the same input, and a **parallel worker** runs one node\nacross every item of a list. Mixing them up is the most common failure here.\n\n```python\nfrom google.adk import Agent, Workflow\nfrom google.adk.workflow import JoinNode, node\n```\n\n## Fan-out: several nodes, same input\n\nA tuple of targets sends the same output to each of them concurrently:\n\n```python\nagent = Workflow(\n    name='fan_out',\n    edges=[('START', (analyze_text, translate_text, summarize_text))],\n)\n```\n\n## Fan-in: `JoinNode`\n\nA `JoinNode` waits for every predecessor, then fires once with all their outputs\nin a dict keyed by predecessor node name:\n\n```python\njoin = JoinNode(name='collect_results')\n\nagent = Workflow(\n    name='fan_out_fan_in',\n    edges=[\n        ('START', (analyze_text, translate_text, summarize_text)),\n        ((analyze_text, translate_text, summarize_text), join),\n        (join, final_processor),\n    ],\n)\n\n\ndef final_processor(node_input: dict) -> str:\n  # {'analyze_text': ..., 'translate_text': ..., 'summarize_text': ...}\n  return f\"Combined: {node_input['analyze_text']}\"\n```\n\nWhile waiting, the join holds partial inputs in session state. If a predecessor\nis an `LlmAgent` without `output_schema`, what it holds is a `types.Content`,\nwhich a database-backed session service cannot serialize — set `output_schema`\non every LLM agent feeding a join.\n\n## Multi-trigger: fan-out into one shared successor\n\nPoint several branches at one node with no join and that node runs once per\nbranch:\n\n```python\nagent = Workflow(\n    name='root_agent',\n    input_schema=str,\n    edges=[(\n        'START',\n        (make_uppercase, count_characters, reverse_string),\n        send_message,\n    )],\n)\n```\n\n`send_message` fires three times here. A `JoinNode` in the same position would\nfire once with a merged dict. Pick by whether the downstream work is per-branch\nor needs all branches at once.\n\n## Parallel workers: one node, every item of a list\n\n`parallel_worker=True` wraps a node so it receives a list, runs an instance per\nitem concurrently, and emits a list of results in input order.\n\n```python\n@node(parallel_worker=True)\ndef process_item(node_input: int) -> int:\n  return node_input * 2\n\n\ndef produce_list(node_input: str) -> list:\n  return [1, 2, 3, 4, 5]\n\n\nagent = Workflow(\n    name='parallel_processing',\n    edges=[('START', produce_list, process_item)],\n)\n# process_item emits [2, 4, 6, 8, 10]\n```\n\nThere is no `ParallelWorker` class to import; the wrapper is internal.\n\nBehavior worth knowing:\n\n- A non-list input is wrapped in a one-element list rather than rejected.\n- An empty list produces an empty list, and downstream still fires.\n- `rerun_on_resume` is forced to `True`.\n- `max_parallel_workers=N` caps concurrency; unset means unbounded. It must be\n  at least 1.\n\n### On an agent\n\nSet the flag on the `LlmAgent` itself — each item is handled by a clone:\n\n```python\nexplain_topic = Agent(\n    name='explain_topic',\n    instruction='Explain how this topic relates to \"{topic}\".',\n    output_schema=TopicExplanation,\n    parallel_worker=True,\n)\n\nagent = Workflow(\n    name='parallel_analysis',\n    edges=[('START', process_input, find_related_topics, explain_topic, aggregate)],\n)\n```\n\n### Do not put `parallel_worker=True` on a fan-out branch\n\nFan-out edges already run their targets concurrently. Adding the flag makes the\nbranch expect a list and iterate it; handed a single value it iterates once, and\nhanded `None` it emits nothing — so a downstream join waits forever.\n\n## Diamond\n\n```python\njoin = JoinNode(name='merge')\n\n\ndef combiner(node_input: dict) -> str:\n  return f\"{node_input['branch_a']} + {node_input['branch_b']}\"\n\n\nagent = Workflow(\n    name='diamond',\n    edges=[\n        ('START', splitter),\n        (splitter, (branch_a, branch_b)),\n        ((branch_a, branch_b), join),\n        (join, combiner),\n    ],\n)\n```\n\n## `SequentialAgent` and `ParallelAgent`\n\n> **Deprecated.** Both are deprecated in favour of `Workflow` and will be\n> removed in a future version. Use the edge forms above for new code.\n\nShorthands for the two commonest graphs, when you have agents rather than\nfunctions:\n\n```python\nfrom google.adk.agents import ParallelAgent, SequentialAgent\n\n# START -> writer -> reviewer -> editor\npipeline = SequentialAgent(\n    name='pipeline',\n    sub_agents=[writer_agent, reviewer_agent, editor_agent],\n)\n\n# START -> (analyzer, translator, summarizer)\nparallel = ParallelAgent(\n    name='concurrent',\n    sub_agents=[analyzer_agent, translator_agent, summarizer_agent],\n)\n```\n","isInternal":false,"tokens":1121,"sizeBytes":4649},{"name":"routing-and-conditions.md","path":".agents/skills/adk-agent-builder/references/routing-and-conditions.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/routing-and-conditions.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Routing and Conditional Branching\n\nA node emits `Event(route=...)`; the edges leaving that node decide which\nsuccessors fire.\n\n## Dict routing — the default form\n\nMap route values to targets in one edge tuple:\n\n```python\nfrom google.adk import Event, Workflow\n\n\ndef classify(node_input: str):\n  route = 'error' if 'error' in node_input else 'success'\n  return Event(output=node_input, route=route)\n\n\nagent = Workflow(\n    name='router',\n    edges=[\n        ('START', classify),\n        (classify, {'success': handle_success, 'error': handle_error}),\n    ],\n)\n```\n\nThe three-tuple form `(classify, handle_success, 'success')` does the same thing\none target at a time. Reach for it only when a single edge must match several\nroutes (see below), since the dict form maps one route to one target.\n\n## Sequence shorthand\n\nA tuple of more than two elements becomes a chain:\n\n```python\nedges = [('START', step_a, step_b, step_c)]\n# equivalent to [('START', step_a), (step_a, step_b), (step_b, step_c)]\n```\n\nChains and dict routing compose:\n\n```python\nedges = [\n    ('START', process_input, classify),\n    (classify, {'approved': send, 'rejected': discard}),\n]\n```\n\n## Route values\n\nA route is a `str`, `bool`, or `int`, or a list of those.\n\n```python\n(decision, {'approve': path_a, 'reject': path_b})   # strings\n(decision, {True: yes_path, False: no_path})        # booleans\n(decision, {0: path_0, 1: path_1})                  # integers\n```\n\n## Default route\n\n`'__DEFAULT__'` (exported as `DEFAULT_ROUTE`) fires when no other route on that\nnode matches:\n\n```python\nedges = [\n    ('START', classify),\n    (classify, {\n        'success': handler_a,\n        'error': handler_b,\n        '__DEFAULT__': fallback_handler,\n    }),\n]\n```\n\nThe default fans out like any other route, so `'__DEFAULT__': (a, b)` triggers\nboth. `'__DEFAULT__'` may not appear inside a list of routes on one edge — give\nit its own edge.\n\n## One edge, several routes\n\nPassing a list matches any value in it:\n\n```python\nedges = [\n    ('START', classifier),\n    (classifier, {'route_z': handler_b}),\n    (classifier, handler_a, ['route_x', 'route_y']),\n]\n```\n\n## Several routes from one node\n\nA node can emit a list of routes to fire multiple branches at once:\n\n```python\ndef fan_out_router(node_input: str):\n  return Event(output=node_input, route=['path_a', 'path_b'])\n\n\nagent = Workflow(\n    name='multi_route',\n    edges=[\n        ('START', fan_out_router),\n        (fan_out_router, {'path_a': branch_a, 'path_b': branch_b}),\n    ],\n)\n```\n\n## Self-loop\n\n```python\ndef guess_number(target_number: int):\n  guess = random.randint(0, 10)\n  yield Event(message=f'Guessing {guess}...')\n  if guess == target_number:\n    yield Event(message='Correct!')\n  else:\n    yield Event(route='guessed_wrong')\n\n\nagent = Workflow(\n    name='root_agent',\n    edges=[\n        ('START', validate_input, guess_number),\n        (guess_number, {'guessed_wrong': guess_number}),\n    ],\n)\n```\n\n## Revision loop\n\n```python\nedges = [\n    ('START', process_input, draft_email, human_review),\n    (human_review, {\n        'revise': draft_email,\n        'approved': send,\n        'rejected': discard,\n    }),\n]\n```\n\n## Constraints the graph validator enforces\n\n- **A cycle needs at least one routed edge.** An entirely unconditional cycle is\n  rejected at construction time, because nothing could ever break out of it.\n- **Edges leaving `START` may not carry a route.** `START` never runs, so it\n  never emits one.\n- **No two edges may share a source and a target**, even with different routes.\n  To reach one destination from both a named route and `__DEFAULT__`, point the\n  default at a thin wrapper function.\n\n## Unrouted edges always fire\n\nAn edge with no route fires on every output event from its source, whatever\nroute that event carries. So if a node routes at all, give *every* one of its\noutgoing edges a route — otherwise the unrouted one fires alongside the branch\nyou selected.\n\n```python\nedges = [\n    ('START', node_a),  # unconditional\n    (node_a, node_b),   # fires on every node_a output\n]\n```\n","isInternal":false,"tokens":1021,"sizeBytes":4056},{"name":"session-and-state.md","path":".agents/skills/adk-agent-builder/references/session-and-state.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/session-and-state.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Sessions, Artifacts, and Memory\n\nWhere data lives beyond a single node: the session store, binary artifacts, and\nlong-term memory.\n\n## State key scopes\n\nA prefix on the key decides how far the value travels and how long it lives.\n\n| Key form | Scope |\n|---|---|\n| `key` | This session |\n| `app:key` | The whole app — shared across every session and user |\n| `user:key` | This user, across their sessions |\n| `temp:key` | This invocation only; never persisted |\n\nA `state_schema` on a node validates writes, but prefixed keys bypass that\nvalidation.\n\nMutate, do not rebind — `state['key'] = value` records a delta, whereas\n`state = {'key': value}` just rebinds a local name and is lost.\n\n```python\ndef my_tool(tool_context: ToolContext):\n  tool_context.state['user_name'] = 'Alice'\n  tool_context.state['app:feature_flag'] = True\n```\n\nIn parallel branches, two nodes writing the same key race. Give each branch its\nown key, or write to a shared `app:`-scoped key deliberately.\n\n## Session services\n\n| Service | Use for | Import |\n|---|---|---|\n| `InMemorySessionService` | local development and tests | `from google.adk.sessions import InMemorySessionService` |\n| `DatabaseSessionService` | production on SQLite or PostgreSQL | `from google.adk.sessions import DatabaseSessionService` |\n| `VertexAiSessionService` | Vertex AI Agent Engine | `from google.adk.sessions import VertexAiSessionService` |\n\n```python\nfrom google.adk import Runner\nfrom google.adk.sessions import InMemorySessionService\n\nrunner = Runner(\n    agent=root_agent,\n    app_name='my_app',\n    session_service=InMemorySessionService(),\n)\n```\n\n`DatabaseSessionService` needs the `db` extra (`pip install \"google-adk[db]\"`).\nIt also serializes everything you put in state and in a `JoinNode`'s parked\ninputs, so a non-JSON-serializable value that works in memory fails here.\n\n## Artifacts\n\nArtifacts hold bytes — images, files, generated documents — keyed by filename\nand versioned per session.\n\n```python\nfrom google.genai import types\n\n\nasync def save_chart(tool_context: ToolContext):\n  part = types.Part.from_bytes(data=generate_chart(), mime_type='image/png')\n  version = await tool_context.save_artifact('chart.png', part)\n\n\nasync def get_chart(tool_context: ToolContext):\n  part = await tool_context.load_artifact('chart.png')\n  return part.inline_data.data\n```\n\n## Memory\n\nMemory is recall across sessions, as opposed to state, which is recall within\none.\n\n```python\nfrom google.adk.memory.in_memory_memory_service import InMemoryMemoryService\n\nrunner = Runner(\n    agent=root_agent,\n    app_name='my_app',\n    session_service=InMemorySessionService(),\n    memory_service=InMemoryMemoryService(),\n)\n```\n\nAgents reach it through the `load_memory` and `preload_memory` tools, or\ndirectly with `await ctx.search_memory(query)`.\n","isInternal":false,"tokens":668,"sizeBytes":2809},{"name":"state-and-events.md","path":".agents/skills/adk-agent-builder/references/state-and-events.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/state-and-events.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Context and Events\n\nThe two objects a node touches: `Context`, which is how it reads its\nsurroundings, and `Event`, which is how it says anything.\n\n```python\nfrom google.adk import Context, Event\n```\n\n## Getting a `Context`\n\nDeclare a parameter named `ctx`. Nodes that do not need it can omit it.\n\n```python\ndef my_node(ctx: Context, node_input: str) -> str:\n  value = ctx.state.get('key', 'default')\n  return f'{ctx.session.id}: {value}'\n```\n\n## Context properties\n\nAvailable everywhere (a `Context` is also what a callback and a tool receive —\n`CallbackContext` and `ToolContext` are aliases for this same class):\n\n| Property | Type | Notes |\n|---|---|---|\n| `state` | `State` | Delta-aware session state; reads and writes like a dict |\n| `session` | `Session` | Current session, with the workflow's local events merged in |\n| `invocation_id` | `str` | This invocation |\n| `user_id` | `str` | Read-only |\n| `user_content` | `types.Content \\| None` | The message that started the invocation |\n| `agent_name` | `str` | Agent currently running |\n| `run_config` | `RunConfig \\| None` | Read-only |\n| `actions` | `EventActions` | State and artifact deltas being accumulated |\n| `branch` | `str \\| None` | Event-isolation branch |\n| `function_call_id` | `str \\| None` | Set when running as a tool |\n\nMeaningful only inside a workflow node:\n\n| Property | Type | Notes |\n|---|---|---|\n| `node` | `BaseNode \\| None` | The node being executed |\n| `node_path` | `str` | Full path, e.g. `'WorkflowA/node1'` |\n| `run_id` | `str` | This node-run, e.g. `'1'`, `'2'` |\n| `attempt_count` | `int` | 1 on the first try, higher on a retry |\n| `resume_inputs` | `dict[str, Any]` | Human-in-the-loop answers, keyed by `interrupt_id` |\n| `error`, `error_node_path` | `Exception \\| None`, `str` | Set after a node fails |\n\n## Context methods\n\n| Method | Purpose |\n|---|---|\n| `await run_node(node, node_input=None, *, use_as_output=False, run_id=None, use_sub_branch=False, override_branch=None)` | Run a node dynamically; the caller needs `rerun_on_resume=True` |\n| `await save_artifact(filename, part)` / `await load_artifact(filename)` | Session artifacts |\n| `await search_memory(query)` | Long-term memory lookup |\n| `get_auth_response(auth_config)` / `request_credential(auth_config)` | Credentials |\n| `get_invocation_context()` | Escape hatch to the underlying `InvocationContext` |\n\n## Parameters resolved from state\n\nAny function-node parameter that is not `ctx` or `node_input` is looked up in\n`ctx.state` by name, and coerced to its annotation. If the key is absent, the\nparameter's default is used.\n\n```python\n# With state {'user_name': 'Alice', 'threshold': 0.5}\ndef my_node(node_input: str, user_name: str, threshold: float) -> str:\n  return f'{user_name}: {node_input} (threshold={threshold})'\n```\n\nResolution order: `ctx` → `node_input` → `ctx.state[name]` → default.\n\n## Event fields\n\n`Event` extends `LlmResponse`. Three of its constructor arguments are\nconveniences that write somewhere else:\n\n| Constructor argument | Where it lands |\n|---|---|\n| `output=` | `event.output` — data for the next node |\n| `message=` | `event.content` — what the UI renders |\n| `state=` | `event.actions.state_delta` |\n| `route=` | `event.actions.route` |\n\n`message` and `content` are mutually exclusive; passing both raises. `message`\naccepts a string, a `types.Part`, a list of parts, or a `types.Content`.\n\nOther fields you will read: `author`, `content`, `partial`, `branch`,\n`node_info` (with `node_info.path` identifying the emitting node), and\n`is_final_response()`.\n\n## Emitting user-visible messages\n\n```python\nyield Event(message='Processing step 1...')\n\n# Multimodal\nfrom google.genai import types\n\nyield Event(message=[\n    types.Part.from_text(text='Here is the result:'),\n    types.Part.from_bytes(data=image_bytes, mime_type='image/png'),\n])\n\n# Streaming chunks, then the real output\nasync def verbose_node(ctx: Context, node_input: str):\n  yield Event(message='Processing step 1...', partial=True)\n  yield Event(message='Processing step 2...', partial=True)\n  yield Event(output='final result')\n```\n\n## What the workflow itself outputs\n\nAfter every node settles, the workflow emits one more event of its own. Its\nvalue comes from the terminal nodes — those with no outgoing edges. That event\nis authored by the workflow, with `node_info.path` set to the workflow's own\npath, which is why filtering test assertions on `event.author` picks up the\nwrapper rather than the node you meant.\n","isInternal":false,"tokens":1156,"sizeBytes":4496},{"name":"task-mode.md","path":".agents/skills/adk-agent-builder/references/task-mode.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/task-mode.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Task Delegation (`mode='task'` / `mode='single_turn'`)\n\nHand a sub-agent a schema-validated job and get a schema-validated answer back,\ninstead of transferring the whole conversation to it.\n\n## The three modes\n\n`LlmAgent.mode` is `'chat'`, `'task'`, `'single_turn'`, or unset. Unset means\n`'chat'` when the agent is a sub-agent, `'single_turn'` when it is a workflow\nnode.\n\n| Mode | How the parent reaches it | User interaction | How it finishes |\n|---|---|---|---|\n| `chat` | the `transfer_to_agent` tool | full conversation | transfers back |\n| `task` | a tool named after the sub-agent | can ask the user for clarification | calls `finish_task` |\n| `single_turn` | a tool named after the sub-agent | none — told no reply is coming | calls `finish_task` |\n\nThe delegation tool takes the sub-agent's **`name`** verbatim. An agent called\n`researcher` is exposed to the coordinator as a tool called `researcher`, and\nits `description` becomes the tool description, so write the description for a\nmodel deciding whether to call it.\n\n## Task mode\n\n```python\nfrom google.adk import Agent\nfrom pydantic import BaseModel\n\n\nclass ResearchInput(BaseModel):\n  topic: str\n  depth: str = 'standard'\n\n\nclass ResearchOutput(BaseModel):\n  summary: str\n  key_findings: str\n  confidence: str\n\n\ndef search_web(query: str) -> str:\n  \"\"\"Search the web for information.\"\"\"\n  return f'Results for \"{query}\": ...'\n\n\nresearcher = Agent(\n    name='researcher',\n    mode='task',\n    input_schema=ResearchInput,\n    output_schema=ResearchOutput,\n    description='Researches topics using web search and analysis.',\n    instruction=(\n        'Research the given topic with search_web. If the user asks for'\n        ' changes, adjust. When done, call finish_task with summary,'\n        ' key_findings, and confidence.'\n    ),\n    tools=[search_web],\n)\n\nroot_agent = Agent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    sub_agents=[researcher],\n    instruction=(\n        'When the user asks for research, call the researcher tool. Summarize'\n        ' its result for the user.'\n    ),\n)\n```\n\nSequence: the coordinator calls the `researcher` tool with structured input; the\nresearcher works, possibly talking to the user; the researcher calls\n`finish_task` with structured output; the coordinator gets the result.\n\n## Single-turn mode\n\nSame shape, no conversation. The framework appends a nudge to the sub-agent's\ninput telling it no further user replies will arrive, so it must finish from the\ninput alone.\n\n```python\nclass SummaryOutput(BaseModel):\n  summary: str\n  word_count: int\n\n\nsummarizer = Agent(\n    name='summarizer',\n    mode='single_turn',\n    output_schema=SummaryOutput,\n    description='Summarizes documents autonomously.',\n    instruction='Summarize the document with extract_text, then finish_task.',\n    tools=[extract_text],\n)\n\nroot_agent = Agent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    sub_agents=[summarizer],\n    instruction='Delegate summarization to the summarizer tool.',\n)\n```\n\n## Schemas\n\n`input_schema` types the delegation tool's parameters; `output_schema` types\n`finish_task`'s parameters. Both are optional.\n\n```python\nagent = Agent(\n    name='worker',\n    mode='task',\n    input_schema=TaskInput,    # validates the delegation call\n    output_schema=TaskOutput,  # validates the finish_task call\n    ...\n)\n```\n\nWithout them the defaults are a single string each:\n\n```python\n# delegation tool parameters\n{'request': str}   # \"Detailed instructions or context for the task sub-agent.\"\n\n# finish_task parameters\n{'result': str}\n```\n\nA schema violation is not fatal — `finish_task` returns a validation-error\nmessage and the model gets to retry.\n\n## `finish_task`\n\n`mode='task'` attaches a tool called `finish_task` to the sub-agent\nautomatically, and injects an instruction telling the model to complete the work\nbefore calling it. Its parameters come from `output_schema`, or `{'result':\nstr}` when there is none. There is nothing to import or register.\n\n## Mixed modes under one coordinator\n\n```python\nflight_searcher = Agent(\n    name='flight_searcher',\n    mode='task',                # interactive: can discuss options\n    input_schema=FlightSearchInput,\n    output_schema=FlightSearchOutput,\n    description='Searches and books flights interactively.',\n    instruction='Search flights, discuss with the user, then finish_task.',\n    tools=[search_flights, book_flight],\n)\n\nweather_checker = Agent(\n    name='weather_checker',\n    mode='single_turn',         # autonomous\n    output_schema=WeatherOutput,\n    description='Checks weather for a destination.',\n    instruction='Check the weather and call finish_task.',\n    tools=[get_weather],\n)\n\nroot_agent = Agent(\n    name='travel_planner',\n    model='gemini-3.5-flash',\n    sub_agents=[flight_searcher, weather_checker],\n    instruction=(\n        'Plan trips. Use weather_checker for weather and flight_searcher for'\n        ' booking.'\n    ),\n)\n```\n\n## Rules worth knowing\n\n- Only the coordinator needs `model=`; sub-agents inherit it from the nearest\n  `LlmAgent` ancestor.\n- Every delegating sub-agent needs a `description` — it is the entire tool\n  description the coordinator's model sees.\n- The delegation tool is marked as deferring its response and its description\n  tells the model **not** to call it in parallel with other tools. Do not build\n  a prompt that asks for several delegations in one turn.\n- A `mode='chat'` sub-agent gets neither a delegation tool nor `finish_task`; it\n  stays a `transfer_to_agent` target.\n\n## Task mode versus chat transfer\n\n| | chat (`transfer_to_agent`) | task / single_turn |\n|---|---|---|\n| input | free-form conversation | schema-validated |\n| output | free-form conversation | schema-validated |\n| control returns when | the agent transfers back | the agent calls `finish_task` |\n| user interaction | full chat | `task`: multi-turn, `single_turn`: none |\n\n## Where the code lives\n\n| Component | File |\n|---|---|\n| `mode`, `input_schema`, `output_schema` | `src/google/adk/agents/llm_agent.py` |\n| delegation tools, default input schema | `src/google/adk/tools/agent_tool.py` |\n| `finish_task` | `src/google/adk/agents/llm/task/_finish_task_tool.py` |\n| `TaskRequest`, `TaskResult` | `src/google/adk/agents/llm/task/_task_models.py` |\n","isInternal":false,"tokens":1502,"sizeBytes":6277},{"name":"testing.md","path":".agents/skills/adk-agent-builder/references/testing.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/testing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Testing Workflow Agents\n\n`pytest` plus `InMemoryRunner`. Everything below uses the published\n`google-adk` package — no test-internal helpers.\n\n## Setup\n\n```bash\nuv add \"google-adk>=2.0\" pytest pytest-asyncio\n```\n\n```toml\n[tool.pytest.ini_options]\nasyncio_mode = \"auto\"\n```\n\n`asyncio_mode = \"auto\"` saves marking every test `@pytest.mark.asyncio`. Omit it\nif you prefer explicit marks.\n\n## Imports\n\n```python\nimport pytest\nfrom google.adk import Workflow\nfrom google.adk.agents import LlmAgent\nfrom google.adk.apps import App, ResumabilityConfig\nfrom google.adk.events import Event, RequestInput\nfrom google.adk.runners import InMemoryRunner\nfrom google.genai import types\n```\n\n## Two helpers worth having\n\n```python\nasync def run(agent, text='hi', app_name='test_app'):\n    runner = InMemoryRunner(agent=agent, app_name=app_name)\n    session = await runner.session_service.create_session(\n        app_name=app_name, user_id='u1'\n    )\n    msg = types.Content(role='user', parts=[types.Part(text=text)])\n    events = []\n    async for event in runner.run_async(\n        user_id='u1', session_id=session.id, new_message=msg\n    ):\n        events.append(event)\n    return runner, session, events\n\n\ndef node_name(event):\n    \"\"\"'workflow@1/step@1' -> 'step'.\"\"\"\n    if not event.node_info:\n        return None\n    return event.node_info.path.split('/')[-1].split('@')[0]\n```\n\n`event.author` is the *enclosing workflow's* name, not the node's, so filtering\non it silently matches the wrong events. `event.node_info.path` is the one that\nidentifies the node.\n\n## A workflow\n\n```python\nasync def test_simple_workflow():\n    def step_one(node_input: str) -> str:\n        return 'step 1 done'\n\n    def step_two(node_input: str) -> str:\n        return 'step 2 done'\n\n    agent = Workflow(\n        name='test_workflow', edges=[('START', step_one, step_two)]\n    )\n\n    _, _, events = await run(agent)\n    final = [e for e in events if node_name(e) == 'step_two' and e.output][-1]\n    assert final.output == 'step 2 done'\n```\n\n## Routing\n\n```python\nasync def test_routing():\n    def router(node_input: str):\n        route = 'error' if 'error' in node_input else 'success'\n        return Event(output=node_input, route=route)\n\n    agent = Workflow(\n        name='routing_test',\n        edges=[\n            ('START', router),\n            (router, {'success': success_handler, 'error': error_handler}),\n        ],\n    )\n\n    _, _, ok = await run(agent, text='all good')\n    assert any(node_name(e) == 'success_handler' for e in ok)\n\n    _, _, err = await run(agent, text='error case')\n    assert any(node_name(e) == 'error_handler' for e in err)\n```\n\n## Pause and resume\n\n```python\nasync def test_hitl_workflow():\n    async def ask_user(ctx, node_input: str):\n        yield RequestInput(message='Approve?', interrupt_id='ask')\n\n    def after_approval(node_input) -> str:\n        return f'Approved: {node_input}'\n\n    agent = Workflow(\n        name='hitl_test', edges=[('START', ask_user, after_approval)]\n    )\n    app = App(\n        name='hitl_test_app',\n        root_agent=agent,\n        resumability_config=ResumabilityConfig(is_resumable=True),\n    )\n    runner = InMemoryRunner(app=app)\n    session = await runner.session_service.create_session(\n        app_name='hitl_test_app', user_id='u1'\n    )\n\n    msg = types.Content(role='user', parts=[types.Part(text='start')])\n    paused = [\n        e\n        async for e in runner.run_async(\n            user_id='u1', session_id=session.id, new_message=msg\n        )\n    ]\n    fc_events = [e for e in paused if e.get_function_calls()]\n    assert fc_events, 'expected an interrupt function call'\n    fc = fc_events[-1].get_function_calls()[0]\n\n    response = types.Content(\n        role='user',\n        parts=[types.Part(function_response=types.FunctionResponse(\n            id=fc.id, name=fc.name, response={'result': 'yes'},\n        ))],\n    )\n    resumed = [\n        e\n        async for e in runner.run_async(\n            user_id='u1', session_id=session.id, new_message=response\n        )\n    ]\n    final = [e for e in resumed if node_name(e) == 'after_approval'][-1]\n    assert final.output == 'Approved: yes'\n```\n\n## State\n\nPrefer reading the session back after the run over inspecting state mid-flight.\n\n```python\nasync def test_state_management():\n    def writer(node_input: str):\n        return Event(output=node_input, state={'counter': 1})\n\n    def reader(ctx, node_input):\n        return f\"counter={ctx.state['counter']}\"\n\n    agent = Workflow(name='state_test', edges=[('START', writer, reader)])\n\n    runner, session, events = await run(agent)\n    final = [e for e in events if node_name(e) == 'reader' and e.output][-1]\n    assert final.output == 'counter=1'\n\n    after = await runner.session_service.get_session(\n        app_name='test_app', user_id='u1', session_id=session.id\n    )\n    assert after.state['counter'] == 1\n```\n\n## Parallel workers\n\n```python\nfrom google.adk.workflow import node\n\n\nasync def test_parallel_worker():\n    def produce(node_input: str) -> list:\n        return [1, 2, 3]\n\n    @node(parallel_worker=True)\n    def double(node_input: int) -> int:\n        return node_input * 2\n\n    def collect(node_input: list) -> str:\n        return f'results: {node_input}'\n\n    agent = Workflow(\n        name='parallel_test', edges=[('START', produce, double, collect)]\n    )\n\n    _, _, events = await run(agent)\n    final = [e for e in events if node_name(e) == 'collect' and e.output][-1]\n    assert final.output == 'results: [2, 4, 6]'\n```\n\n## Faking the model\n\n`BaseLlm` has exactly one abstract method, so a fake is short:\n\n```python\nfrom google.adk.models.base_llm import BaseLlm\nfrom google.adk.models.llm_response import LlmResponse\n\n\nclass FakeLlm(BaseLlm):\n\n    def __init__(self, *, responses: list[str]):\n        super().__init__(model='fake')\n        self._responses = list(responses)\n\n    async def generate_content_async(self, llm_request, stream=False):\n        yield LlmResponse(content=types.Content(\n            role='model', parts=[types.Part(text=self._responses.pop(0))],\n        ))\n\n\nasync def test_llm_agent_with_fake():\n    agent = LlmAgent(name='x', model=FakeLlm(responses=['ok']), instruction='Help.')\n    _, _, events = await run(agent, text='hi')\n    assert events[-1].content.parts[0].text == 'ok'\n```\n\nTo assert on the request shape instead, `monkeypatch` the agent's\n`canonical_model.generate_content_async`.\n\nDo **not** assert on `event.output` for an LLM agent's own event — the runner\nclears it before you see it. Assert on the downstream node's output, on\n`session.state[output_key]`, or on `event.content.parts[*].text`.\n\n## Tests that hit a real model\n\n```python\nimport os\n\nimport pytest\n\n\n@pytest.fixture(scope='session', autouse=True)\ndef adk_env():\n    if 'GOOGLE_API_KEY' not in os.environ:\n        pytest.skip('GOOGLE_API_KEY not set')\n    os.environ.setdefault('GOOGLE_GENAI_USE_ENTERPRISE', 'FALSE')\n\n\n@pytest.mark.integration\nasync def test_real_model():\n    ...\n```\n\n`pytest -m integration` runs them; `pytest -m \"not integration\"` skips them.\n\n## Habits that avoid flakes\n\n- One `InMemoryRunner` and one session per test — runners carry state.\n- A unique `app_name` per test (`request.node.name` works) so parallel pytest\n  workers do not collide.\n- `event.is_final_response()` filters for \"the agent's last word\".\n- Any LLM agent feeding a `JoinNode` needs `output_schema=`, or the join buffer\n  fails to serialize under `DatabaseSessionService`.\n- `pytest -xvs` while iterating: stop at the first failure, verbose, show prints.\n","isInternal":false,"tokens":1898,"sizeBytes":7548},{"name":"tool-catalog.md","path":".agents/skills/adk-agent-builder/references/tool-catalog.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/references/tool-catalog.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Tool Catalog\n\nEvery way to give an agent a capability, from a plain Python function to a whole\nremote API.\n\n## Python functions\n\nPass callables straight to `tools=`. The name, docstring, and type hints become\nthe schema the model sees, so all three are load-bearing — an undocumented or\nuntyped parameter is invisible to the model.\n\n```python\ndef get_weather(city: str, unit: str = 'celsius') -> str:\n  \"\"\"Get the current weather for a city.\n\n  Args:\n    city: The city name to look up.\n    unit: Temperature unit, 'celsius' or 'fahrenheit'.\n\n  Returns:\n    A string with the weather information.\n  \"\"\"\n  return f'Sunny, 22 degrees {unit} in {city}'\n\n\nroot_agent = Agent(tools=[get_weather], ...)\n```\n\nSync and async both work.\n\n### Getting the context inside a tool\n\nAdd a parameter annotated with `ToolContext` (or `Context` / `CallbackContext` —\nthey are all the same class). It is matched **by annotation**, not by name, and\nexcluded from the schema the model sees. A parameter literally named\n`tool_context` is used as a fallback when no annotation matches.\n\n```python\nfrom google.adk.tools import ToolContext\n\n\nasync def my_tool(query: str, tool_context: ToolContext) -> str:\n  tool_context.state['key'] = 'value'\n  await tool_context.save_artifact('f.txt', part)\n  results = await tool_context.search_memory('q')\n  return 'done'\n```\n\nA parameter named `input_stream` is also excluded, for streaming tools.\n\n## Built-in tools\n\n| Tool | Import from `google.adk.tools` |\n|---|---|\n| `google_search` | Google Search grounding |\n| `url_context` | Fetch and ground on URLs in the prompt |\n| `load_artifacts` | Pull session artifacts into context |\n| `load_memory` / `preload_memory` | Query long-term memory |\n| `exit_loop` | Break out of a `LoopAgent` |\n| `transfer_to_agent` | Hand control to another agent |\n| `get_user_choice` | Ask the user to pick an option |\n| `google_maps_grounding`, `enterprise_web_search` | Other grounding sources |\n\n## Long-running tools\n\n`LongRunningFunctionTool` returns its result asynchronously against the original\n`function_call_id`, which is how an agent pauses for a human.\n\n```python\nfrom google.adk.tools import LongRunningFunctionTool\n\n\ndef approve_expense(amount: float) -> dict:\n  \"\"\"Submit an expense for approval.\"\"\"\n  return {'status': 'pending', 'id': 'exp-123'}\n\n\nroot_agent = Agent(tools=[LongRunningFunctionTool(approve_expense)], ...)\n```\n\n## Workflows and `@node` functions as tools\n\nPass a `Workflow` (with `input_schema` set to a Pydantic `BaseModel`) or a\n`@node`-decorated function straight into `tools=`. The agent runs it in an\nisolated sub-branch (`{tool_name}@{function_call_id}`) and receives the\nterminal output.\n\nSet `ctx.actions.skip_summarization = True` inside the node when its output\nshould be emitted directly as the final user-visible text response without a\nfollow-up LLM summarization turn:\n\n```python\nfrom google.adk import Agent, Context\nfrom google.adk.workflow import node\n\n\n@node\ndef generate_report(project: str, ctx: Context) -> str:\n  \"\"\"Generate a status report for a project.\"\"\"\n  ctx.actions.skip_summarization = True\n  return f'Report for {project}: Ready'\n\n\nroot_agent = Agent(tools=[generate_report], ...)\n```\n\n## MCP servers\n\n```python\nfrom google.adk.tools.mcp_tool import McpToolset, StdioConnectionParams\nfrom mcp import StdioServerParameters\n\nroot_agent = Agent(\n    tools=[\n        McpToolset(\n            connection_params=StdioConnectionParams(\n                server_params=StdioServerParameters(\n                    command='npx',\n                    args=['-y', '@modelcontextprotocol/server-filesystem', '/path'],\n                ),\n                timeout=5,\n            ),\n            tool_filter=['read_file', 'list_directory'],\n        )\n    ],\n    ...\n)\n```\n\nConnection classes: `StdioConnectionParams`, `SseConnectionParams`,\n`StreamableHTTPConnectionParams`.\n\nNeeds `pip install mcp`. `StdioServerParameters` comes from that package, not\nfrom ADK. Use `McpToolset`; the all-caps `MCPToolset` still resolves but warns.\n\n## OpenAPI specs\n\n```python\nfrom google.adk.tools.openapi_tool import OpenAPIToolset\n\ntoolset = OpenAPIToolset(spec_str=open('openapi.yaml').read(), spec_str_type='yaml')\nroot_agent = Agent(tools=[toolset], ...)\n```\n\n`spec_str_type` is `'json'` (the default) or `'yaml'`. Pass `spec_dict=` instead\nto skip parsing. `RestApiTool` from the same module wraps a single endpoint.\n\n## Google API toolsets\n\nGenerated from Google's API discovery documents. `BigQueryToolset`,\n`CalendarToolset`, and their siblings all take the same arguments.\n\n```python\nfrom google.adk.tools.google_api_tool.google_api_toolsets import BigQueryToolset\n\nbigquery = BigQueryToolset(\n    client_id='...',\n    client_secret='...',\n    tool_filter=['bigquery_datasets_list'],\n)\n```\n\nAlso accepted: `service_account=` instead of the OAuth pair, and\n`tool_name_prefix=` to namespace the generated tool names.\n\n## Code execution\n\nThe code executor is its own agent field, not a tool.\n\n```python\nfrom google.adk.code_executors.built_in_code_executor import BuiltInCodeExecutor\n\nroot_agent = Agent(code_executor=BuiltInCodeExecutor(), ...)\n```\n\n## Custom `BaseTool`\n\n```python\nfrom google.adk.tools import BaseTool\nfrom google.genai import types\n\n\nclass MyTool(BaseTool):\n\n  def __init__(self):\n    super().__init__(name='my_tool', description='Does something.')\n\n  def _get_declaration(self):\n    return types.FunctionDeclaration(\n        name=self.name,\n        description=self.description,\n        parameters_json_schema={\n            'type': 'object',\n            'properties': {'param': {'type': 'string'}},\n            'required': ['param'],\n        },\n    )\n\n  async def run_async(self, *, args, tool_context):\n    return {'result': args['param']}\n```\n\n## Custom `BaseToolset`\n\nA toolset supplies tools dynamically, so the set can depend on context.\n\n```python\nfrom google.adk.tools.base_toolset import BaseToolset\n\n\nclass MyToolset(BaseToolset):\n\n  def __init__(self):\n    super().__init__(tool_filter=None, tool_name_prefix='my')\n\n  async def get_tools(self, readonly_context=None):\n    return [ToolA(), ToolB()]\n\n  async def process_llm_request(self, *, tool_context, llm_request):\n    llm_request.append_instructions(['Custom instruction'])\n```\n\n`tool_filter` is a list of tool names or a `ToolPredicate` callable;\n`tool_name_prefix` renames every tool the toolset returns, which is how you keep\ntwo toolsets from colliding.\n","isInternal":false,"tokens":1540,"sizeBytes":6433},{"name":"SKILL.md","path":".agents/skills/adk-agent-builder/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-agent-builder/SKILL.md","title":"adk-agent-builder","category":"anthropic-skill","format":"markdown","content":"---\nname: adk-agent-builder\ndescription: >-\n  Builds ADK (Agent Development Kit) Python agents: LLM agents with tools,\n  graph workflows of function and agent nodes, conditional routing, fan-out and\n  join, schema-validated delegation between agents, human-in-the-loop pauses,\n  and pytest coverage for all of it. Use when asked to create an agent or a\n  workflow, add a tool to one, branch or loop between nodes, run steps in\n  parallel, pause for user approval, or test an agent. Don't use for explaining\n  how ADK works internally or designing its core components (use\n  `adk-architecture`), for an agent that already runs but misbehaves (use\n  `adk-debug`), for authoring a sample under `contributing/` (use\n  `adk-sample-creator`), or for naming, typing, and formatting conventions (use\n  `adk-style`).\n---\n\n# ADK Agent Builder\n\nRead only the reference that matches the task. Loading the whole tree costs\ncontext and buries the part that matters.\n\nEvery API below was checked against `google-adk` 2.6.2. If a symbol is missing\nat runtime, read the source under `src/google/adk/` rather than guessing a\nneighbouring name.\n\n## Start here\n\n| Task | Reference |\n|---|---|\n| First agent, environment, `adk` CLI | [getting-started.md](references/getting-started.md) |\n| Which import path is the canonical one | [import-paths.md](references/import-paths.md) |\n| The rules that cause most runtime failures | [best-practices.md](references/best-practices.md) |\n\n## Building blocks\n\n- [tool-catalog.md](references/tool-catalog.md) — function tools, MCP, OpenAPI,\n  Google API toolsets, built-in tools, custom `BaseTool` and `BaseToolset`.\n- [function-nodes.md](references/function-nodes.md) — plain functions as nodes:\n  parameter resolution, generators, `node_input` typing rules.\n- [llm-agent-nodes.md](references/llm-agent-nodes.md) — an `LlmAgent` used as a\n  workflow node: output types, instruction templates, `output_schema`,\n  auto-wrapping behavior.\n- [task-mode.md](references/task-mode.md) — `mode='task'` and\n  `mode='single_turn'` delegation with schema-validated input and output.\n\n## Graph orchestration\n\n- [routing-and-conditions.md](references/routing-and-conditions.md) — routed\n  edges, dict routing maps, default routes, self-loops, revision loops.\n- [parallel-and-fanout.md](references/parallel-and-fanout.md) — fan-out edges,\n  `JoinNode` fan-in, `parallel_worker=True` list processing.\n- [dynamic-nodes.md](references/dynamic-nodes.md) — scheduling nodes at runtime\n  with `ctx.run_node()` and imperative workflow construction.\n- [human-in-the-loop.md](references/human-in-the-loop.md) — `RequestInput`,\n  resume behavior, resumable vs replayed sessions.\n- [advanced-patterns.md](references/advanced-patterns.md) — nested workflows,\n  retries, custom `BaseNode` subclasses, graph validation rules.\n- [multi-agent.md](references/multi-agent.md) — chat-transfer hierarchies, and\n  the deprecated `SequentialAgent` / `LoopAgent` / `ParallelAgent` shells that\n  `Workflow` replaces.\n\n## Runtime and verification\n\n- [state-and-events.md](references/state-and-events.md) — the `Context` object,\n  `Event` fields, and how state flows between nodes.\n- [session-and-state.md](references/session-and-state.md) — session services,\n  artifacts, memory, and state key scoping.\n- [callbacks-and-plugins.md](references/callbacks-and-plugins.md) — the six\n  agent callbacks and app-level plugins.\n- [testing.md](references/testing.md) — `pytest` with `InMemoryRunner`, faking a\n  model, asserting on node output.\n","frontmatter":{"name":"adk-agent-builder","description":"Builds ADK (Agent Development Kit) Python agents: LLM agents with tools, graph workflows of function and agent nodes, conditional routing, fan-out and join, schema-validated delegation between agents, human-in-the-loop pauses, and pytest coverage for all of it. Use when asked to create an agent or a workflow, add a tool to one, branch or loop between nodes, run steps in parallel, pause for user approval, or test an agent. Don't use for explaining how ADK works internally or designing its core components (use `adk-architecture`), for an agent that already runs but misbehaves (use `adk-debug`), for authoring a sample under `contributing/` (use `adk-sample-creator`), or for naming, typing, and formatting conventions (use `adk-style`)."},"isInternal":false,"tokens":880,"sizeBytes":3543},{"name":"api-principles.md","path":".agents/skills/adk-architecture/references/api-principles.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/api-principles.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# API Principles\n\nGuidelines for designing and maintaining the ADK public API surface.\n\n## Public API Surface\n\nThe public API surface of ADK includes:\n- All public classes, methods, and functions in the `google.adk` namespace.\n- The names, required parameters, and expected behavior of all built-in Tools.\n- The structure and schema of persisted data (Sessions, Memory, Evaluation datasets).\n- The JSON request/response format of the ADK API server.\n- The command-line interface (CLI) commands, arguments, and flags.\n- The expected file structure for agent definitions (e.g., `agent.py` convention loaded by CLI).\n\n## Design Principles\n\n### 1. Stability and Backward Compatibility\n- ADK adheres to Semantic Versioning 2.0.0.\n- Any change that forces a developer to alter their existing code to upgrade is a **breaking change** and necessitates a MAJOR version bump.\n- Avoid breaking changes whenever possible by using optional parameters and deprecation cycles.\n- **Session, Artifact, and Memory Service Changes**:\n  Adding any new field to `Session`, `ArtifactVersion`, `MemoryEntry`, or to their associated service base classes (`BaseSessionService`, `BaseArtifactService`, `BaseMemoryService`) and database schemas is strictly considered a **breaking change** (even if the new field is optional or provides a default value).\n  - *Rationale*: There are numerous downstream implementations and custom subclasses across diverse storage backends (e.g. relational databases, key-value stores, document databases, external cloud backends). Downstream storage implementations do not automatically persist, deserialize, or round-trip fields they were not compiled or migrated to handle, leading to silent state loss or deserialization errors when sessions or memories are reloaded.\n\n### 2. Self-Containment\n- Each package should be as self-contained as possible to reduce coupling.\n- Within the ADK framework, importing from a package's `__init__.py` is **not allowed**. Import from the specific module directly.\n\n### 3. Explicit Exports\n- The public API of a package must be explicitly exported in `__init__.py`.\n- **Only public names** should be imported into `__init__.py`. This keeps `__init__.py` minimal and prevents accidental exposure of internal implementation details.\n\n### 4. Intuitive Naming\n- Public method and class names should be concise and intuitive.\n- Private method names can be longer and more self-explanatory to reduce the need for comments.\n\n#### Examples\n\n**Public Naming**\n- **Good**: `Runner.run()`, `Session.get_events()`\n- **Bad**: `Runner.orchestrate_agent_invocation_loop()`, `Session.retrieve_all_events_from_storage()`\n\n**Private Naming**\n- **Good**: `_prepare_context_for_llm()`, `_should_trim_history()`\n- **Bad**: `_prep()`, `_trim()`\n","isInternal":false,"tokens":595,"sizeBytes":2766},{"name":"architecture-checkpoint-resume.md","path":".agents/skills/adk-architecture/references/architecture-checkpoint-resume.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/architecture-checkpoint-resume.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Checkpoint and Resume Lifecycle\n\nHITL (Human-in-the-Loop) follows this pattern:\n\n1. **Interrupt**: Node yields an event with `long_running_tool_ids`.\n   Each ancestor propagates the interrupt upward via `ctx.interrupt_ids`.\n2. **Persist**: Only the leaf node's interrupt event is persisted to\n   session. Workflow sets `ctx._interrupt_ids` directly (no internal\n   event needed).\n3. **Resume**: User sends a `FunctionResponse` message. The Runner\n   scans session events to find the matching `invocation_id`, then\n   reconstructs node state from persisted events.\n4. **Continue**: The interrupted node receives the FR and continues\n   execution. Downstream nodes receive the resumed node's output.\n\n## run_id on resume\n\nResumed nodes reuse the same `run_id` from the original\nexecution. From the node's perspective, the execution never paused\n— events before and after the resume share the same run_id.\n\nFresh dispatches (first run, loop re-trigger) get a new run_id.\n\n## Resume behavior by `rerun_on_resume`\n\nA node with multiple interrupt IDs may receive partial FRs (only\nsome resolved). The behavior depends on `rerun_on_resume`:\n\n**`rerun_on_resume=True`** (Workflow, orchestration nodes):\n\n| FRs received | Status | Behavior |\n|---|---|---|\n| Partial | PENDING | Re-execute immediately with partial `resume_inputs`. Node handles remaining interrupts internally (e.g., Workflow dispatches resolved children, keeps unresolved as WAITING). |\n| All | PENDING | Re-execute with all `resume_inputs`. |\n\nThis is critical for Workflow — when one child's FR arrives, it\nre-runs immediately to dispatch that resolved child. It doesn't\nwait for all children's FRs.\n\n**`rerun_on_resume=False`** (leaf nodes, simple HITL):\n\n| FRs received | Status | Behavior |\n|---|---|---|\n| Partial | WAITING | Stay waiting. Need all FRs. |\n| All | COMPLETED | Auto-complete. Output = aggregated `resolved_responses`. No re-execution. |\n\n## Resume with prior output and interrupts\n\nA node can produce output AND interrupt in the same execution (e.g.,\na Workflow where child A completes with output and child B interrupts).\nOn resume:\n\n- Some interrupt IDs are resolved (provided in `resume_inputs`)\n- Remaining interrupt IDs carry forward via `prior_interrupt_ids`\n- Prior output carries forward via `prior_output`\n- NodeRunner pre-populates ctx with these values before re-executing\n\n```python\nrunner = NodeRunner(\n    node=node, parent_ctx=ctx,\n    run_id=prior_run_id,  # reuse\n    prior_output=cached_output,\n    prior_interrupt_ids={'fc-2'},  # still unresolved\n)\nchild_ctx = await runner.run(\n    node_input=input,\n    resume_inputs={'fc-1': response},\n)\n```\n","isInternal":false,"tokens":615,"sizeBytes":2650},{"name":"architecture-context.md","path":".agents/skills/adk-architecture/references/architecture-context.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/architecture-context.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Context\n\n## Two scoping objects\n\n- **InvocationContext** — one per invocation. Holds shared state (session,\n  services, event queue) reachable from every node.\n- **Context** — one per node execution. Holds per-node results (output, route,\n  interrupt IDs) and is the API surface node code actually touches.\n\nEvery Context references the same InvocationContext (`_invocation_context`).\nService access (artifacts, memory, auth) is delegated through it.\n\n```text\nRoot Context                      ← created by Runner from the InvocationContext\n└── Context [runner.node]         ← the root node (e.g., Workflow)\n    ├── Context [child_a]         ← child node A\n    └── Context [child_b]         ← child node B\n        └── Context [grandchild]  ← nested child\n```\n\nThe Runner creates the root Context and passes it as `parent_ctx` to the root\nnode's `NodeRunner`. The root Context exists only to be that parent; it carries\nno node path of its own.\n\nInvocationContext holds:\n\n- `session`, `agent`, `user_content`, `branch`\n- `invocation_id`, `app_name`, `user_id`\n- Services: `artifact_service`, `memory_service`, `credential_service`\n- `run_config`, `live_request_queue`\n- `is_resumable` — derived from the app's `ResumabilityConfig`\n- `_event_queue` — private queue drained by the Runner's main loop. Nodes never\n  touch it directly; they go through `ic._enqueue_event(event)`.\n\n## 1:1 node-context mapping\n\nEvery node execution gets its own Context. The relationship is strictly 1:1,\nso the Context tree mirrors the node execution tree.\n\n`NodeRunner._create_child_context()` builds the child from the parent. The\nchild inherits:\n\n- `_invocation_context` — the same object, so session and services are shared\n  (copied with `model_copy` when the child runs on a sub-branch)\n- `node_path` — parent path plus `name@run_id`\n- `run_id` — a sequential counter string per node path, reused on resume\n- `event_author`\n- the enclosing Workflow's dynamic-node scheduler\n\nThe child does not inherit output, route or interrupt IDs — those are\nper-execution results and start empty, unless a resume carries them forward via\n`prior_output` / `prior_interrupt_ids`.\n\n## Node result properties\n\nThese are how a node hands results back to its parent:\n\n- **`ctx.output`** — the node's result. Set once per execution, either by\n  `yield value` (the framework assigns it) or by `ctx.output = X`. A second\n  write raises `ValueError`.\n- **`ctx.route`** — routing value for conditional edges, `RouteValue` or a\n  list of them. Independent of output. Workflow-specific.\n- **`ctx.interrupt_ids`** — read-only set. The framework fills it when the node\n  yields an Event carrying `long_running_tool_ids`; the getter returns a copy.\n\nOutput and interrupts can coexist in one execution — a Workflow whose child A\nfinished while child B is waiting has both. The orchestrator's `_finalize`\ndecides what to propagate upward.\n\n## Class hierarchy\n\n```text\nReadonlyContext\n  └── Context\n```\n\n**ReadonlyContext** — read-only view handed to callbacks and plugins:\n`user_content`, `invocation_id`, `agent_name`, `session`, `user_id`,\n`run_config`, and `state` as an immutable `MappingProxyType`.\n\n**Context** — full read-write context for node execution.\n\n## Property reference\n\n| Category | Members |\n|---|---|\n| State & actions | `state` (mutable `State`), `actions` (`EventActions`), `custom_metadata` |\n| Node results | `output`, `route`, `interrupt_ids` (read-only) |\n| Node identity | `node`, `parent_ctx`, `node_path`, `run_id`, `attempt_count`, `resume_inputs` |\n| Scoping | `branch`, `isolation_scope` |\n| Errors | `error`, `error_node_path` — set by NodeRunner when the node fails and retries are exhausted |\n| Telemetry | `telemetry_context` |\n| Session | `session`, `get_invocation_context()` |\n| Child execution | `run_node()` |\n| Artifacts | `load_artifact()`, `save_artifact()`, `get_artifact_version()`, `list_artifacts()` |\n| Memory | `search_memory()`, `add_session_to_memory()`, `add_events_to_memory()`, `add_memory()` |\n| Auth | `request_credential()`, `load_credential()`, `save_credential()`, `get_auth_response()` |\n| Tools | `request_confirmation()`, `tool_confirmation`, `function_call_id` |\n| UI | `render_ui_widget()` |\n","isInternal":false,"tokens":1040,"sizeBytes":4280},{"name":"architecture-llm-context-orchestration.md","path":".agents/skills/adk-architecture/references/architecture-llm-context-orchestration.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/architecture-llm-context-orchestration.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# LLM context orchestration from events\n\n## Source versus view\n\nThe event stream and the LLM context are not the same thing:\n\n- **Events are the ground truth.** Immutable records of what happened — user\n  messages, model responses, tool calls, results. They are the audit log and\n  the persistence state.\n- **LLM context is an orchestrated view.** What gets sent to a model is not a\n  dump of the event log. It is filtered and transformed for the role, task and\n  branch of the agent currently running.\n\nTreating the two as interchangeable is the root of most \"why did the model see\nthat?\" bugs.\n\n## Delegation\n\nA coordinator hands work to a sub-agent with the `transfer_to_agent` tool,\nwhich sets `actions.transfer_to_agent` on the event rather than calling the\nsub-agent inline. The alternative is `AgentTool`, which wraps an agent so it is\ninvoked as an ordinary tool and returns its result to the caller.\n\nEither way the sub-agent does not inherit the coordinator's full transcript\nverbatim; it is given the task framing plus whatever history its branch\nexposes.\n\n## Branch isolation\n\nEvents from every node and branch share one session, in chronological order.\nIsolation comes from the `branch` field, a dot-separated path built by\n`_BranchPath`: `create_sub_branch('parent', name='child', run_id='1')` yields\n`'parent.child@1'`. A node running on a sub-branch sees only events on its own\npath, which is what keeps parallel siblings from polluting each other.\n\n`Event.isolation_scope` is a separate, coarser tag — NodeRunner stamps it from\n`ctx.isolation_scope` when the event does not carry one already.\n\n## History trimming and compaction\n\nLong histories overflow the context window and drag stale retry loops back into\nthe prompt. When the app sets `events_compaction_config`, the Runner compacts\nevents after the invocation. Because compaction rewrites aged history, do not\nstore transient status on an event and expect to read it back later.\n","isInternal":false,"tokens":439,"sizeBytes":1956},{"name":"architecture-node-runner.md","path":".agents/skills/adk-architecture/references/architecture-node-runner.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/architecture-node-runner.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# NodeRunner\n\n`NodeRunner` is the per-node executor. It creates the child Context, drives\n`BaseNode.run()`, opens the node's span, enriches and enqueues events, retries\non failure, and returns the child Context to the caller.\n\n## Two communication channels\n\n- **Context** — parent ↔ child. Output, route, state, resume inputs and\n  interrupt IDs flow through `ctx`. The orchestrator reads `ctx` after the\n  child finishes to decide what happens next.\n- **Event** — persistence and streaming. Events are appended to the session and\n  streamed to the caller. They carry message content, state deltas, function\n  calls and interrupt markers.\n\nA node writes to `ctx` to talk to its parent. It yields Events to persist data\nand stream to the user.\n\n## Execution flow\n\n```text\nOrchestrator\n  │\n  ├─ NodeRunner(node=child, parent_ctx=ctx)\n  │    │\n  │    ├─ _create_child_context()       → child Context (attempt_count)\n  │    ├─ start_as_current_node_span()  → ctx._telemetry_context\n  │    ├─ _execute_node()               → iterate node.run()\n  │    │    ├─ _track_event_in_context()    → write results to ctx\n  │    │    └─ _enqueue_event()             → enrich + persist\n  │    ├─ _flush_output_and_deltas()    → emit deferred output/route/deltas\n  │    └─ return child ctx\n  │\n  └─ reads ctx.output, ctx.route, ctx.interrupt_ids, ctx.error\n```\n\n1. **Create child Context.** Shares the InvocationContext, builds `node_path`\n   from the parent, assigns `run_id`, records `attempt_count`. If the session\n   already holds events for this node path, resolved responses are rehydrated\n   into `ctx._resume_inputs` before the node runs.\n\n2. **Open the span** via `node_tracing.start_as_current_node_span`, storing the\n   resulting `TelemetryContext` on `ctx`. Opening it inside the `try` is\n   deliberate — exceptions are recorded on the span.\n\n3. **Iterate `node.run()`.** For each yielded Event:\n\n   - **Track in context** — `_track_event_in_context` copies output, route and\n     `long_running_tool_ids` onto `ctx`, which is the source of truth. Route\n     and `transfer_to_agent` are only picked up from *native* events (no\n     author, or authored by this node), so a composite parent does not\n     re-bubble a decision its own sub-agent already handled. The event ID is\n     also registered on the node's span.\n   - **Enrich** — `_enrich_event` stamps `author` (`ctx.event_author` or the\n     node name), `invocation_id`, `node_info.path`, `branch`, and\n     `isolation_scope`. For an event carrying output it also sets\n     `node_info.output_for` to this node path plus its output ancestors.\n     `node_info.run_id` is **not** stamped separately — it is a property\n     derived from the last segment of `node_info.path` (`wf@1/child@2`).\n   - **Flush deltas** — for non-partial events, pending state and artifact\n     deltas move from `ctx.actions` onto the event.\n   - **Enqueue** — `ic._enqueue_event(event)` puts it on the shared queue for\n     session persistence.\n\n4. **Flush deferred output.** If `ctx.output` or `ctx.route` were set directly\n   rather than yielded, `_flush_output_and_deltas` emits one final Event after\n   `_run_impl` returns, bundling any remaining deltas onto it.\n\n5. **Return the child ctx.** The orchestrator reads `output`, `route`,\n   `interrupt_ids` and `error`.\n\n## Timeouts, retries and errors\n\n- `node.timeout` wraps the iteration in `asyncio.wait_for`; a timeout raises\n  `NodeTimeoutError`.\n- On any exception, NodeRunner enqueues an Event with `error_code` and\n  `error_message`, then consults `node.retry_config`. A retry sleeps for the\n  configured delay, increments `attempt_count` and rebuilds the child Context\n  from scratch. Retry count is **not** persisted, so it does not survive a\n  resume.\n- A child started with `ctx.run_node()` that fails reaches the node as\n  `DynamicNodeFailError`. The child's runner already emitted the error event,\n  so NodeRunner only matches the retry policy against the child's error. A\n  node with a `retry_config` copies the run-id counters under it at the start\n  of every attempt (`_snapshot_dynamic_children`), and a retry for such a\n  failure restores that copy (`_reset_dynamic_children_for_retry`). The retry\n  then hands out the same run ids and replays the children the failed attempt\n  completed, at any depth. A retry for the node's own exception keeps the\n  counters, so its children run again.\n- A parallel worker (`_ParallelWorker`, or a `Node` with\n  `parallel_worker=True`) is not retried for an item's `DynamicNodeFailError`\n  (`_delegates_item_retry`): the item node already applied the retry policy\n  to that item.\n- A `Workflow` reports a failing child by setting `ctx.error` rather than\n  raising, and NodeRunner applies the retry policy to that error as well.\n- When retries are exhausted, the error is recorded on `ctx.error` and\n  `ctx.error_node_path` and the Context is returned normally — NodeRunner does\n  not re-raise to the orchestrator.\n- `NodeInterruptedError` from a dynamic child is swallowed here: the child's\n  interrupt IDs are already on `ctx`, so the caller just reads\n  `ctx.interrupt_ids`.\n- When `abort_signal` is triggered (`ic.is_aborted` is `True`), active node\n  tasks are cancelled without retrying. In `_node_runner_utils`, the outer\n  workflow runner loop catches the cancellation, calls\n  `runner._synthesize_abort_events_if_needed(ic)` to emit `INVOCATION_ABORTED`\n  events (and seal any dangling `FunctionCall`s), and finishes the generator\n  cleanly so `after_run` callbacks still run.\n\n## Output delegation (`use_as_output`)\n\nWhen a child is scheduled with `use_as_output=True`, its output Event also\ncounts as the parent's output. NodeRunner sets `ctx._output_delegated`, drops\nthe output field from the parent's own event (keeping the event if it still\ncarries deltas), stamps `node_info.output_for` with the ancestor paths, and\npublishes non-tool output events on the outermost explicit claiming ancestor's\nbranch (leaving tool responses such as `finish_task` paired on the child's\nbranch).\n","isInternal":false,"tokens":1488,"sizeBytes":6118},{"name":"architecture-observability.md","path":".agents/skills/adk-architecture/references/architecture-observability.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/architecture-observability.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Observability\n\nNode tracing lives in `telemetry/node_tracing.py`. Spans are **scope-based**:\n`NodeRunner` opens one around each node execution with an async context\nmanager, so the span closes on the way out even if the node raises.\n\n```python\nasync with node_tracing.start_as_current_node_span(parent_ctx, node) as tel_ctx:\n  ctx._telemetry_context = tel_ctx\n  await self._execute_node(ctx, node_input)\n```\n\n## TelemetryContext, not a raw span\n\nEach `Context` exposes `ctx.telemetry_context`, a frozen dataclass:\n\n| Member | Purpose |\n|---|---|\n| `otel_context` | OTel context holding the current span. Passed to children so their spans parent correctly. |\n| `add_event(event)` | Records an event ID as belonging to this node's span. |\n\nThe parent's `otel_context` is what makes the span tree mirror the node tree —\n`start_as_current_node_span` passes `context.telemetry_context.otel_context` as\nthe explicit parent rather than relying on whatever OTel considers current.\nInside the span the child builds a fresh `TelemetryContext` from\n`context_api.get_current()`.\n\nWhen attaching OTel context by hand (`context_api.attach()`), pair it with\n`detach()` in a `finally` — an unbalanced attach leaks the context into\nwhatever coroutine runs next on the loop.\n\n## Which span a node gets\n\n`start_as_current_node_span` dispatches on node type:\n\n| Node type | Span |\n|---|---|\n| `BaseAgent` subclass | none of its own — passes through, the agent emits its own `invoke_agent {name}` span |\n| `Workflow` | `invoke_workflow {name}` |\n| anything else | `invoke_node {name}` |\n\n`invoke_agent` follows OpenTelemetry semantic conventions v1.36 for backward\ncompatibility; `invoke_workflow` follows v1.41; `invoke_node` is not in any\nsemconv release yet.\n\n## Attributes\n\n| Attribute | Set on | Value |\n|---|---|---|\n| `gen_ai.operation.name` | all | `\"invoke_workflow\"` / `\"invoke_node\"` |\n| `gen_ai.conversation.id` | all | `ctx.session.id` |\n| `gen_ai.workflow.name` | workflow | the workflow's `name`, when non-empty |\n| `gen_ai.workflow.nested` | workflow | `True` only for a nested workflow; the entrypoint workflow omits the attribute entirely |\n| `gcp.vertex.agent.associated_event_ids` | all | IDs collected via `tel_ctx.add_event()`, stamped on span close when non-empty and the span is recording |\n| `adk.experimental.response.source` | `execute_tool`, `call_llm` | Callback provenance when a callback or plugin answered or replaced the result and `ADK_EXPERIMENTAL_TELEMETRY=true` is set (`\"before_tool_callback\"`, `\"after_tool_callback\"`, `\"on_tool_error_callback\"` on `execute_tool`; `\"before_model_callback\"`, `\"on_model_error_callback\"` on `call_llm`); omitted when the tool or model itself answered |\n\nNesting is detected through an OTel context key set by the first workflow in\nthe invocation. Because the key rides on the propagated `otel_context`, an\nagent-as-tool that spins up its own runner still reports `nested=true`.\n\n## Metrics\n\nRecorded from `telemetry/_metrics.py` under the `gcp.vertex.agent` meter:\n\n| Function | Recorded when |\n|---|---|\n| `record_workflow_invocation_duration` | an `invoke_workflow` span closes, tagged with `nested` and any error |\n| `record_agent_invocation_duration` | an agent invocation completes |\n| `record_invoke_agent_inference_calls` | per agent, count of model calls |\n| `record_invoke_agent_tool_calls` | per agent, count of tool calls |\n| `record_tool_execution_duration` | a tool finishes |\n| `record_client_operation_duration`, `record_client_token_usage` | model client calls |\n\n## Python logging\n\nUse the `google_adk` logger namespace so callers can filter ADK output:\n\n```python\nlogger = logging.getLogger(\"google_adk.\" + __name__)\n\nlogger.debug(\"node %s started.\", ctx.node_path)\n```\n\nUse `%`-style arguments, not f-strings — the formatting is then skipped\nentirely when the level is disabled.\n\n`NodeRunner` already logs node start, node end, execute-loop boundaries,\nrehydrated resume inputs, retries (`warning`) and unhandled exceptions\n(`logger.exception`). Do not re-log those from inside a node.\n","isInternal":false,"tokens":974,"sizeBytes":4065},{"name":"architecture-runner-roles.md","path":".agents/skills/adk-architecture/references/architecture-runner-roles.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/architecture-runner-roles.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Runner vs NodeRunner vs Workflow\n\nThese three are deliberately separate:\n\n- **Runner** = lifecycle orchestrator (InvocationContext, session,\n  plugins, invocation boundaries)\n- **NodeRunner** = task scheduler (asyncio tasks, node execution,\n  completions)\n- **Workflow** = graph engine (edges, traversal, node sequencing)\n\nMerging Runner and NodeRunner would deadlock on nested workflows\n(inner workflow's NodeRunner would block the outer's Runner).\n","isInternal":false,"tokens":93,"sizeBytes":452},{"name":"architecture-workflow-resumability.md","path":".agents/skills/adk-architecture/references/architecture-workflow-resumability.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/architecture-workflow-resumability.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Workflow resumability: model and direction\n\nHow a `Workflow` node preserves and restores execution state across a\nhuman-in-the-loop pause, how that compares to peer agent frameworks, and where\nit is heading. This is the whole-workflow view; the interrupt/resume lifecycle\nof a single node is covered separately.\n\nThe first thing to be clear about: a `Workflow` reconstructs its progress from\nthe session event stream on every run, so it resumes whether or not resumability\nis configured. The `is_resumable` flag does not switch resume on or off. What it\nswitches on is **durability** — persisting loadable checkpoints and letting an\ninvocation be continued across separate runner calls.\n\n## Current model\n\n### Resume is always on: reconstruction by event replay\n\nOn every run, the workflow scans the current invocation's session events and\nrebuilds its in-memory loop state (which nodes completed, their outputs, which\nare still waiting on interrupts). Completed nodes are fast-forwarded from their\nrecovered output rather than re-executed; the interrupted node re-runs with the\nsupplied responses.\n\nThis path (`_run_impl` -> `ReplayManager.scan_workflow_events`) has no\n`is_resumable` guard — the scan matches events purely by invocation id. The loop\nstate is not persisted and the checkpoint the workflow writes is not read back\non the load path; the session event log is the source of truth. So within an\ninvocation, a workflow is inherently replay-resumable, flag or no flag.\n\n### What `is_resumable` actually adds: durability\n\n`ResumabilityConfig.is_resumable` is a durability switch. When it is set:\n\n1.  **Cross-call resume.** The runner will continue an existing invocation\n    without a fresh user message and set up a \"resumed invocation\" context,\n    rehydrating the recorded agent state and end-of-agent markers from history.\n    With the flag off, the runner requires a new message and starts a fresh\n    invocation instead.\n2.  **Checkpoint markers in the log.** Composite agents (`LlmAgent`,\n    `SequentialAgent`, `LoopAgent`, `ParallelAgent`, and `LlmAgent`s wrapped as\n    workflow nodes) write `agent_state` / `end_of_agent` events into the session\n    only when the invocation is resumable. These make progress loadable across a\n    process boundary.\n3.  **Function-response routing.** Routing an incoming function response back to\n    its originating invocation is enabled only when resumable.\n\nThe config's own definition is durability-shaped: pause an invocation on a\nlong-running call, and resume it from the last event if it paused or failed\nmidway, best-effort and at-least-once, with in-memory state lost. So the\naccurate statement is: the flag decides whether progress is persisted as\nloadable checkpoints and whether an invocation can be resumed across runner\ncalls — not whether the workflow can resume. Resumability here is really\ndurability.\n\n### The `Workflow` node writes a checkpoint, but does not read one\n\n`Workflow._emit_node_checkpoint` does persist a node-status snapshot when\n`ic.is_resumable` — an `agent_state` event carrying `{\"nodes\": {name: {status,\ninterrupts}}}` for each static child (`resume_inputs` is intentionally omitted\nfrom the checkpoint). Two sibling markers are\nwritten under the same flag: `_maybe_reemit_replayed_output` re-surfaces a\nfast-forwarded node's output, and `_emit_end_of_agent` records that the\nworkflow ran to completion.\n\nWhat is missing is the other half. Nothing loads that checkpoint: setup still\nbegins with `ReplayManager.scan_workflow_events` over the whole invocation, and\n`_run_impl` carries a `TODO: resume from checkpoint event`. So the checkpoint\nis currently an observable record of progress rather than the durable state\nresume actually runs off. Resume cost still scales with history length.\n\n## How peer frameworks do it\n\nEvery mainstream agent framework persists a **state snapshot with a position\ncursor** and, on resume, **loads that snapshot** — none reconstruct by replaying\nthe entire history.\n\nFramework                | Durable unit                                                        | Position cursor                                                                    | Resume\n------------------------ | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------\nLangGraph (graph)        | `StateSnapshot` per super-step in a pluggable checkpointer          | `next` nodes + parent-pointer chain + per-task pending writes                      | re-invoke same thread id; load latest checkpoint, re-run only the interrupted node\npydantic-graph (graph)   | `NodeSnapshot{state, node, status}` via a state-persistence backend | the snapshot's `node` = next node to run; `status` created/pending/running/success | `iter_from_persistence()` loads the next `created` snapshot\nOpenAI Agents SDK (loop) | serialized `RunState` blob                                          | run cursor inside the state; correlate by tool-call id                             | deserialize the state, apply approvals, resume the run\nPydantic AI (loop)       | message history + deferred-tool results                             | implicit in the transcript; correlate by tool-call id                              | new run over the prior message history\n\nADK's durable unit today is the event log itself, and resume is by replay over\nit. Two patterns from the peers are worth copying, both consistent across them:\n\n-   **A snapshot is the source of truth for resume.** The runtime writes a\n    snapshot as it advances and reads the latest one to continue. Resume cost is\n    bounded by the snapshot size, not by history length.\n-   **Resume re-runs the paused unit from its start** (LangGraph and\n    pydantic-graph both re-execute the whole node, not a saved program counter),\n    which keeps the durable state small and pushes an idempotency contract onto\n    the node author — the same at-least-once contract ADK already documents.\n\n## Direction: load the checkpoint instead of replaying\n\nThe write half exists. The remaining step — peer-aligned, and the one ADK's own\ncomposite agents already take — is to make that checkpoint the thing resume\nreads:\n\n-   On resume, seed the loop state from the most recent `agent_state`\n    checkpoint instead of scanning the invocation's events, then continue:\n    re-run only the interrupted node and dispatch newly-ready successors.\n-   This makes resume cost independent of history length and unifies the\n    `Workflow` node with composite agents and with LangGraph / pydantic-graph /\n    the OpenAI SDK.\n\nThis only applies when durability is on. Without `is_resumable` nothing is\npersisted, and the workflow continues to resume within an invocation by replay.\n\n## Open considerations\n\n-   **Payload completeness.** A workflow checkpoint must carry (or be able to\n    recover) each completed node's output, run id, and branch — the equivalent\n    of LangGraph's per-task pending writes — to fully replace event replay.\n-   **Partial interrupt resolution.** A node with several interrupts may receive\n    only some responses on a resume. The re-run-vs-wait behavior differs between\n    an orchestrating node (re-run to dispatch the resolved branch) and a leaf\n    node (wait for all), keyed on `rerun_on_resume`. This is decided in the\n    shared replay-interception logic and should be settled before a load path\n    relies on it.\n-   **Versioning.** Long-lived paused runs can outlive a code change. A version\n    marker on the checkpoint lets a resume route to a compatible code path (the\n    OpenAI SDK makes this an explicit recommendation).\n-   **Serialization.** Keep payloads JSON-serializable (Pydantic `model_dump`),\n    so any persistence backend works and no code objects are serialized; node\n    objects are rebound from the in-memory graph definition on resume.\n","isInternal":false,"tokens":1671,"sizeBytes":7920},{"name":"interface-agent.md","path":".agents/skills/adk-architecture/references/interface-agent.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/interface-agent.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Agent\n\n`Agent` is a type alias for `LlmAgent` (`Agent: TypeAlias = LlmAgent`), the\nmodel-backed agent most users instantiate. It is not the same thing as\n`BaseAgent`, which is the abstract base every agent derives from.\n\n`BaseAgent` extends `BaseNode`, so an agent is a node: it can stand alone under\na Runner or sit inside a Workflow graph.\n\n## Key fields\n\n- **`name`** — unique identifier within the agent tree. Validated: must be a\n  valid Python identifier, and `\"user\"` is rejected because it is reserved for\n  end-user input.\n- **`description`** — capability description the model uses when choosing\n  which sub-agent to delegate to.\n- **`sub_agents`** — child agents. Names must be unique across the tree;\n  each child's `parent_agent` is wired up automatically.\n- **`before_agent_callback` / `after_agent_callback`** — intercept the agent\n  lifecycle.\n\n## Entrance methods\n\n| Method | Use |\n|---|---|\n| `run_async(parent_context)` | Text conversation. Yields `Event`s. Runs the before/after callbacks, the error callback, and invocation instrumentation around `_run_async_impl`. |\n| `run_live(parent_context)` | Video/audio conversation. Marked `@final` — override `_run_live_impl` instead. |\n| `run(ctx=..., node_input=...)` | Inherited from `BaseNode`, `@final`. This is what a Workflow calls when the agent is a graph node; it routes into `_run_impl`, which for `BaseAgent` delegates to `run_async`. |\n\nWhich one you call depends on the caller, not on age: a Workflow calls `run()`,\ndirect text callers use `run_async()`. Nothing in the source marks `run_async`\ndeprecated, and it remains the path every agent's real logic runs through.\n\n## Other methods\n\n- **`clone(...)`** — copy the agent, detached from its parent.\n- **`find_agent(name)` / `find_sub_agent(name)`** — search the agent tree.\n- **`root_agent`** — walk up to the top of the tree.\n- **`from_config(config, config_abs_path)`** — build an agent from a config\n  object. Both `@deprecated` and `@experimental`; do not build on it.\n","isInternal":false,"tokens":501,"sizeBytes":2024},{"name":"interface-base-agent.md","path":".agents/skills/adk-architecture/references/interface-base-agent.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/interface-base-agent.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# BaseAgent\n\n`BaseAgent` is the abstract base for every agent. It extends `BaseNode`, so an\nagent is a node with agent-specific lifecycle on top: callbacks, error\nhandling, invocation instrumentation, and an agent tree.\n\n## What to override\n\nOverride **`_run_async_impl(ctx)`** for text conversation, and\n**`_run_live_impl(ctx)`** for live audio/video. Both receive an\n`InvocationContext`. Every built-in composite agent — `LlmAgent`,\n`SequentialAgent`, `LoopAgent`, `ParallelAgent` — implements these two and\nnothing else.\n\nDo **not** override `_run_impl`. `BaseAgent` already overrides it (marked\n`@override`) as the bridge from node execution into `run_async`, which is what\napplies the before/after callbacks, the error callback and the invocation\nmetrics. Replacing it silently drops all of that.\n\n```text\nWorkflow calls node.run()          (BaseNode, @final)\n  └─ BaseAgent._run_impl           (bridge — do not override)\n      └─ BaseAgent.run_async       (callbacks, instrumentation, error handling)\n          └─ your _run_async_impl  ← override point\n```\n\n`LlmAgent` is the exception that proves the rule: it overrides `_run_impl` too,\nin order to run through a dedicated node wrapper. That is framework-internal.\n\n## Key attributes to configure\n\n- **`name`** — must be a valid Python identifier, unique within the agent tree,\n  and cannot be `\"user\"`.\n- **`description`** — capability description used by the model for delegation.\n- **`sub_agents`** — child agents for hierarchical delegation. Duplicate names\n  across the tree are rejected at validation time.\n- **`before_agent_callback` / `after_agent_callback`** — lifecycle hooks. Both\n  accept a list; the canonical forms are exposed as\n  `canonical_before_agent_callbacks` / `canonical_after_agent_callbacks`.\n\n## Author attribution\n\nWhen an agent runs as a workflow node, `_run_impl` copies each event's author\nonto `ctx.event_author` so the enclosing NodeRunner does not overwrite it with\nthe parent workflow's name. Events therefore stay attributed to the agent that\nactually produced them.\n","isInternal":false,"tokens":474,"sizeBytes":2087},{"name":"interface-base-node.md","path":".agents/skills/adk-architecture/references/interface-base-node.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/interface-base-node.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# BaseNode\n\nBaseNode is the primitive unit of execution in the workflow runtime.\nEvery computation — LLM calls, tool execution, orchestration — is\na node. It is a Pydantic `BaseModel` subclass.\n\n## The node contract\n\nEvery node follows a two-method pattern:\n\n- `run()` is `@final` — normalizes yields to Events. Never override.\n- `_run_impl()` is the extension point — subclasses implement their\n  logic here as an async generator.\n\n```python\nclass MyNode(BaseNode):\n    async def _run_impl(self, *, ctx, node_input):\n        result = do_work(node_input)\n        yield result  # becomes Event(output=result)\n```\n\n**Why this split:** `run()` guarantees consistent normalization\nregardless of what the subclass does. The subclass only thinks\nabout its domain logic.\n\n**Normalization rules** (`run()` applies these to each yield):\n\n- `None` → skipped\n- `Event` → pass through\n- `RequestInput` → interrupt Event\n- any other value → `Event(output=value)`\n\n**Generator conventions:**\n\nA node can yield three types of data:\n\n- **Output** — the node's result value. Flows between nodes\n  (parent reads `ctx.output` after child completes). At most one\n  per execution (second raises `ValueError`).\n- **Message** — user-visible content streamed to the end user\n  (e.g., progress text, partial responses). Multiple allowed.\n- **Route** — Workflow-specific concept. Triggers conditional\n  edges in the graph. Set via `ctx.route` or `event.actions.route`.\n\nAdditional rules:\n\n- Yielding nothing produces no output event\n- `yield None` is silently skipped\n\nA custom node interacts with the runtime through two arguments:\n\n- **`ctx`** (Context) — communicate results to the parent node\n- **`node_input`** — data passed by the parent/orchestrator\n\n## Output and streaming\n\nThree ways to produce output (pick one per execution):\n\n```python\n# 1. Yield a value (most common)\nasync def _run_impl(self, *, ctx, node_input):\n    yield compute(node_input)\n\n# 2. Set ctx.output directly\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.output = compute(node_input)\n    return\n    yield  # generator contract\n\n# 3. Yield an Event with output\nasync def _run_impl(self, *, ctx, node_input):\n    yield Event(output=compute(node_input))\n```\n\nA second output raises `ValueError` — at most one per execution.\n\n**Streaming messages** — yield Events with `message` to send\nuser-visible text (`message` is an alias for `content` on Event):\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    yield Event(message='working...')\n    yield final_result  # this is the output\n```\n\n## State and routing\n\n**Mutating state:**\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.state['key'] = 'value'  # recorded as state_delta\n    yield result\n```\n\n**Setting route for conditional edges:**\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.route = 'approve' if score > 0.8 else 'reject'\n    yield node_input\n```\n\n## Advanced: child nodes and HITL\n\n**Running child nodes** via `ctx.run_node()`:\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    child_result = await ctx.run_node(some_node, node_input)\n    yield f'child said: {child_result}'\n```\n\nRequires `rerun_on_resume = True` on the calling node.\n\n**Requesting interrupt (HITL):**\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    if ctx.resume_inputs and 'fc-1' in ctx.resume_inputs:\n        yield f'approved: {ctx.resume_inputs[\"fc-1\"]}'\n        return\n    yield Event(long_running_tool_ids={'fc-1'})\n```\n\n## Configuration reference\n\n| Field | Type | Default | Purpose |\n|---|---|---|---|\n| `name` | `str` | required | Unique identifier. Validated: must be a valid Python identifier. |\n| `description` | `str` | `''` | Human-readable description |\n| `rerun_on_resume` | `bool` | `False` | Re-execute from scratch on resume (required for `ctx.run_node()`). When `False` the node completes immediately using the resume input as its output. |\n| `wait_for_output` | `bool` | `False` | Stay WAITING until output *or* route is yielded, so predecessors can re-trigger the node (join nodes). Never yielding either deadlocks the node. |\n| `retry_config` | `RetryConfig \\| None` | `None` | Retry on failure. Not persisted, so the count restarts after a resume. |\n| `timeout` | `float \\| None` | `None` | Max execution time in seconds; exceeding it raises `NodeTimeoutError`, which `retry_config` can retry. |\n| `input_schema` | `SchemaType \\| None` | `None` | Validate/coerce input data before `run()` |\n| `output_schema` | `SchemaType \\| None` | `None` | Validate/coerce output data |\n| `state_schema` | `type[BaseModel] \\| None` | `None` | Declare expected `ctx.state` keys and types; mutations are validated at runtime. Children inherit it unless they declare their own. Prefixed keys (`app:`, `user:`, `temp:`) bypass validation. |\n\n`START` (in the same module) is a sentinel `BaseNode` marking a graph's entry\npoint. It is never executed — the Workflow seeds triggers for its successors\ndirectly.\n","isInternal":false,"tokens":1248,"sizeBytes":5009},{"name":"interface-event.md","path":".agents/skills/adk-architecture/references/interface-event.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/interface-event.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Event\n\n`Event` is the record of one thing that happened in a session — a message, a\ntool call, a state change, a node's output. It is the unit of persistence and\nthe substrate every resume path reads. It extends `LlmResponse`, so response\nfields (`content`, `partial`, `error_code`, `error_message`, ...) are Event\nfields too.\n\n## Key fields\n\n| Field | Meaning |\n|---|---|\n| `invocation_id` | Invocation this event belongs to. Non-empty before appending to a session. |\n| `author` | `'user'` or the name of the agent/node that appended it. |\n| `content` | The message payload, inherited from `LlmResponse`. |\n| `actions` | `EventActions` — `state_delta`, `artifact_delta`, `route`, `transfer_to_agent`, `escalate`, `agent_state`, `end_of_agent`, requested auth configs and tool confirmations. Function calls live in `content`, not here. |\n| `output` | Generic data output from a workflow node. |\n| `node_info` | `NodeInfo`: `path` (e.g. `wf@1/child@2`), `output_for`, `message_as_output`. |\n| `long_running_tool_ids` | IDs of long-running calls. Setting these is what makes the framework treat the event as an interrupt. |\n| `branch` | Dot-separated branch path for isolating peer sub-agents' history. |\n| `isolation_scope` | Scope tag for which logical context this event belongs to. |\n| `id`, `timestamp` | Identity and time. |\n\n`NodeInfo.run_id` is a **derived property**, not a stored field: it is parsed\noff the last `name@run_id` segment of `node_info.path`. Nothing stamps it\nseparately, so a path without an `@` yields an empty run id.\n\n## Convenience kwargs\n\nThe constructor routes four shorthand kwargs onto nested fields, so these are\nequivalent to writing the nested form:\n\n| Kwarg | Lands on |\n|---|---|\n| `message=` | `content` (converted via the genai content transformer) |\n| `state=` | `actions.state_delta` |\n| `route=` | `actions.route` |\n| `node_path=` | `node_info.path` |\n\nPassing both `message` and `content` raises `ValueError`.\n\n## Methods\n\n- `get_function_calls()` / `get_function_responses()` — inherited from\n  `LlmResponse`; pull the function calls or responses out of `content`.\n- `is_final_response()` — whether this is an agent's final response.\n- `has_trailing_code_execution_result()` — whether the content ends in a code\n  execution result.\n- `message` — property aliasing `content`, with a matching setter.\n- `node_name` — the node name parsed off `node_info.path`.\n\n## State lifecycle and immutability\n\n- **Events are immutable once saved.** Never assume an event is mutated or\n  cleared after it lands in a session; resume works by reading the log\n  forward, not by rewriting it.\n- **Signals resolve by later events, not by edits.** To decide whether a\n  request is pending or resolved, look for the matching later event (the\n  function response), not a flag flipped on the original.\n- **Aborted invocations seal dangling calls.** When an invocation is cancelled\n  via `abort_signal`, the runner appends a terminal event with\n  `error_code='INVOCATION_ABORTED'` carrying synthetic `FunctionResponse`\n  parts for any unclosed `FunctionCall`s so later turns see a balanced log.\n- **Beware stateful flags on events.** Background compaction may rewrite or\n  drop aged events, so a transient status stored on one can survive or vanish\n  in ways you did not intend.\n","isInternal":false,"tokens":821,"sizeBytes":3314},{"name":"interface-runner.md","path":".agents/skills/adk-architecture/references/interface-runner.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/interface-runner.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Runner\n\n`Runner` is the public entry point for executing agents and workflows. It owns\nthe invocation lifecycle: building the `InvocationContext`, draining the event\nqueue onto the session, and wiring in the artifact, session, memory and\ncredential services plus the plugin manager.\n\n`InMemoryRunner` is the batteries-included subclass that supplies in-memory\nservices; it accepts either an `agent=` or a `node=`.\n\n## Entrance methods\n\n### `run_async`\n\nThe main asynchronous entry point. Use this in production.\n\n- Yields events as they are produced; does not block concurrent calls for other\n  queries.\n- Runs event compaction after the invocation when the app has\n  `events_compaction_config` set.\n\n| Argument | Meaning |\n|---|---|\n| `user_id` | User ID of the session. |\n| `session_id` | Session ID. |\n| `invocation_id` | Set to resume an interrupted invocation. |\n| `new_message` | Message to append to the session. Optional — omit it when resuming. |\n| `state_delta` | State changes to apply to the session. |\n| `run_config` | Run config for the agent. |\n| `yield_user_message` | Yield the user-message event before agent/node events. |\n| `abort_signal` | Optional `asyncio.Event` for cooperative cancellation. Setting it halts the active invocation cleanly. |\n\n### `run`\n\nSynchronous convenience wrapper for local testing: runs the async path on a\nbackground thread and re-yields its events. Takes `user_id`, `session_id`,\n`new_message`, `state_delta` and `run_config` — no `invocation_id`, so it\ncannot resume.\n\n### `run_live`\n\nAudio/video streaming entry point, driven by a `LiveRequestQueue`\n(`from google.adk.live import LiveRequestQueue`) rather than a single\n`new_message`.\n\n### `run_debug`\n\nConvenience harness for local iteration: takes one message or a list of\nmessages, creates the session if needed, and prints the exchange (`quiet` and\n`verbose` control how much).\n\n## Execution cancellation (`abort_signal`)\n\nWhen `abort_signal` is passed to `run_async` and set during execution:\n\n- `InvocationContext.is_aborted` flips to `True` and active agent or workflow\n  tasks are cancelled cleanly without surfacing an unhandled `CancelledError` to\n  the caller.\n- `Runner._synthesize_abort_events_if_needed` appends and yields synthetic\n  abort `Event`s with `error_code='INVOCATION_ABORTED'`. When function calls are\n  dangling, it emits one abort event per `(author, branch, isolation_scope)`\n  tuple sealing those calls with synthetic `FunctionResponse(response={'error':\n  'Invocation was aborted by client.'})` parts so each response pairs with its\n  call in the issuing agent's view. If nothing is dangling, it yields a single\n  root-authored abort event instead. This preserves session log invariants so\n  that subsequent conversational turns or resumes do not fail on dangling tool\n  calls or re-run the cancelled tool.\n- `PluginManager.run_after_run_callback` still executes before `run_async`\n  exits.\n- In the FastAPI server (`/run_sse`), client HTTP disconnects automatically set\n  `abort_signal` on the active runner invocation.\n","isInternal":false,"tokens":698,"sizeBytes":3059},{"name":"interface-workflow.md","path":".agents/skills/adk-architecture/references/interface-workflow.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/references/interface-workflow.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Workflow\n\nWorkflow is a graph-based orchestration node. It extends BaseNode\nand implements `_run_impl()` as a scheduling loop that drives static\ngraph nodes and tracks dynamic nodes spawned by `ctx.run_node()`.\n\n## Two kinds of child nodes\n\nWorkflow manages two kinds of child nodes through a unified scheduler (`DynamicNodeScheduler`):\n\n- **Static (graph) nodes** — declared in `edges`, compiled into a\n  `Graph`. Scanned upfront into `_LoopState.recovered_executions` by\n  `ReplayManager.scan_workflow_events(ctx)`. When scheduled by the\n  orchestration loop, `_start_node_task` checks `check_interception` on any\n  recovered execution to mark `_LoopState.replayed_nodes` and pre-populate\n  `_LoopState.runs[node_path]` (`DynamicNodeRun` with `is_static=True`), then\n  delegates to `ctx._run_node_internal` (`DynamicNodeScheduler`). Tracked in\n  `_LoopState.nodes` by node name (`NodeState`).\n- **Dynamic nodes** — spawned at runtime via `ctx.run_node()` from\n  inside a graph node's `_run_impl`. Lazily rehydrated from `ReplayManager`\n  by `DynamicNodeScheduler._rehydrate_from_events` and tracked in\n  `_LoopState.runs` by full `node_path` (`DynamicNodeRun`,\n  which holds `state: NodeState`, `output`, `task`, `transfer_to_agent`,\n  `recovered_state`, and `is_static`). Managed by `DynamicNodeScheduler`.\n\nStatic and dynamic nodes share the same `DynamicNodeScheduler`, replay\ninterception (`check_interception` / `ReplayManager`), and\n`_LoopState.interrupt_ids` set, so the Workflow sees a unified view of\nexecution and all pending interrupts.\n\n## Implementing a graph node\n\nA graph node is a regular BaseNode placed in a Workflow's edges.\nIn `_start_node_task`, the Workflow checks `check_interception` against\n`_LoopState.recovered_executions` (to record `_LoopState.replayed_nodes` and\nseed `_LoopState.runs` with `is_static=True`) and dispatches\n`ctx._run_node_internal` to `DynamicNodeScheduler`, which either returns a\nreplayed mock `Context` (`create_mock_context`) or wraps the node in a\n`NodeRunner` with a child `Context`, reading `ctx.output`, `ctx.route`, and\n`ctx.interrupt_ids` after it completes.\n\n**Output** — two paths. At most one per execution. The Workflow\nreads the output to pass downstream.\n\n```python\n# Yield (persisted immediately)\nasync def _run_impl(self, *, ctx, node_input):\n    yield compute(node_input)\n\n# ctx (deferred until node end)\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.output = compute(node_input)\n    return\n    yield\n```\n\n**Routing** — two paths. The Workflow uses the route to select\nconditional edges.\n\n```python\n# Yield (persisted immediately)\nasync def _run_impl(self, *, ctx, node_input):\n    yield Event(route='approve' if node_input > 0.8 else 'reject')\n\n# ctx (deferred until node end)\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.route = 'approve' if node_input > 0.8 else 'reject'\n    yield node_input\n```\n\n**State** — two paths. `ctx.state` deltas are flushed onto the next\nyielded Event, or a final Event at node end.\n\n```python\n# Yield (persisted immediately)\nasync def _run_impl(self, *, ctx, node_input):\n    yield Event(state={'count': 1})\n\n# ctx (flushed onto next/final Event)\nasync def _run_impl(self, *, ctx, node_input):\n    ctx.state['count'] = 1\n    yield result\n```\n\n**Interrupts** — yield only (`ctx.interrupt_ids` is read-only). The\nWorkflow marks the node WAITING and propagates the interrupt IDs\nupward. On resume, if `rerun_on_resume=True` (default for Workflow),\nthe node is re-executed with `ctx.resume_inputs` populated.\n\n```python\nasync def _run_impl(self, *, ctx, node_input):\n    if ctx.resume_inputs and 'fc-1' in ctx.resume_inputs:\n        yield f'approved: {ctx.resume_inputs[\"fc-1\"]}'\n        return\n    yield Event(long_running_tool_ids={'fc-1'})\n```\n\n## Dynamic nodes via ctx.run_node()\n\nA graph node can spawn child nodes at runtime:\n\n```python\nclass Orchestrator(BaseNode):\n    rerun_on_resume: bool = True  # required\n\n    async def _run_impl(self, *, ctx, node_input):\n        result = await ctx.run_node(some_node, input_data)\n        yield f'child returned: {result}'\n```\n\n### Requirements\n\n- The calling node **must** have `rerun_on_resume = True`. Without\n  this, the Workflow cannot re-execute the node on resume to let it\n  re-acquire its dynamic children's results.\n\n### Tracking\n\nDynamic nodes are tracked by **full node_path**, not by name alone.\nEach segment is `node_name@run_id`:\n\n```text\nwf@1/graph_node_a@1/dynamic_child@1        ← dynamic node under graph_node_a\nwf@1/graph_node_a@1/dynamic_child@1/inner@1  ← transitive dynamic node\n```\n\nThe node name comes from the node's own `name` field. The run id comes from\nthe `run_id` argument to `ctx.run_node()`, or a generated counter when that\nargument is omitted. There is no `name=` parameter on `ctx.run_node()` — pass\na node whose `name` is what you want, and pass `run_id=` to pin the suffix.\n\nEach unique `node_path` is tracked in `_LoopState.runs`\n(`dict[str, DynamicNodeRun]`). This enables:\n\n- **Dedup** — if the same path is encountered again (after resume),\n  the cached output is returned without re-execution.\n- **Resume** — if the node was interrupted, its state is\n  reconstructed from session events via `ReplayManager` and `check_interception`.\n\n### Unified interception and resume protocol (DynamicNodeScheduler)\n\n`DynamicNodeScheduler.__call__` wraps single-step node execution (`_execute_step`)\nin a sequential `transfer_to_agent` loop: if a completed agent step sets\n`child_ctx.actions.transfer_to_agent`, `__call__` resolves the target agent and\nparent context (`resolve_and_derive_transfer_context`), delegates the next step\nto the target context's owning scheduler, and only preserves `use_as_output`\nwhile `curr_parent_ctx is ctx`.\n\nWithin a single step (`_execute_step`), when replay is enabled:\n- If `node_path` is not in `_state.runs` (for recovered static nodes,\n  `_start_node_task` already inserted a `DynamicNodeRun` with `is_static=True`),\n  `_rehydrate_from_events` scans `ReplayManager` for that `node_path` and\n  populates `_state.runs[node_path]` if historical events exist.\n- If `node_path` is still not in `_state.runs`, `_check_existing_run` returns\n  `(None, False)` and `_execute_step` runs the node fresh via\n  `_run_node_internal(..., is_fresh=True)` (`NodeRunner`).\n- If `node_path` is in `_state.runs`, `_check_existing_run` awaits any in-flight\n  concurrent `run.task`, or calls `check_interception(node=curr_node,\n  recovered=run.recovered_state, current_run=run)`:\n\n1. **Same-turn dedup / failed / waiting (`current_run` present and `not current_run.is_static`)** —\n   if `current_run.state.status == COMPLETED`, returns `should_run=False` with\n   `current_run.output`, `current_run.route`, and\n   `current_run.transfer_to_agent`. If `FAILED`, returns `should_run=True`\n   with `resume_inputs=recovered.resolved_responses` so a dynamic run that\n   failed in the current turn reruns instead of consulting stale recovered\n   events. If `WAITING` with `interrupts`, returns `should_run=False` with\n   those `interrupts`. (Every static dispatch gets a fresh `run_id`, so loop\n   edges never reuse a `node_path`; static runs have `is_static=True` so\n   no-outcome static nodes fast-forward in step 7 via\n   `should_run = (current_run is not None and not current_run.is_static)`.)\n\n2. **Nested `Workflow` (`isinstance(node, Workflow)`)** — before checking\n   completion or interrupts, `check_interception` immediately returns\n   `should_run=True` with `resume_inputs=recovered.resolved_responses`. A child\n   `Workflow` never fast-forwards at the parent boundary; it always re-enters\n   `_run_impl` so its own `ReplayManager` and scheduler can fast-forward or\n   resume its internal children.\n\n3. **Waiting / Partial resume (`recovered.interrupt_ids - recovered.resolved_ids` non-empty)** —\n   - If `node.rerun_on_resume` is `True` and `recovered.resolved_ids` is\n     non-empty (**partial resume**): returns `should_run=True` with\n     `resume_inputs=recovered.resolved_responses`.\n   - Otherwise: returns `should_run=False` with `interrupts=unresolved`,\n     propagating unresolved interrupt IDs to `_state.interrupt_ids` via a\n     mock `Context` (`create_mock_context`).\n\n4. **Prior failure (`recovered.error_code is not None`)** — returns\n   `should_run=True` with `resume_inputs=recovered.resolved_responses` so a\n   node that failed in a prior turn re-executes instead of fast-forwarding with\n   `None`.\n\n5. **Fast-forward (`recovered.route`, `recovered.output`, or `recovered.transfer_to_agent` present)** —\n   non-`Workflow` nodes that recorded an output, route, or agent transfer in a\n   prior turn return `should_run=False` with the rehydrated `output`, `route`,\n   and `transfer_to_agent` via `create_mock_context` (after awaiting\n   `ReplayManager.wait_sequence`) without re-executing `NodeRunner`.\n\n6. **All interrupts resolved, no output yet (`recovered.interrupt_ids` non-empty, `unresolved` empty)** —\n   - `rerun_on_resume=False`: fast-forwards (`should_run=False`), setting\n     `output` to the single value in `recovered.resolved_responses` (when\n     `len == 1`) or `dict(recovered.resolved_responses)`.\n   - `rerun_on_resume=True`: re-executes (`should_run=True`) via\n     `_run_node_internal(..., is_fresh=False)` with\n     `resume_inputs=recovered.resolved_responses`.\n\n7. **No output, route, or interrupts in recovered events** —\n   - If `node.wait_for_output` or `node.rerun_on_resume`: returns\n     `should_run=True` with `resume_inputs=recovered.resolved_responses`.\n   - Otherwise: returns `should_run = (current_run is not None and not current_run.is_static)`\n     (static nodes that completed with `None` output fast-forward with\n     `should_run=False`, while dynamic nodes with no recorded outcome\n     re-execute with `should_run=True`).\n\n### Interrupt propagation\n\nWhen a dynamic child interrupts:\n\n1. `DynamicNodeScheduler._record_result` sets the child's `DynamicNodeRun`\n   status to WAITING and adds its interrupt IDs to\n   `_LoopState.interrupt_ids`.\n2. `ctx.run_node()` checks `child_ctx.interrupt_ids`. If non-empty,\n   it propagates them to the calling node's `ctx._interrupt_ids`\n   and raises `NodeInterruptedError`.\n3. `NodeRunner` catches `NodeInterruptedError` in `_execute_node` and\n   records the interrupt on the calling node's `Context`.\n4. The Workflow's `_handle_completion` sees the interrupt and marks\n   the graph node as WAITING.\n\nOn resume, the Workflow re-executes the graph node (because\n`rerun_on_resume=True`). The graph node calls `ctx.run_node()`\nagain, which hits the scheduler. `_rehydrate_from_events` scans session\nevents for the child's prior state into a `_ChildScanState`, and\n`check_interception` reads that scanned state, finds the resolved FR, and\neither fast-forwards with cached output or re-executes the dynamic child\nwith `resume_inputs`.\n\n### ctx.run_node() options\n\n| Argument | Effect |\n|---|---|\n| `node_input` | Data handed to the child. |\n| `use_as_output` | The child's output becomes the calling node's output. |\n| `run_id` | Pins the `@run_id` suffix on the child's node path. |\n| `use_sub_branch` | Runs the child on a sub-branch so its events are isolated. |\n| `override_branch`, `override_isolation_scope` | Replace the inherited branch / scope tag. |\n| `raise_on_wait` | Defaults to `False`. If `True`, raises `NodeInterruptedError` when a child `Workflow` or `wait_for_output=True` node finishes with `output=None` instead of returning `None`. |\n\n### Output delegation (use_as_output)\n\n`ctx.run_node(node, use_as_output=True)` makes the dynamic child's\noutput count as the calling node's output:\n\n```python\nclass Delegator(BaseNode):\n    rerun_on_resume: bool = True\n\n    async def _run_impl(self, *, ctx, node_input):\n        # child's output becomes this node's output\n        await ctx.run_node(worker, node_input, use_as_output=True)\n```\n\n- Sets `ctx._output_delegated = True` on the parent\n- NodeRunner stamps `event.node_info.output_for` with ancestor paths\n- Only one `use_as_output=True` per execution (second raises\n  `ValueError`)\n\n## Dynamic nodes from dynamic nodes (transitive)\n\nA dynamic node can itself call `ctx.run_node()`, creating a\ntransitive chain:\n\n```python\nclass Outer(BaseNode):\n    rerun_on_resume: bool = True\n\n    async def _run_impl(self, *, ctx, node_input):\n        result = await ctx.run_node(Inner(name='inner'), 'data')\n        yield result\n\nclass Inner(BaseNode):\n    rerun_on_resume: bool = True\n\n    async def _run_impl(self, *, ctx, node_input):\n        sub = await ctx.run_node(Leaf(name='leaf'), node_input)\n        yield f'inner got: {sub}'\n```\n\nThis works because:\n\n- All dynamic nodes in the subtree are tracked by the **same**\n  enclosing Workflow. The scheduler is inherited down the Context\n  tree automatically.\n- Each level gets a unique `node_path`:\n  `wf/graph_node/outer/inner/leaf`\n- Nested interrupts are correctly attributed — the scheduler\n  matches events from any descendant under a given path.\n- Only a nested **orchestration node** (another Workflow) takes over\n  scheduling. Regular nodes inherit the enclosing Workflow's scheduler.\n\n### Scoping\n\nEach Workflow has its own `DynamicNodeScheduler` and `_LoopState`.\nA nested Workflow creates a new scheduler, so dynamic nodes within\nit are scoped to that inner Workflow — not mixed with the outer\nWorkflow's state.\n\n## event_author\n\nWorkflow sets `ctx.event_author = self.name` at the start of\n`_run_impl`. This propagates to all child Contexts via NodeRunner.\nAll events emitted by children carry this author, giving the UI\nconsistent attribution.\n\nA nested Workflow overrides `event_author` with its own name, so events are\nattributed to the nearest orchestration ancestor.\n\n## Orchestration loop lifecycle\n\n```text\n_run_impl\n  ├─ SETUP\n  │    ├─ loop_state.replay_manager.scan_workflow_events(ctx)\n  │    ├─ _seed_start_triggers\n  │    └─ ctx._workflow_scheduler = DynamicNodeScheduler(state=loop_state)\n  ├─ LOOP (_run_loop):\n  │    ├─ _schedule_ready_nodes → pop triggers, dispatch via _start_node_task / ctx._run_node_internal\n  │    ├─ asyncio.wait(FIRST_COMPLETED)\n  │    ├─ _handle_completion → update state, emit checkpoint / replayed output, buffer downstream\n  │    └─ await detached dynamic tasks (loop_state.get_dynamic_tasks()) & _surface_detached_dynamic_outcome\n  ├─ _cleanup_all_tasks  (finally)\n  ├─ _collect_remaining_interrupts\n  ├─ FINALIZE: set ctx.output or ctx._interrupt_ids\n  └─ _emit_end_of_agent  (only when no interrupts remain)\n```\n\nThe event scan is unconditional: `ReplayManager` indexes progress from the\nsession on every run, whether or not the app is configured resumable.\n\nKey behaviors:\n\n- **Concurrency** — `max_concurrency` limits parallel graph nodes.\n  Dynamic nodes are excluded (they run inline, throttling would\n  deadlock).\n- **Terminal output** — nodes with no outgoing edges are terminal.\n  Their output is delegated to the Workflow's own output via\n  `output_for`. Only one terminal node may produce output.\n- **Loop edges** — a completed node can be re-triggered by a\n  downstream edge pointing back to it. Its status resets to PENDING.\n\n## Resume from session events\n\nOn every run, `Workflow` reconstructs progress from session events in three\nstages:\n\n1. **Upfront static scan (`ReplayManager.scan_workflow_events`)** — during\n   `_run_impl` SETUP, `ReplayManager` scans the invocation's events and\n   populates `loop_state.recovered_executions` (keyed by `node_name@run_id`)\n   with outputs, routes, interrupts, resolved function responses, error codes,\n   and transfers for direct static children. `Workflow` then seeds `START`\n   triggers normally (`_seed_start_triggers`).\n2. **Static node interception in `_start_node_task` and `DynamicNodeScheduler`** —\n   when `_schedule_ready_nodes` pops a trigger for a static node,\n   `_start_node_task` looks up `recovered = loop_state.recovered_executions.get(f\"{node_name}@{run_id}\")`:\n   - If `recovered` is present, `_start_node_task` calls\n     `check_interception(node=node, recovered=recovered)` to check whether the\n     run will fast-forward (`not result.should_run` and not a resolved transfer\n     interrupt), adding `node_name` to `loop_state.replayed_nodes` (so\n     checkpoint emission is skipped in `_schedule_ready_nodes` and on the\n     `COMPLETED` path of `_handle_completion`; `_handle_completion` still emits\n     a checkpoint if a replayed node comes back `WAITING`) and seeding\n     `loop_state.runs[node_path]` with\n     `DynamicNodeRun(state=NodeState(run_id=run_id), recovered_state=recovered, is_static=True)`.\n   - `_start_node_task` then dispatches `ctx._run_node_internal` to\n     `DynamicNodeScheduler._check_existing_run`, which runs\n     `check_interception(node=curr_node, recovered=run.recovered_state, current_run=run)`:\n     - **Nested `Workflow`**: `check_interception` always returns\n       `should_run=True` (with `resume_inputs=recovered.resolved_responses`),\n       so a child `Workflow` never fast-forwards at the parent level and instead\n       re-runs `_run_impl` to replay or resume its own children.\n     - **Completed non-`Workflow` node (or `rerun_on_resume=False` with all interrupts resolved)**:\n       returns `should_run=False`; `_check_existing_run` builds a mock\n       `Context` (`create_mock_context`) and awaits\n       `ReplayManager.wait_sequence` so `_handle_completion` can re-surface\n       replayed output (`_maybe_reemit_replayed_output`) and buffer downstream\n       triggers without running `NodeRunner`.\n     - **Interrupted with unresolved interrupts**: if `rerun_on_resume=True`\n       and `recovered.resolved_ids` is non-empty (**partial resume**),\n       re-executes (`should_run=True`) via `NodeRunner` with\n       `resume_inputs=recovered.resolved_responses`; otherwise stays WAITING\n       (`should_run=False` with `interrupts` populated) and records unresolved\n       IDs in `_LoopState.interrupt_ids`.\n     - **Interrupted with all interrupts resolved (`rerun_on_resume=True`) or prior failure (`error_code`)**:\n       re-executes (`should_run=True`) via `NodeRunner` with\n       `resume_inputs=recovered.resolved_responses`.\n3. **Dynamic node lazy rehydration (`_rehydrate_from_events`)** — dynamic\n   children are not scanned upfront into `recovered_executions`. When a\n   re-executing parent calls `ctx.run_node()`,\n   `DynamicNodeScheduler._execute_step` lazily populates\n   `_LoopState.runs[node_path]` via `_rehydrate_from_events` and applies the\n   same `check_interception` rules in `_check_existing_run` (with\n   `current_run=run` also deduplicating same-turn completed or waiting runs).\n\n## Key design rules for node authors\n\n1. **Set `rerun_on_resume = True`** if your node calls\n   `ctx.run_node()`. The Workflow must be able to re-execute your\n   node so it can re-acquire dynamic children's results.\n\n2. **Use deterministic names** for dynamic children. The child node's `name`\n   (plus the optional `run_id=`) determines the `node_path`, which is the\n   dedup/resume key. A name derived from a timestamp, a UUID or model output\n   produces a different path on every run, so resume never finds the prior\n   execution and the child re-runs.\n\n3. **Always `await ctx.run_node()` (or `asyncio.gather`). Never detach tasks.**\n   A dynamic child's interrupt must propagate through the awaiting caller so the\n   caller can pause and resume. If a dynamic task is left detached\n   (`asyncio.create_task(ctx.run_node(...))` left unawaited), its interrupt\n   cannot be handled by the parent and the Workflow turns it into a\n   `RuntimeError` that shuts down the workflow.\n\n4. **Yield output after all dynamic children complete.** If your\n   node calls `ctx.run_node()` and then yields, the output is\n   emitted only after all children finish. This is the expected\n   pattern.\n\n5. **Handle `NodeInterruptedError` only if you need custom logic.**\n   Normally, `ctx.run_node()` raises `NodeInterruptedError` when a\n   child interrupts. NodeRunner catches it automatically. Only\n   catch it yourself if you need to clean up or adjust state before\n   the interrupt propagates.\n\n6. **Don't set `ctx.event_author`** unless your node is an orchestration node\n   like Workflow. The Workflow sets it for you and it propagates to all\n   descendants.\n","isInternal":false,"tokens":4964,"sizeBytes":20360},{"name":"SKILL.md","path":".agents/skills/adk-architecture/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-architecture/SKILL.md","title":"adk-architecture","category":"anthropic-skill","format":"markdown","content":"---\nname: adk-architecture\ndescription: >-\n  Explains how the ADK runtime fits together: the node and graph execution\n  model, Context and Event flow, checkpoint and resume, tracing, and the rules\n  governing the public API surface. Use when answering \"how does X work\" about\n  ADK internals, tracing where an event or a piece of state comes from,\n  deciding where a new capability belongs, reviewing a change to BaseNode,\n  Workflow, Runner, Agent, Event or Context, working out why a node re-ran or\n  stayed waiting after a resume, or judging whether a change breaks the public\n  API. Don't use for assembling an agent from existing pieces (use\n  adk-agent-builder), diagnosing one failing run or test (use adk-debug), or\n  formatting and naming conventions (use adk-style).\n---\n\n# ADK Architecture\n\nThe runtime is a graph of nodes. `BaseNode` is the unit of execution.\n`Workflow` is a node that schedules other nodes along declared edges.\n`NodeRunner` executes exactly one node. `Runner` owns the invocation and the\nsession. Agents are nodes too — `BaseAgent` extends `BaseNode`.\n\nA node communicates with its parent through a per-execution `Context`, and\nwith the session through `Event`s it yields. Those are two separate channels:\n`ctx` carries the result upward, events carry persistence and streaming.\n\nRead the source before relying on any signature here. These notes drift; the\ncode does not. Paths below are relative to `src/google/adk/`.\n\n## Pick a reference\n\n| Question | Reference |\n|---|---|\n| What must a node implement? What may it yield? Which config fields exist? | [BaseNode](references/interface-base-node.md) |\n| How does the graph schedule nodes, dedup dynamic children, propagate interrupts? | [Workflow](references/interface-workflow.md) |\n| How does a caller start an invocation? | [Runner](references/interface-runner.md) |\n| What is `Agent`, and which methods do I call on it? | [Agent](references/interface-agent.md) |\n| I am subclassing an agent — what do I override? | [BaseAgent](references/interface-base-agent.md) |\n| What is on an `Event`, and what may I assume about its lifetime? | [Event](references/interface-event.md) |\n| What does a node read and write on `ctx`? | [Context](references/architecture-context.md) |\n| Who creates the child Context, stamps events, retries, catches errors? | [NodeRunner](references/architecture-node-runner.md) |\n| Why are Runner, NodeRunner and Workflow three separate things? | [Runner roles](references/architecture-runner-roles.md) |\n| How does a human-in-the-loop pause and resume work for one node? | [Checkpoint and resume](references/architecture-checkpoint-resume.md) |\n| How does a whole workflow survive a pause, and what does `is_resumable` change? | [Workflow resumability](references/architecture-workflow-resumability.md) |\n| How are spans created, and what attributes do they carry? | [Observability](references/architecture-observability.md) |\n| Why does the model not see the raw event log? | [LLM context orchestration](references/architecture-llm-context-orchestration.md) |\n| Is this change a breaking change? Where does a new export belong? | [API principles](references/api-principles.md) |\n\n## Where the code lives\n\n| Concept | Module |\n|---|---|\n| `BaseNode`, `START` | `workflow/_base_node.py` |\n| `Workflow`, `_LoopState` | `workflow/_workflow.py` |\n| `Graph`, edge compilation | `workflow/_graph.py` |\n| `NodeRunner` | `workflow/_node_runner.py` |\n| `DynamicNodeScheduler`, `DynamicNodeState`, `DynamicNodeRun` | `workflow/_dynamic_node_scheduler.py` |\n| `ReplayManager` (resume scan) | `workflow/utils/_replay_manager.py` |\n| `check_interception` (replay fast-forward) | `workflow/utils/_replay_interceptor.py` |\n| `NodeInterruptedError`, `NodeTimeoutError` | `workflow/_errors.py` |\n| `Context`, `ctx.run_node()` | `agents/context.py` |\n| `ReadonlyContext` | `agents/readonly_context.py` |\n| `InvocationContext` | `agents/invocation_context.py` |\n| `BaseAgent`, `LlmAgent` (aliased `Agent`) | `agents/base_agent.py`, `agents/llm_agent.py` |\n| `BaseLlmFlow`, `SingleFlow`, `AutoFlow`, `flows/llm_flows/` subpackages (`context/`, `prompt/`, `tools/`, `extensions/`, `core/`) | `flows/llm_flows/` |\n| `Event`, `NodeInfo` | `events/event.py` |\n| `EventActions` | `events/event_actions.py` |\n| Branch paths (`parent.child@1`) | `events/_branch_path.py` |\n| Node paths (`wf@1/child@2`) | `events/_node_path_builder.py` |\n| `Runner`, `InMemoryRunner` | `runners.py` |\n| `LiveRequestQueue`, `LiveRequest` | `live/live_request_queue.py` |\n| Node spans, `TelemetryContext` | `telemetry/node_tracing.py` |\n| `ResumabilityConfig` | `apps/_configs.py` |\n\nEverything under `workflow/` is a leading-underscore module. Treat those names\nas internal — they can change without a major version bump, so a change there\nis not automatically a breaking change.\n","frontmatter":{"name":"adk-architecture","description":"Explains how the ADK runtime fits together: the node and graph execution model, Context and Event flow, checkpoint and resume, tracing, and the rules governing the public API surface. Use when answering \"how does X work\" about ADK internals, tracing where an event or a piece of state comes from, deciding where a new capability belongs, reviewing a change to BaseNode, Workflow, Runner, Agent, Event or Context, working out why a node re-ran or stayed waiting after a resume, or judging whether a change breaks the public API. Don't use for assembling an agent from existing pieces (use adk-agent-builder), diagnosing one failing run or test (use adk-debug), or formatting and naming conventions (use adk-style)."},"isInternal":false,"tokens":1219,"sizeBytes":4846},{"name":"cli-run.md","path":".agents/skills/adk-debug/references/cli-run.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-debug/references/cli-run.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Debugging with `adk run`\n\n`adk run {agent_dir}` with a trailing query argument runs one turn and exits;\nwithout a query it drops into an interactive prompt. Prefer the query form —\nit needs no human in the loop and composes with shell tooling.\n\n```bash\nadk run --jsonl {agent_dir} \"{query}\"\nadk run --jsonl --in_memory {agent_dir} \"{query}\"    # no persisted session\n```\n\n## Flags\n\nFlag | Default | Why you'd use it\n--- | --- | ---\n`--jsonl` | off | One JSON object per event on stdout. Without it only text parts are printed, so tool calls, tool errors, and actions are invisible.\n`--in_memory` | off | Skip the local session store, so repeated runs cannot contaminate each other.\n`--session_id {id}` | new session | In query mode, reuse that session (creating it if absent) — this is how you carry state across separate `adk run` invocations. In interactive mode it only names the file `--save_session` writes.\n`--state '{json}'` | none | Seed session state for a run that only misbehaves with particular state.\n`--replay {file.json}` | none | Replay a saved state + query list into a fresh session. Mutually exclusive with a query argument.\n`--resume {file.json}` | none | Reopen a session saved by `--save_session` and keep interacting (interactive mode only).\n`--timeout 30s` | none | Bound a hanging turn instead of waiting forever.\n`-v` / `--log_level DEBUG` | `INFO` | Raise log verbosity. Output goes to the log file, not the terminal — see `references/logs-and-traces.md`.\n`--default_llm_model {model}` | none | Override the model for agents that do not set one, e.g. to test whether the model is the problem.\n\nFull list: `adk run --help`.\n\n## JSONL event shape\n\nEach line is `Event.model_dump(mode='json', by_alias=True, exclude_none=True)`,\nso keys are camelCase (`invocationId`, `functionCall`, `longRunningToolIds`),\nwith `session_id` and `node_path` injected and `author` first. Empty `actions`\nentries are dropped, so an absent `actions` key means \"no actions\", not\n\"unknown\".\n\nIn `--jsonl` mode stdout is pure JSONL; the human-readable session banner only\nprints when `--jsonl` is off, and goes to stderr either way. So this is safe:\n\n```bash\nadk run --jsonl {agent_dir} \"{query}\" 2>/dev/null > /tmp/events.jsonl\nhead -1 /tmp/events.jsonl | python3 -m json.tool     # inspect the real shape\n```\n\nRead one event before writing a parser — the schema changes. Then filter on\nwhatever you actually saw, for example every tool call:\n\n```python\nimport json\n\nfor line in open(\"/tmp/events.jsonl\"):\n  event = json.loads(line)\n  for part in (event.get(\"content\") or {}).get(\"parts\", []):\n    if \"functionCall\" in part:\n      print(event[\"author\"], part[\"functionCall\"][\"name\"], part[\"functionCall\"].get(\"args\"))\n```\n\n## Exit codes\n\nCode | Meaning\n--- | ---\n`0` | The turn completed.\n`1` | Error — bad `--state` JSON, both a query and `--replay`, no query and no stdin, timeout, or an exception during the run.\n`2` | Paused. The run emitted an event with `longRunningToolIds`, i.e. a human-in-the-loop tool is waiting.\n\nOn exit 2 the run prints the session id. Resume by re-running with that\n`--session_id` and the answer as the query — ADK maps the query onto the\npending `adk_request_confirmation` / `adk_request_input` function response\nautomatically, so do not try to hand-craft a `FunctionResponse`. For a\nconfirmation, a plain `yes`/`no` works; pass a JSON object to supply a custom\npayload.\n\n## Driving a Runner from Python\n\nUse this when you need to assert on events rather than eyeball them. Two\nthings that bite:\n\n- `new_message` must be a `types.Content`, not a string.\n- `Runner` takes keyword arguments only, and `auto_create_session` defaults to\n  `False`, so the session must exist before you run.\n\n```python\nimport asyncio\n\nfrom google.adk import Agent\nfrom google.adk.runners import InMemoryRunner\nfrom google.genai import types\n\nagent = Agent(name=\"test\", model=\"gemini-2.5-flash\", instruction=\"...\")\nrunner = InMemoryRunner(agent=agent, app_name=\"test\")\n\n\nasync def main():\n  session = await runner.session_service.create_session(\n      app_name=\"test\", user_id=\"u\"\n  )\n  async for event in runner.run_async(\n      user_id=\"u\",\n      session_id=session.id,\n      new_message=types.Content(role=\"user\", parts=[types.Part(text=\"hello\")]),\n  ):\n    print(event.author, event.content)\n    if event.actions.transfer_to_agent:\n      print(\"  -> transfer to\", event.actions.transfer_to_agent)\n    if event.output is not None:\n      print(\"  -> output:\", event.output)\n\n\nasyncio.run(main())\n```\n\n`InMemorySessionService.create_session_sync` still exists but logs a\ndeprecation warning; use the async `create_session`.\n\nTo print events the way the CLI does, without reimplementing the formatting:\n\n```python\nfrom google.adk.utils._debug_output import print_event\n\nprint_event(event)                # text parts only\nprint_event(event, verbose=True)  # plus tool calls, tool results, code, blobs\n```\n\n`verbose` is keyword-only. Source: `src/google/adk/utils/_debug_output.py`.\n","isInternal":false,"tokens":1262,"sizeBytes":5007},{"name":"event-flow.md","path":".agents/skills/adk-debug/references/event-flow.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-debug/references/event-flow.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Event flow and where to look\n\n## One user message becomes events\n\n```text\nRunner.run_async()\n  Runner._exec_with_plugin()          # plugin hooks + persisting events\n    agent.run_async()                 # BaseAgent: before/after agent callbacks\n      LlmAgent._run_async_impl()      # yields events\n        BaseLlmFlow.run_async()\n          SingleFlow | AutoFlow       # AutoFlow adds agent transfer\n            call_llm                  # request build + model call\n            handle_function_calls_async()   # tool dispatch\n```\n\n`LlmAgent._llm_flow` picks `SingleFlow` only when\n`disallow_transfer_to_parent` and `disallow_transfer_to_peers` are both set and\nthe agent has no sub-agents; otherwise it is `AutoFlow`. If an agent refuses to\ntransfer, check those two fields before suspecting the prompt.\n\nWorkflow-graph execution takes a different path: `LlmAgent._run_impl` runs the\nagent as a node via `src/google/adk/workflow/`.\n\n## Callback order\n\nBoth the plugin manager and the agent get a turn, plugins first:\n\nPoint | Plugin manager | Agent\n--- | --- | ---\nBefore model | `run_before_model_callback` | `canonical_before_model_callbacks`\nAfter model | `run_after_model_callback` | `canonical_after_model_callbacks`\nModel error | `run_on_model_error_callback` | `canonical_on_model_error_callbacks`\nBefore tool | `run_before_tool_callback` | `canonical_before_tool_callbacks`\nAfter tool | `run_after_tool_callback` | `canonical_after_tool_callbacks`\nTool error | `run_on_tool_error_callback` | `canonical_on_tool_error_callbacks`\n\nThe manager also exposes run-level hooks with no agent counterpart:\n`run_on_user_message_callback`, `run_before_run_callback`,\n`run_after_run_callback`, `run_on_event_callback`, and the agent/run error\nhooks. Source: `src/google/adk/plugins/plugin_manager.py`.\n\nA plugin callback that returns a value short-circuits the step, so an agent\nthat \"ignores\" its own callback is often a plugin that already answered.\n\n## Event fields worth reading\n\n`Event` serializes with a camelCase alias generator, so JSON from the HTTP API\nor `adk run --jsonl` uses `invocationId`, `functionCall`, `nodeInfo`,\n`longRunningToolIds`, while Python attribute access stays snake_case.\n\nField | Why it matters\n--- | ---\n`author` | `user` or the agent name — the fastest way to see which agent actually spoke.\n`branch` | `agent_1.agent_2` path. Drives which history the agent can see.\n`nodeInfo.path` | Node path inside a workflow, e.g. `wf/A@1/B@1`.\n`content.parts` | `text`, `functionCall`, `functionResponse` — a turn with no `text` part is not a bug, it is a tool round trip.\n`output` | Generic node output value. Absent on ordinary chat events.\n`longRunningToolIds` | Present means the run is parked on a human-in-the-loop tool.\n`actions.transferToAgent` | The agent handed control to a named agent.\n`actions.escalate` | The agent gave up to its parent, typically ending a loop.\n`actions.endOfAgent` | The agent finished.\n`actions.stateDelta` / `artifactDelta` | State and artifact writes made by this event.\n\n`isolationScope` also appears on task-agent events; it is internal, so read it\nfor orientation but do not build on it. Source:\n`src/google/adk/events/event.py`, `src/google/adk/events/event_actions.py`.\n\n## Source map\n\nArea | File\n--- | ---\nRunner and event persistence | `src/google/adk/runners.py`\nFlow driver, LLM call, callbacks | `src/google/adk/flows/llm_flows/base_llm_flow.py`\nRequest assembly (model, tools, schema) | `src/google/adk/flows/llm_flows/basic.py`\nWhich history reaches the model | `src/google/adk/flows/llm_flows/contents.py`\nTool dispatch and tool errors | `src/google/adk/flows/llm_flows/functions.py`\nAgent transfer | `src/google/adk/flows/llm_flows/agent_transfer.py`\nAgent config and validation | `src/google/adk/agents/llm_agent.py`\nInvocation state and call limits | `src/google/adk/agents/invocation_context.py`\nTask agents | `src/google/adk/agents/llm/task/`\nGraph orchestration | `src/google/adk/workflow/`\nEvent model | `src/google/adk/events/event.py`\nSession services | `src/google/adk/sessions/`\nPlugin hook ordering | `src/google/adk/plugins/plugin_manager.py`\nHTTP API (production-safe routes) | `src/google/adk/cli/api_server.py`\nDev-only routes, including traces | `src/google/adk/cli/dev_server.py`\nAgent discovery | `src/google/adk/cli/utils/agent_loader.py`\nLog setup | `src/google/adk/cli/utils/logs.py`\nTracing and span attributes | `src/google/adk/telemetry/tracing.py`\nEvent printer used by the CLI | `src/google/adk/utils/_debug_output.py`\n\n`src/google/adk/cli/adk_web_server.py` is a deprecated shim; `AdkWebServer` now\njust subclasses `DevServer`. Read `api_server.py` / `dev_server.py` instead.\n","isInternal":false,"tokens":1159,"sizeBytes":4681},{"name":"failure-modes.md","path":".agents/skills/adk-debug/references/failure-modes.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-debug/references/failure-modes.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# ADK failure modes\n\nMatch the symptom first; each entry names the mechanism and a check you can\nactually run.\n\n## The agent emits raw JSON instead of calling tools\n\n`output_schema` puts the model into controlled generation: it sets\n`config.response_schema` and `config.response_mime_type =\n\"application/json\"` on the request, and a model in JSON mode returns JSON, not\ntool calls.\n\nCheck the `call_llm` span's `gcp.vertex.agent.llm_request` for\n`response_mime_type` (`references/logs-and-traces.md`). ADK only\napplies the schema when the agent has no tools, or when the model can do\nschemas and tools together — so the opposite symptom, a schema that seems to be\nignored, means you landed on the other branch.\n\nSource: `src/google/adk/flows/llm_flows/basic.py`,\n`src/google/adk/utils/output_schema_utils.py`.\n\n## `ValueError` when constructing the agent\n\nThree messages come from the same validator, all meaning \"you set this on\n`generate_content_config` instead of on the agent\":\n\n- `All tools must be set via LlmAgent.tools.`\n- `System instruction must be set via LlmAgent.instruction.`\n- `Response schema must be set via LlmAgent.output_schema.`\n\nSource: `LlmAgent.validate_generate_content_config` in\n`src/google/adk/agents/llm_agent.py`.\n\n## `LlmCallsLimitExceededError: Max number of llm calls limit of N exceeded`\n\n`run_config.max_llm_calls` was hit. Treat the limit as a loop detector before\nraising it — dump the events and look for the same tool being called with the\nsame arguments turn after turn. Source:\n`src/google/adk/agents/invocation_context.py`.\n\n## A tool \"fails\" but the agent carries on\n\nTool failures are converted into a function response carrying the error, so the\nmodel sees a result and keeps going. Look for a `functionResponse` whose payload\nhas an `error` key. `FunctionTool` produces the same shape for two non-exception\ncases: missing mandatory arguments, and a confirmation-required tool that was\nnot confirmed or was rejected.\n\nTo intervene, register `on_tool_error_callback` on a plugin or\n`on_tool_error_callbacks` on the agent. Source:\n`src/google/adk/flows/llm_flows/functions.py`,\n`src/google/adk/tools/function_tool.py`.\n\n## `adk web` does not list the agent, or returns 404\n\n```bash\ncurl -s http://localhost:8000/list-apps | python3 -m json.tool\n```\n\nThe loader accepts four layouts under `{agents_dir}`, checking for a top-level\n`app` before `root_agent`:\n\n```text\n{name}/agent.py          # defines root_agent (or app)\n{name}.py                # defines root_agent (or app)\n{name}/__init__.py       # defines root_agent (or app) in the package\n{name}/root_agent.yaml   # config-defined agent\n```\n\n`__init__.py` does not need `from . import agent` — the loader imports the\n`agent` submodule itself. Pointing `adk web` at a directory that itself contains\n`agent.py` or `root_agent.yaml` runs that single agent instead of treating the\ndirectory as a collection. Source:\n`src/google/adk/cli/utils/agent_loader.py`.\n\n## A sub-agent cannot see the parent conversation\n\nEvents carry a `branch` (`agent_1.agent_2.agent_3`), and the content builder\ndrops events that do not belong to the current agent's branch — that isolation\nis deliberate, so peers do not read each other's history. Delegated task agents\nare isolated further by `isolation_scope`.\n\nThere is no flag to switch it off. Put whatever the sub-agent needs into the\ndelegation input; the sub-agent's `description` is what steers the parent into\nincluding it. Source: `_is_event_belongs_to_branch` in\n`src/google/adk/flows/llm_flows/contents.py`.\n\n## The whole agent stalls while one tool runs\n\nA synchronous tool function is awaited inline on the event loop, so anything\nblocking inside it — a `requests` call, `time.sleep`, a large file read —\nfreezes the entire run, not just that tool.\n\nMake the callable `async`, or hand synchronous `FunctionTool` and `FunctionNode`\ncallables (and all tools in live mode) to a thread pool:\n\n```python\nfrom google.adk.agents.run_config import RunConfig, ToolThreadPoolConfig\n\nrun_config = RunConfig(tool_thread_pool_config=ToolThreadPoolConfig())  # 4 workers\n```\n\nSource: `_resolve_sync_callable_runner` in\n`src/google/adk/utils/_sync_runner.py`, `FunctionTool._invoke_callable` in\n`src/google/adk/tools/function_tool.py`, and `_call_tool_in_thread_pool` in\n`src/google/adk/flows/llm_flows/tools/_thread_pool.py`.\n\n## The run stops early and nothing looks wrong\n\n`adk run` exits 2 when an event carries `longRunningToolIds`: a\nhuman-in-the-loop tool is waiting for an answer. See\n`references/cli-run.md` for how to resume.\n\n## The answer is cut off, empty, or blocked\n\nRead `gen_ai.response.finish_reasons` on the `call_llm` span rather than\ninferring from the text — `max_tokens` means raise `max_output_tokens`,\n`safety` and `recitation` mean the model refused. See\n`references/logs-and-traces.md`.\n\n## The run ends with `error_code=\"INVOCATION_ABORTED\"`\n\n`abort_signal` was set on `runner.run_async`, or the HTTP client disconnected\nfrom `/run_sse` before the stream finished. The runner synthesizes a terminal\nevent with `error_code=\"INVOCATION_ABORTED\"` and seals any in-flight\n`FunctionCall` with a `FunctionResponse` carrying `{\"error\": \"Invocation was\naborted by client.\"}` so the session remains valid for the next turn. Source: `Runner._synthesize_abort_events_if_needed` in\n`src/google/adk/runners.py`, `src/google/adk/cli/api_server.py`.\n","isInternal":false,"tokens":1329,"sizeBytes":5400},{"name":"logs-and-traces.md","path":".agents/skills/adk-debug/references/logs-and-traces.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-debug/references/logs-and-traces.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Logs and traces\n\n## Where the logs actually go\n\nThis differs per command and is the most common reason \"I turned on `-v` and\nsaw nothing\".\n\nCommand | Destination\n--- | ---\n`adk run` | `{tempdir}/agents_log/agent.{timestamp}.log`, i.e. `/tmp/agents_log/...` on Linux, with an `agent.latest.log` symlink. It clears the root logger's handlers, so **nothing** is logged to the terminal.\n`adk web`, `adk api_server`, `adk eval`, everything else | stderr, via `logging.basicConfig`. No log file is created.\n\n```bash\nadk run -v {agent_dir} \"{query}\"\ntail -F /tmp/agents_log/agent.latest.log\n```\n\nFor `adk web`, tee stderr into a file so both you and the user can read it:\n\n```bash\nadk web -v {agents_dir} 2>&1 | tee {readable_path}/adk_web.log\n```\n\n`-v` is a shortcut for `--log_level DEBUG`; the levels are `DEBUG`, `INFO`,\n`WARNING`, `ERROR`, `CRITICAL`. ADK's own records go to the `google_adk`\nlogger, so filter with `grep google_adk` or raise only that logger in-process.\nSetup lives in `src/google/adk/cli/utils/logs.py`.\n\n## Trace endpoints\n\nOnly `adk web` registers these. `adk api_server` runs the production-safe\n`ApiServer`, which has no `/dev/...` routes, so trace lookups there 404.\n\n```bash\n# Every span for a session\ncurl -s http://localhost:8000/dev/apps/{app_name}/debug/trace/session/{session_id} \\\n  | python3 -m json.tool\n\n# The single trace recorded against one event id\ncurl -s http://localhost:8000/dev/apps/{app_name}/debug/trace/{event_id} \\\n  | python3 -m json.tool\n```\n\nThe session response is a list of spans, each with `name`, `span_id`,\n`trace_id`, `parent_span_id`, `start_time`, `end_time`, and `attributes`.\nSpans are kept in memory by the running server, so restarting it loses them.\n\nSpan name | What it covers\n--- | ---\n`call_llm` | One model call, including the `before_model` / `after_model` callbacks.\n`execute_tool (merged)` | A batch of tool calls dispatched from one model response.\n`generate_content {model}` | The underlying GenAI SDK call, when the OTel GenAI instrumentation is active.\n\n## Reading what the model actually received\n\nPull the `call_llm` spans and decode `gcp.vertex.agent.llm_request` — it is a\nJSON *string* holding `contents`, `config` (tools, `response_schema`,\n`response_mime_type`, `system_instruction`), and `model`. This is the ground\ntruth for \"why did the model do that\": compare it against what you believe the\nagent is configured to send.\n\nAttribute | Meaning\n--- | ---\n`gcp.vertex.agent.llm_request` | Full request as a JSON string.\n`gcp.vertex.agent.llm_response` | Full response as a JSON string.\n`gcp.vertex.agent.tool_call_args` / `.tool_response` | Tool arguments and result.\n`gcp.vertex.agent.event_id` | Correlates the span with an event in the session.\n`gcp.vertex.agent.invocation_id` / `.session_id` | Correlates spans across one turn.\n`gen_ai.request.model` | Model name as sent.\n`gen_ai.usage.input_tokens` / `.output_tokens` | Token counts — check these before blaming a prompt for being ignored.\n`gen_ai.response.finish_reasons` | List of lowercased reasons, e.g. `[\"max_tokens\"]` for a truncated answer or `[\"safety\"]` for a filtered one.\n\nIf the content-bearing attributes come back as `\"{}\"`, content capture is off —\nsee the env vars below, not a bug in the agent.\n\n## Environment variables\n\nVariable | Effect\n--- | ---\n`ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS` | Whether prompts and responses are written onto the legacy `gcp.vertex.agent.*` span attributes. Defaults to `true`; set `false` to strip content.\n`OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` | OTel-spec content capture for the GenAI semantic-convention spans and log records.\n`ADK_TELEMETRY_SCHEMA_VERSION_OPT_IN` | Pins the telemetry schema to `1` (default off Agent Engine) or `2`. Under `2` the `invocation` span becomes `invoke_workflow` and `call_llm` goes away, so check this first if the span names above are missing.\n`GOOGLE_CLOUD_PROJECT` | Required by `adk web --trace_to_cloud`; without it the server logs a warning and exports nothing.\n\n`--trace_to_cloud` only exports traces. `--otel_to_cloud` is the newer flag and\ncovers Cloud Trace plus Cloud Logging; `adk deploy agent_engine` already warns\nthat `--trace_to_cloud` is being replaced by it.\n\nSource: `src/google/adk/telemetry/tracing.py`,\n`src/google/adk/telemetry/context.py`,\n`src/google/adk/telemetry/_schema_version.py`.\n","isInternal":false,"tokens":1136,"sizeBytes":4352},{"name":"web-api.md","path":".agents/skills/adk-debug/references/web-api.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-debug/references/web-api.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Debugging with `adk web`\n\n`adk web` is a FastAPI server: a browser UI at `http://localhost:8000/dev-ui/`\nplus the HTTP API below. Reach for it when you need to click through a\npersisted session, or when you need the trace endpoints\n(`references/logs-and-traces.md`).\n\n## Starting the server\n\nCheck for an existing server before starting your own — a second one will fail\nto bind port 8000, and the user may already have one with the sessions you care\nabout:\n\n```bash\ncurl -s http://localhost:8000/health     # {\"status\":\"ok\"} if one is running\n```\n\nIf none is running, start it in the background and shut it down when you are\ndone:\n\n```bash\nadk web {agents_dir}                     # http://127.0.0.1:8000\nadk web -v --reload_agents {agents_dir}\n```\n\n`{agents_dir}` is a directory of agent subdirectories, or a single agent folder\n(one containing `agent.py` or `root_agent.yaml`). It defaults to the current\ndirectory.\n\nFlag | Default | Note\n--- | --- | ---\n`--port` | `8000` | Use a second port to run two servers side by side.\n`--host` | `127.0.0.1` | Endpoints are unauthenticated; keep it on loopback.\n`--reload_agents` | off | Re-import agent modules when their files change. This is the one you want while editing an agent.\n`--reload` | **on** | Uvicorn's own source autoreload. Pass `--no-reload` when a restart mid-run is confusing you.\n`-v` / `--log_level` | `INFO` | Logs go to the terminal, not to a file — see `references/logs-and-traces.md`.\n\n`adk api_server` takes the same flags but serves only the production-safe\nroutes — no UI and no `/dev/...` debug or trace endpoints. Use `adk web` when\ndebugging.\n\n## Inspecting sessions\n\n```bash\ncurl -s http://localhost:8000/list-apps | python3 -m json.tool\n\ncurl -s http://localhost:8000/apps/{app_name}/users/{user_id}/sessions \\\n  | python3 -m json.tool\n\ncurl -s http://localhost:8000/apps/{app_name}/users/{user_id}/sessions/{session_id} \\\n  | python3 -m json.tool\n```\n\nThe session response holds the full event list. Fetch the raw JSON and write a\nsummarizer against the structure you actually see rather than a remembered\nschema. Fields worth pulling per event: `author`, `branch`, `nodeInfo.path`,\n`content.parts` (`text`, `functionCall`, `functionResponse`), `output`, and\n`actions` (`transferToAgent`, `escalate`, `endOfAgent`). Keys are camelCase.\n\n`DELETE .../sessions/{session_id}` exists; do not use it to tidy up after\nyourself, because the user may still want the session in the UI.\n\n## Sending a test message\n\nCreate a session, then post one turn. `/run` returns the whole event list as\nJSON, which is far easier to assert on than a stream:\n\n```bash\nSESSION=$(curl -s -X POST http://localhost:8000/apps/{app_name}/users/test/sessions \\\n  -H \"Content-Type: application/json\" -d '{}' \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)['id'])\")\n\ncurl -s -X POST http://localhost:8000/run \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"app_name\\\":\\\"{app_name}\\\",\\\"user_id\\\":\\\"test\\\",\\\"session_id\\\":\\\"$SESSION\\\",\n       \\\"new_message\\\":{\\\"role\\\":\\\"user\\\",\\\"parts\\\":[{\\\"text\\\":\\\"{query}\\\"}]}}\" \\\n  | python3 -m json.tool\n```\n\nUse `/run_sse` with `\"streaming\":true` and `curl -N` only when the bug is in\nstreaming itself — partial events, chunk ordering, or a stream that never\nterminates.\n\nA `POST` to a session id that does not exist returns 404 rather than creating\nit, so create the session first. Supply your own id by passing\n`{\"session_id\": \"...\"}` in the create body when you want a stable id across\nruns.\n","isInternal":false,"tokens":947,"sizeBytes":3505},{"name":"SKILL.md","path":".agents/skills/adk-debug/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-debug/SKILL.md","title":"adk-debug","category":"anthropic-skill","format":"markdown","content":"---\nname: adk-debug\ndescription: >-\n  Diagnoses misbehaving ADK agents by inspecting sessions, events, tool calls,\n  and the exact request that reached the model. Covers the `adk run` CLI and the\n  `adk web` dev server with its session, trace, and debug HTTP endpoints. Use\n  when an agent returns the wrong answer, ignores a tool or swallows a tool\n  error, hangs, loops, emits raw JSON instead of calling tools, is not\n  discovered by `adk web`, when a sub-agent cannot see the parent conversation,\n  or when you need the LLM request/response, token counts, or logs for a run.\n  Don't use for how ADK is designed internally (use `adk-architecture`), for\n  building a new agent or workflow (use `adk-agent-builder`), for environment or\n  dependency setup failures (use `adk-setup`), or for lint and style nits (use\n  `adk-style`).\n---\n\n# Debugging ADK agents\n\nTwo entry points. Default to `adk run`: one process, no server, and `--jsonl`\noutput that pipes straight into `grep` or `python3`. Switch to `adk web` when\nyou need the browser UI, a persisted session you can click through, or the\ntrace endpoints that expose the exact LLM request.\n\n## First moves\n\n1. Reproduce headlessly: `adk run --jsonl {agent_dir} \"{query}\"`. Without\n   `--jsonl`, `adk run` prints only text parts — tool calls and tool errors are\n   invisible.\n2. Read the log file. `adk run` writes to `/tmp/agents_log/agent.latest.log` and\n   nothing to the terminal; `adk web` does the opposite. See\n   [logs-and-traces.md](references/logs-and-traces.md).\n3. Match the symptom in [failure-modes.md](references/failure-modes.md) before\n   reading source — most reports are one of a handful of known shapes.\n4. If the text is fine but the routing is not, dump the events and read\n   `author`, `branch`, `nodeInfo.path`, and `actions` —\n   [event-flow.md](references/event-flow.md).\n5. If the model itself misbehaved, read what it actually received from the\n   `call_llm` span rather than guessing from the agent definition —\n   [logs-and-traces.md](references/logs-and-traces.md).\n\n## References\n\n- [cli-run.md](references/cli-run.md) — `adk run` flags, the JSONL event shape,\n  multi-turn and human-in-the-loop resume, exit codes, driving a `Runner` from\n  Python.\n- [web-api.md](references/web-api.md) — starting `adk web`, listing and reading\n  sessions over HTTP, posting test messages to `/run_sse`.\n- [logs-and-traces.md](references/logs-and-traces.md) — log levels and where\n  each command writes them, the trace endpoints, span attributes, and the env\n  vars that control whether prompts appear in spans.\n- [failure-modes.md](references/failure-modes.md) — ADK-specific symptoms with\n  the cause and a concrete check for each.\n- [event-flow.md](references/event-flow.md) — how an invocation becomes events,\n  callback order, the event fields that matter, and where each stage lives in\n  the source.\n\n## Ground rules\n\n- Leave sessions in place when you finish. The user may still want to open them\n  in the web UI, and `adk web` has no undelete.\n- Delete any throwaway agent you created for a repro, unless the user asked to\n  keep it.\n- Reach for a unit test in `tests/unittests/` when the bug is inside one\n  component, and for a sample under `contributing/samples/` (see\n  `adk-sample-creator`) when it only reproduces with runner, agent, and workflow\n  wired together.\n","frontmatter":{"name":"adk-debug","description":"Diagnoses misbehaving ADK agents by inspecting sessions, events, tool calls, and the exact request that reached the model. Covers the `adk run` CLI and the `adk web` dev server with its session, trace, and debug HTTP endpoints. Use when an agent returns the wrong answer, ignores a tool or swallows a tool error, hangs, loops, emits raw JSON instead of calling tools, is not discovered by `adk web`, when a sub-agent cannot see the parent conversation, or when you need the LLM request/response, token counts, or logs for a run. Don't use for how ADK is designed internally (use `adk-architecture`), for building a new agent or workflow (use `adk-agent-builder`), for environment or dependency setup failures (use `adk-setup`), or for lint and style nits (use `adk-style`)."},"isInternal":false,"tokens":873,"sizeBytes":3366},{"name":"SKILL.md","path":".agents/skills/adk-git/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-git/SKILL.md","title":"adk-git","category":"anthropic-skill","format":"markdown","content":"---\nname: adk-git\ndescription: >-\n  Writes commit messages and pull request descriptions for the adk-python\n  repository: Conventional Commits types and scopes, subject lines that say why\n  a change was made, and the linked-issue and testing-plan sections the PR\n  template requires. Use when writing or rewording a commit message, squashing\n  commits before a pull request, drafting a PR description, or checking that a\n  change is shaped to land. Don't use for generic git mechanics such as\n  rebasing, resolving conflicts, cherry-picking, or branch surgery; those need\n  no skill. Don't use to judge the content of a change (use adk-review) or for\n  code style and naming (use adk-style).\n---\n\n# Commit and Pull Request Conventions\n\n## Commit message format\n\nConventional Commits:\n\n```text\n<type>(<scope>): <description>\n```\n\nThe type decides where the commit lands in `CHANGELOG.md`. `release-please`\ngenerates the changelog from merged commit subjects, so the wrong type either\nfiles the change under the wrong heading or drops it from the release notes\nentirely.\n\n| Type                                               | Changelog section        |\n| -------------------------------------------------- | ------------------------ |\n| `feat`                                             | Features                 |\n| `fix`                                              | Bug Fixes                |\n| `perf`                                             | Performance Improvements |\n| `docs`                                             | Documentation            |\n| `refactor`, `test`, `build`, `ci`, `style`, `chore` | hidden, no entry         |\n\nThe mapping lives in `.github/release-please-config.json`. A type that is not\nlisted there produces no changelog entry at all.\n\nScope is optional. Use a short module name with no underscores\n(`fix(cli):`, `feat(a2a):`, `fix(sessions):`) or leave it off.\n\n## Subject line\n\nSay why the change exists, not which lines moved. A reviewer who reads only the\nsubject should understand the motivation.\n\n| Write                                                        | Not                                                            |\n| ------------------------------------------------------------ | -------------------------------------------------------------- |\n| `fix(sessions): prevent duplicate events when resuming HITL` | `fix(sessions): check interrupt_id before appending`           |\n| `feat(workflow): support parallel tool execution`            | `feat(workflow): add asyncio.gather call in execute_tools_node` |\n| `refactor: make graph public for dev UI serialization`       | `refactor: make graph a public field on Workflow`              |\n\nRules:\n\n1. Imperative mood: `add`, not `added`.\n2. Lowercase the first word after the colon. `release-please` copies the\n   subject into `CHANGELOG.md` verbatim, and the great majority of merged\n   commits are lowercase, so capitalizing makes one line stand out.\n3. No trailing period.\n4. Keep the subject under about 72 characters. Nothing enforces this, but each\n   commit renders as one changelog line.\n5. Reference the issue in the body, not the subject: `Fixes #1234` or\n   `Closes #1234`, or the full issue URL when the issue lives in another\n   repository.\n\nSelf-check: read the subject back and ask whether it says *why* someone made\nthe change. If it only names the edit, rewrite it.\n\n## Commit body\n\nAdd a blank line, then a short concrete explanation. For a feature, show the\nnew capability or a usage line. For a fix, say what caused the failure and how\nthe change addresses it.\n\n```text\nfeat(workflow): support JSON string parsing in schema validation\n\nParse JSON strings into dicts or Pydantic models when input_schema or\noutput_schema is defined on a node.\n```\n\n```text\nfix(sessions): prevent duplicate events when resuming HITL\n\ninterrupt_id was not checked before appending, so resuming twice appended the\nsame event twice. Ignore interrupts that were already processed.\n\nFixes #1234\n```\n\n## Before committing\n\n`pre-commit` reformats and checks staged files, and the same hooks run again in\nCI on every pull request, so a commit made with hooks skipped fails there.\n\n```bash\npre-commit install                  # once per clone\npre-commit run --files {paths}      # check only what changed\n```\n\nThe hooks include `isort`, `pyink`, `addlicense`, `mdformat`, `ruff`,\n`codespell`, and repository-local compliance checks; see\n`.pre-commit-config.yaml`. If `pre-commit` is not installed, point the user at\nthe `adk-setup` skill rather than committing unformatted code.\n\n## Pull requests\n\n- Every PR except a small documentation or typo fix needs a linked issue.\n  Put `Closes: #{issue_number}` in the PR description, or describe the problem\n  and solution inline following the issue templates.\n- Fill in the Testing Plan section of `.github/pull_request_template.md`,\n  including a summary of passing `pytest` results.\n- Do not merge on GitHub. The `Do Not Merge on GitHub` check fails on every PR\n  to `main` by design; a maintainer lands the change and it is synced back to\n  the repository. GitHub then shows the PR as closed with a `merged` label\n  rather than merged, and the landed commit carries the original authorship.\n  That red check is expected and is not something to fix.\n","frontmatter":{"name":"adk-git","description":"Writes commit messages and pull request descriptions for the adk-python repository: Conventional Commits types and scopes, subject lines that say why a change was made, and the linked-issue and testing-plan sections the PR template requires. Use when writing or rewording a commit message, squashing commits before a pull request, drafting a PR description, or checking that a change is shaped to land. Don't use for generic git mechanics such as rebasing, resolving conflicts, cherry-picking, or branch surgery; those need no skill. Don't use to judge the content of a change (use adk-review) or for code style and naming (use adk-style)."},"isInternal":false,"tokens":1135,"sizeBytes":5291},{"name":"SKILL.md","path":".agents/skills/adk-review/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-review/SKILL.md","title":"adk-review","category":"anthropic-skill","format":"markdown","content":"---\nname: adk-review\ndescription: >-\n  Reviews the uncommitted changes in an adk-python working tree and reports\n  correctness, design, public-API stability, test, sample and documentation\n  gaps as a prioritized findings report, fixing them only when asked. Use when\n  the user asks to review local changes, wants a self-review before opening a\n  pull request, asks whether a change breaks the public API or needs tests,\n  samples or docs, or asks what is wrong with the current diff. Required for\n  changes to public APIs, core architecture (Runner, Workflow, BaseNode), new\n  features and major refactors. Don't use for a single style nit (use\n  adk-style), for diagnosing a failing test or a misbehaving agent at runtime\n  (use adk-debug), or for wording a commit message or PR description (use\n  adk-git).\n---\n\n# ADK Change Reviewer\n\nReview the working-tree diff against the seven dimensions below, report what is\nwrong, and stop. Fix only what the user then asks you to fix.\n\n## Workflow\n\n1. `git status` and `git diff` (add `--staged` for staged work) to get the exact\n   set of added, modified, and deleted files.\n2. Review the diff file by file against the checklist.\n3. Emit the report in the format below.\n4. Stop. Do not edit any file, and do not offer to. Wait for the user to ask.\n5. If the user asks for fixes, apply them, then re-run the affected tests\n   (`pytest tests/unittests/{path}`) and `pre-commit run --files {paths}`.\n\nStep 4 is the part that is easy to get wrong: an unrequested fix buries the\nfindings the user asked for and mixes review output with new, unreviewed edits.\n\n## Checklist\n\n### 1. Correctness\n\n- **Types**: no new `mypy` errors. CI diffs `mypy` output against the base\n  branch and fails only on *newly introduced* errors, so a pre-existing error in\n  a file you touched is not a blocker but a new one is.\n- **Imports**: no circular imports; absolute imports where the module already\n  uses them.\n- **Exceptions**: no bare `except:`; catch a specific type and log with enough\n  context to identify the caller.\n- **Type discrimination**: check an object's type before reading a\n  type-specific attribute (for example confirm a node is an `LlmAgent` before\n  inspecting `mode`), so an unexpected node type raises nothing.\n- **Boundaries**: `None`, empty collections, zero, and empty strings are\n  handled by validation or a fallback default.\n- **Preconditions**: state invariants are checked before the core logic runs.\n\n### 2. Design\n\n- **Complexity**: functions or classes that would be clearer split up.\n- **Coupling**: high cohesion, low coupling; no anti-patterns introduced.\n- **Performance**: redundant computation, repeated I/O in a loop, or\n  allocations that scale with input where they need not.\n- **Security**: inputs validated, sensitive data not logged, no injection,\n  resource exhaustion, or exposure of internal state.\n\n### 3. Style\n\nCross-reference the diff against the `adk-style` skill rather than restating\nits rules here: visibility and `_` prefixes, typing, Pydantic v2 patterns, lazy\nlogging, imports, async, and file organization.\n\nConfirm the changed files pass `pre-commit run --files {paths}`.\n\n### 4. Architecture and unintended outcomes\n\n- **Public API stability**: does the change modify, remove, or narrow a\n  public interface, class, method, argument list, or CLI surface under\n  `src/google/adk/`? A breaking change needs a deprecation cycle first, because\n  the package is released under Semantic Versioning and users pin minor\n  versions.\n- **Session, Artifact, and Memory service changes**: adding any new field to\n  `Session`, `ArtifactVersion`, `MemoryEntry`, or to `BaseSessionService`,\n  `BaseArtifactService`, `BaseMemoryService` and their schemas is a **breaking\n  change** (even if optional or defaulted), because existing downstream storage\n  subclasses and external backends will not persist or deserialize unknown\n  fields, causing silent state loss upon reload.\n- **Execution and resumption**: changes to workflows, nodes, or state must stay\n  compatible with the event execution lifecycle and with session resumption\n  (human-in-the-loop steps and checkpoints). See the `adk-architecture` skill.\n- **Concurrency and lifetime**: no race conditions; plugins, exporters, and\n  connections are closed on every path, including the error path.\n- **Database and runtime DDL safety**: runtime startup paths (e.g.,\n  `DatabaseSessionService.prepare_tables()` and\n  `_ensure_schema_indexes_exist()`) must never execute destructive DDL such as\n  `DROP INDEX` (which takes `ACCESS EXCLUSIVE` locks and breaks rolling\n  deployments/rollbacks). Any additive runtime DDL must account for\n  multi-container startup races (process-local `asyncio.Lock` does not prevent\n  cross-container TOCTOU races on `checkfirst=True`) by wrapping creation in a\n  `SAVEPOINT` (`connection.begin_nested()`) and tolerating concurrent creation.\n- **Scope**: a new public name, parameter or flag the fix did not need; a flag\n  that keeps the buggy behavior as the default; a fix in a shared layer when\n  one caller needed it; an existing test assertion edited to match the new\n  behavior.\n\n### 5. Documentation impact\n\n- User-facing documentation lives in the separate `adk-docs` repository, so a\n  user-visible change needs a PR there as well; note it in the report.\n- Guides under `docs/guides/` may need an update when a public API or workflow\n  pattern changes.\n- If the change alters a code unit's design contract, the `adk-unit-design`\n  skill owns the design document for that unit.\n\n### 6. Samples\n\n- Do existing samples under `contributing/samples/` still run against the\n  change?\n- Does a new capability warrant a new sample? Follow the `adk-sample-creator`\n  conventions if so.\n\n### 7. Tests\n\n- Every new or modified code path has a test under `tests/unittests/`.\n- A new test belongs in the existing `test_{module}*.py` file for its unit, not\n  in a file named after the change, which fragments that unit's coverage.\n- Tests follow the rules in the `adk-style` testing reference: one behavior per\n  test, behavior-named tests, no assertions on private attributes, minimal\n  fixtures, arrange/act/assert structure.\n\n## Ground each finding\n\nBefore reporting a finding, resolve the symbol through base classes (`Event`\nextends `LlmResponse`). For a correctness finding, name the input that makes it\nfail; for any other finding, quote the rule or convention it breaks. A finding\nthat is neither is a guess; leave it out.\n\n## Report format\n\nGroup findings by priority and give a file path and line for each. Do not\nreport a dimension with nothing to say.\n\n- 🔴 **Critical**: bugs, type errors, race conditions, resource leaks, security\n  issues.\n- 🟠 **Design**: complexity, readability, performance, architectural\n  misalignment.\n- 🟡 **Style**: lint, formatting, non-lazy logging, typing mismatches.\n- 🔵 **Docs, tests, samples**: missing or stale coverage, guides, samples.\n","frontmatter":{"name":"adk-review","description":"Reviews the uncommitted changes in an adk-python working tree and reports correctness, design, public-API stability, test, sample and documentation gaps as a prioritized findings report, fixing them only when asked. Use when the user asks to review local changes, wants a self-review before opening a pull request, asks whether a change breaks the public API or needs tests, samples or docs, or asks what is wrong with the current diff. Required for changes to public APIs, core architecture (Runner, Workflow, BaseNode), new features and major refactors. Don't use for a single style nit (use adk-style), for diagnosing a failing test or a misbehaving agent at runtime (use adk-debug), or for wording a commit message or PR description (use adk-git)."},"isInternal":false,"tokens":1628,"sizeBytes":6947},{"name":"readme-template.md","path":".agents/skills/adk-sample-creator/references/readme-template.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-sample-creator/references/readme-template.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Sample README structure\n\nEvery sample gets a `README.md` with these sections, in this order.\n\n## Overview\n\nWhat the sample does and which feature or pattern it exists to demonstrate.\n\n## Sample Inputs\n\nPrompts a reader can paste in to exercise the sample. Wrap each prompt in\nbackticks. If a prompt needs an explanation, leave a blank line between the\nprompt and the explanation and indent the explanation by two spaces — without\nthe blank line Markdown folds them into one list item.\n\n## Graph\n\nA Mermaid diagram of the structure, not of the request/response flow.\n\n-   For a `Workflow` root agent, draw the graph of nodes and edges.\n-   For an agent that orchestrates tools or sub-agents (`LlmAgent`,\n    `ManagedAgent`), draw the topology of the agent and its tools/sub-agents\n    instead of internal workflow nodes.\n\nKeep it to a few nodes and edges. A `user -> agent -> API -> tool -> user`\nsequence diagram is noise: it says nothing the topology does not.\n\n## How To\n\nThe key techniques the sample uses (for example `ctx.run_node`), with the few\nlines of code that show each one.\n\n## Related Guides\n\nLinks to the guides under `docs/guides/` that explain the classes used, each\nwith a one-line summary. From a sample at\n`contributing/samples/{category}/{sample_name}/` the guides are four levels up:\n\n```markdown\n- [Workflow](../../../../docs/guides/workflow/workflow/index.md) - Explains building complex multi-step graphs.\n```\n\n## Template\n\n````markdown\n# ADK Sample Name\n\n## Overview\n\nBrief description.\n\n## Sample Inputs\n\n- `Prompt example 1`\n\n- `Prompt example 2`\n\n  *Explanation or expected behavior*\n\n## Graph\n\nFor a Workflow root agent:\n\n```mermaid\ngraph TD\n    START --> MyNode\n```\n\nFor an agent orchestrating tools or sub-agents:\n\n```mermaid\ngraph TD\n    MyAgent[my_agent] -->|calls| MyTool(my_tool)\n```\n\n## How To\n\nExplain the details.\n\n## Related Guides\n\n- [Guide Title](../../../../docs/guides/path/to/guide.md) - Brief description of what the guide covers.\n````\n","isInternal":false,"tokens":474,"sizeBytes":1985},{"name":"SKILL.md","path":".agents/skills/adk-sample-creator/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-sample-creator/SKILL.md","title":"adk-sample-creator","category":"anthropic-skill","format":"markdown","content":"---\nname: adk-sample-creator\ndescription: >-\n  Creates a new sample agent in the ADK Python repository — the sample\n  directory, its `agent.py`, and its `README.md` — following the conventions\n  the existing samples already use. Use when the user wants to add a sample or\n  example demonstrating a feature or agent pattern (dynamic nodes,\n  fan-out/fan-in, a standalone tool-using agent), asks where a new sample\n  belongs under `contributing/samples/`, or wants an existing sample's README\n  brought up to the standard structure. Don't use for building a real working\n  agent for the user's own project (use `adk-agent-builder`), or for checking\n  whether the Python blocks in a Markdown file run (use `adk-verify-snippets`).\n---\n\n# ADK Sample Creator\n\nCreates samples under `contributing/samples/`. These are deliberately minimal\nagents that each exercise one or two features — distinct from the `adk-samples`\nrepository, which hosts full end-to-end applications.\n\nRead the `adk-style` skill first for ADK 2.0 conventions if you have not\nalready.\n\n## 1. Pick the category directory\n\nAlmost every sample lives at\n`contributing/samples/{category}/{sample_name}/`. List the categories and\nconfirm with the user which one the sample belongs in before creating\nanything — a workflow sample landing outside `workflows/` is the usual mistake.\n\n```bash\nls contributing/samples/\n```\n\nCategories include `workflows`, `patterns`, `core`, `multi_agent`, `tools`,\n`models`, `live`, `mcp`, `a2a`, `evaluation`, and `plugins`. A handful of\nsamples nest one level further when a single feature needs several variants, as\n`plugins/plugin_reflect_tool_retry/basic/` does.\n\nName the sample directory in `snake_case` after the feature it demonstrates:\n`dynamic_nodes`, `fan_out_fan_in`, `streaming_tool_events`.\n\nDo not add an `_agent` suffix, and do not repeat the category as a prefix —\nevery sample is an agent, and the category is already in the path. Many existing\ndirectories still carry both; do not copy them.\n\n## 2. Write `agent.py`\n\nContents of a sample directory:\n\n| File | Required | Purpose |\n| --- | --- | --- |\n| `agent.py` | yes | The agent or workflow. Must expose `root_agent`. |\n| `README.md` | yes | See [readme-template.md](references/readme-template.md). |\n| `__init__.py` | sometimes | Present when the sample is imported as a package. |\n| `tests/*.json` | no | Recorded sessions used as eval sets. |\n\nUse absolute imports so the file can be run and imported directly.\n\nDo not set `model=` on `Agent` instances. Samples inherit the\nsystem-configured model, which keeps them working when the default model\nchanges; hardcoding `model=\"gemini-2.5-flash\"` pins the sample to a model that\nwill be retired. Set it only when the user explicitly asks for a specific model.\n\nThen pick one of the two shapes.\n\n### Pattern A — Workflow, for multi-step graphs\n\nUse when the sample needs multiple nodes, routing, or parallel execution.\n\n```python\nfrom google.adk import Agent\nfrom google.adk import Context\nfrom google.adk import Event\nfrom google.adk import Workflow\nfrom google.adk.workflow import JoinNode\nfrom google.adk.workflow import node\n```\n\nImport `Workflow` from `google.adk`, not from a private\n`google.adk.workflow._*` module.\n\n```python\nmy_agent = Agent(name=\"my_agent\", instruction=\"...\")\n\n\n@node()\nasync def my_node(node_input: str) -> str:\n  return \"result\"\n\n\nroot_agent = Workflow(\n    name=\"root_agent\",\n    edges=[(\"START\", my_node)],\n)\n```\n\nA plain function can be used as a node directly in `edges`; reach for the\n`@node(...)` decorator when you need one of its options, such as\n`rerun_on_resume=True` for a node that calls `ctx.run_node`.\n\n### Pattern B — Standalone agent, for single-agent or simple tool use\n\nUse when there is no graph and the agent drives its own loop.\n\n```python\nfrom google.adk import Agent\nfrom google.adk.tools import google_search\n\nroot_agent = Agent(\n    name=\"standalone_assistant\",\n    instruction=\"You are a helpful assistant.\",\n    description=\"An assistant that can help with queries.\",\n    tools=[google_search],\n)\n```\n\n## 3. Write `README.md`\n\nFollow [readme-template.md](references/readme-template.md) — section order,\nprompt formatting, the Mermaid topology rules, and the relative link depth for\n`docs/guides/`.\n\n## Worked examples\n\nRead these two before writing a new Pattern A sample — one dynamic graph, one\nstatic one.\n\n-   `contributing/samples/workflows/dynamic_nodes/agent.py` — a Python node\n    driving a `while` loop with `ctx.run_node`, so the number of agent calls is\n    decided at runtime rather than by the edges.\n\n    ```python\n    @node(rerun_on_resume=True)\n    async def orchestrate(ctx: Context, node_input: str) -> str:\n      yield Event(state={\"topic\": node_input})\n\n      while True:\n        headline = await ctx.run_node(generate_headline)\n        # ...\n    ```\n\n-   `contributing/samples/workflows/fan_out_fan_in/agent.py` — three functions\n    run in parallel from `START`, collected by a `JoinNode`, then aggregated.\n\n    ```python\n    join_node = JoinNode(name=\"join_for_results\")\n\n    root_agent = Workflow(\n        name=\"root_agent\",\n        edges=[(\n            \"START\",\n            (make_uppercase, count_characters, reverse_string),\n            join_node,\n            aggregate,\n        )],\n    )\n    ```\n","frontmatter":{"name":"adk-sample-creator","description":"Creates a new sample agent in the ADK Python repository — the sample directory, its `agent.py`, and its `README.md` — following the conventions the existing samples already use. Use when the user wants to add a sample or example demonstrating a feature or agent pattern (dynamic nodes, fan-out/fan-in, a standalone tool-using agent), asks where a new sample belongs under `contributing/samples/`, or wants an existing sample's README brought up to the standard structure. Don't use for building a real working agent for the user's own project (use `adk-agent-builder`), or for checking whether the Python blocks in a Markdown file run (use `adk-verify-snippets`)."},"isInternal":false,"tokens":1266,"sizeBytes":5311},{"name":"SKILL.md","path":".agents/skills/adk-setup/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-setup/SKILL.md","title":"adk-setup","category":"anthropic-skill","format":"markdown","content":"---\nname: adk-setup\ndescription: >-\n  Sets up a local ADK Python development environment in a git clone of the\n  open-source adk-python repository: a uv virtual environment, all dependency\n  extras, pre-commit hooks, and a first unit-test run. Runs only when\n  explicitly requested, never on its own. Use when asked to set up, bootstrap,\n  or repair a development checkout, install project dependencies, fix a\n  missing or broken .venv, or prepare a machine for contributing a pull\n  request. Don't use for debugging a running agent (use adk-debug), for commit\n  and pull-request mechanics (use adk-git), or for re-running formatters on a\n  checkout that is already set up (pre-commit run --all-files).\ndisable-model-invocation: true\n---\n\n# ADK Python Development Setup\n\nSet up a working `adk-python` checkout: a uv-managed virtual environment with\nevery dependency extra, pre-commit hooks, and a green unit-test run.\n\n## Prerequisites\n\n1. **Python.** ADK supports 3.11 through 3.15 (`requires-python = \">=3.11\"` in\n   `pyproject.toml`). These steps use 3.11, the version the repo's own tooling\n   defaults to.\n\n   ```bash\n   python3 --version\n   ```\n\n2. **uv.** Dependencies are declared in `pyproject.toml`. The `uv sync` step\n   below creates a local `uv.lock`, which this repository ignores. Run that step\n   before `tox`, whose lock runner requires the file.\n\n   ```bash\n   uv --version\n   ```\n\n   Install it if missing:\n\n   ```bash\n   curl -LsSf https://astral.sh/uv/install.sh | sh\n   ```\n\n## Setup\n\nRun these from the repository root.\n\n1. **Create and activate the virtual environment:**\n\n   ```bash\n   uv venv --python \"python3.11\" \".venv\"\n   source .venv/bin/activate\n   ```\n\n2. **Install every dependency extra:**\n\n   ```bash\n   uv sync --all-extras\n   ```\n\n   `--all-extras` is what pulls in the `test` and `dev` extras. Syncing only\n   the default dependencies leaves `pytest` and the linters uninstalled.\n\n3. **Install pre-commit and tox as standalone tools:**\n\n   ```bash\n   uv tool install pre-commit\n   uv tool install tox --with tox-uv\n   ```\n\n   Both are also in the `dev` extra; installing them as uv tools puts them on\n   PATH so the git hook and multi-version test runs work without an activated\n   venv.\n\n4. **Install `addlicense` (optional, requires Go):**\n\n   ```bash\n   go install github.com/google/addlicense@latest\n   ```\n\n   Without it the pre-commit hook prints `Warning: addlicense not installed,\n   skipping` and passes, so a missing Go toolchain does not block setup — CI\n   catches missing license headers instead.\n\n5. **Install the git hooks:**\n\n   ```bash\n   pre-commit install\n   ```\n\n   On each commit the hooks auto-format with `isort`, `pyink`, and `mdformat`,\n   then run `ruff`, `addlicense`, `codespell`, and the repo's own compliance\n   checks.\n\n6. **Verify the environment:**\n\n   ```bash\n   pytest tests/unittests -n auto\n   ```\n\n   A green run here is the success criterion for setup. `-n auto` needs\n   `pytest-xdist`, which arrives with the `test` extra in step 2.\n\n## Key commands\n\n| Task                                 | Command                                            |\n| :----------------------------------- | :------------------------------------------------- |\n| Run unit tests                       | `pytest tests/unittests`                           |\n| Run unit tests in parallel           | `pytest tests/unittests -n auto`                   |\n| Run one test file                    | `pytest tests/unittests/agents/test_base_agent.py` |\n| Run tests on every supported Python  | `tox` (uv downloads any missing interpreters)      |\n| Format and lint everything           | `pre-commit run --all-files`                       |\n| Launch the web UI                    | `adk web {agents_dir}`                             |\n| Run an agent from the CLI            | `adk run {agent_dir}`                              |\n| Build the wheel                      | `uv build`                                         |\n","frontmatter":{"name":"adk-setup","description":"Sets up a local ADK Python development environment in a git clone of the open-source adk-python repository: a uv virtual environment, all dependency extras, pre-commit hooks, and a first unit-test run. Runs only when explicitly requested, never on its own. Use when asked to set up, bootstrap, or repair a development checkout, install project dependencies, fix a missing or broken .venv, or prepare a machine for contributing a pull request. Don't use for debugging a running agent (use adk-debug), for commit and pull-request mechanics (use adk-git), or for re-running formatters on a checkout that is already set up (pre-commit run --all-files).","disable-model-invocation":true,"disableModelInvocation":true},"isInternal":false,"tokens":952,"sizeBytes":3973},{"name":"async.md","path":".agents/skills/adk-style/references/async.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-style/references/async.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Async and Concurrency Style Guide\n\n- **I/O belongs in `async def`**: network calls, file system access, database\n  queries, and subprocess waits all go in async functions. ADK runs everything\n  on one event loop, so a synchronous call inside it stalls every concurrent\n  agent, not just the caller.\n- **Don't block the event loop**: no synchronous HTTP clients, `time.sleep`,\n  or blocking file reads inside async code.\n- **Wrap synchronous I/O in `asyncio.to_thread`** when a library offers no\n  async API (`open()`, `pathlib`, most cloud SDK clients):\n\n```python\nasync def save_data(path: Path, data: bytes) -> None:\n  # Wrap the blocking write so the event loop stays free.\n  await asyncio.to_thread(path.write_bytes, data)\n```\n\nNo hook checks this — a blocking call passes CI and shows up later as\nunexplained latency under concurrency, so it is worth catching in review.\n","isInternal":false,"tokens":206,"sizeBytes":880},{"name":"documentation.md","path":".agents/skills/adk-style/references/documentation.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-style/references/documentation.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Documentation and Comments\n\n## Public API Documentation\n\n- **Classes**: explain the intended usage, with a concise example when the\n  usage is not obvious from the signature. Document every public attribute.\n  For Pydantic models, document fields with an attribute docstring under the\n  field — see the Pydantic reference.\n- **Methods and functions**: document every argument, the return value, and\n  each exception raised.\n- **Docstrings say what the API does.** Design history and rejected\n  alternatives belong in the pull request.\n\n## Internal Implementation Comments\n\n- **Explain why, not what.** The code says what it does; a comment earns its\n  place by saying why it does it that way — the constraint, the bug, or the\n  ordering requirement that is not visible from the code.\n- **No links to RFCs, design docs, issues, or pull requests.** They rot faster\n  than the code, and a reader who cannot open the link is left with nothing.\n  Put the reasoning in the comment itself and the link in the pull request.\n  The same applies to issue numbers, and to warnings, log lines and error\n  messages.\n","isInternal":false,"tokens":246,"sizeBytes":1108},{"name":"file-organization.md","path":".agents/skills/adk-style/references/file-organization.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-style/references/file-organization.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# File Organization\n\n- One class per file in `workflow/`.\n- New modules under `src/google/adk/` are private by default — see\n  the visibility reference for the naming rules and how to expose a\n  public symbol.\n\n## File Headers\n\nEvery module under `src/google/adk/` starts with:\n\n1. The Apache 2.0 license header (added by the `addlicense` hook).\n2. `from __future__ import annotations`.\n3. Imports: standard library, third party, then relative.\n\n`from __future__ import annotations` is checked by\n`scripts/compliance_checks.py`, which exempts `__init__.py`, `version.py`,\n`tests/`, and `contributing/samples/`.\n\n## Where tests go\n\nMirror the source path under `tests/unittests/`:\n\n```text\nsrc/google/adk/tools/environment/_edit_file_tool.py\ntests/unittests/tools/environment/test_edit_file_tool.py\n```\n\nWhen one source file needs several test files, use the source file name —\nwithout the leading underscore or extension — as a shared prefix:\n\n```text\nsrc/google/adk/workflow/_workflow.py\ntests/unittests/workflow/test_workflow.py\ntests/unittests/workflow/test_workflow_hitl.py\ntests/unittests/workflow/test_workflow_nested.py\n```\n","isInternal":false,"tokens":277,"sizeBytes":1137},{"name":"formatting.md","path":".agents/skills/adk-style/references/formatting.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-style/references/formatting.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Fformatting.md -->","isInternal":false,"tokens":692,"sizeBytes":2571},{"name":"imports.md","path":".agents/skills/adk-style/references/imports.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-style/references/imports.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Fimports.md -->","isInternal":false,"tokens":791,"sizeBytes":3151},{"name":"logging.md","path":".agents/skills/adk-style/references/logging.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-style/references/logging.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Flogging.md -->","isInternal":false,"tokens":271,"sizeBytes":1210},{"name":"pydantic.md","path":".agents/skills/adk-style/references/pydantic.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-style/references/pydantic.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Fpydantic.md -->","isInternal":false,"tokens":1283,"sizeBytes":5338},{"name":"testing.md","path":".agents/skills/adk-style/references/testing.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-style/references/testing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Ftesting.md -->","isInternal":false,"tokens":2328,"sizeBytes":9975},{"name":"typing.md","path":".agents/skills/adk-style/references/typing.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-style/references/typing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Ftyping.md -->","isInternal":false,"tokens":2016,"sizeBytes":7861},{"name":"visibility.md","path":".agents/skills/adk-style/references/visibility.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-style/references/visibility.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2Freferences%2Fvisibility.md -->","isInternal":false,"tokens":806,"sizeBytes":3388},{"name":"SKILL.md","path":".agents/skills/adk-style/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-style/SKILL.md","title":"adk-style","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-style%2FSKILL.md -->","frontmatter":{"name":"adk-style","description":"Python style and codebase conventions for ADK (Agent Development Kit): private-by-default file visibility, imports, type hints, Pydantic v2 models, formatting, docstrings, logging, async I/O, file and test layout, and unit test structure. Use when writing or editing ADK source or tests, deciding whether a new file or symbol should be public or private, naming or placing a test file, fixing a formatter, linter, or type-check failure (pyink, isort, ruff, mypy, addlicense, compliance-checks), or asking whether code matches house style. Don't use for reviewing a whole changeset (use adk-review), writing a developer guide or design doc for a code unit (use adk-unit-guide or adk-unit-design), building or configuring agents (use adk-agent-builder), or installing the toolchain (use adk-setup)."},"isInternal":false,"tokens":787,"sizeBytes":3080},{"name":"design-template.md","path":".agents/skills/adk-unit-design/references/design-template.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-unit-design/references/design-template.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-unit-design%2Freferences%2Fdesign-template.md -->","isInternal":false,"tokens":267,"sizeBytes":1315},{"name":"SKILL.md","path":".agents/skills/adk-unit-design/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-unit-design/SKILL.md","title":"adk-unit-design","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-unit-design%2FSKILL.md -->","frontmatter":{"name":"adk-unit-design","description":"Writes an as-built architecture document for one ADK code unit — purpose, execution flow, data flow, cross-class dependencies, extension points, and the parts that must not change — to `docs/design/{topic}/{unit}/index.md`. It describes the code as implemented, not a proposed design, and its reader is a developer about to change or extend that unit. Use when asked to \"write a design doc for {file}\", \"document the architecture of {class}\", \"document the extension points of {unit}\", or after adding a core class, node type, or plugin base. Don't use for documentation aimed at developers who only call the unit from their own application — that is a usage guide with runnable examples under `docs/guides/` (use `adk-unit-guide`). Don't use to answer a framework-wide architecture question (use `adk-architecture`)."},"isInternal":false,"tokens":700,"sizeBytes":3047},{"name":"guide-template.md","path":".agents/skills/adk-unit-guide/references/guide-template.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-unit-guide/references/guide-template.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-unit-guide%2Freferences%2Fguide-template.md -->","isInternal":false,"tokens":401,"sizeBytes":1879},{"name":"SKILL.md","path":".agents/skills/adk-unit-guide/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-unit-guide/SKILL.md","title":"adk-unit-guide","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-unit-guide%2FSKILL.md -->","frontmatter":{"name":"adk-unit-guide","description":"Writes a hands-on developer guide for one ADK code unit — a minimal runnable example, how it works, a configuration-option table, advanced uses, limitations, and links to related samples — to `docs/guides/{topic}/{unit}/index.md`, then lists it in the index at `docs/guides/README.md`. Its reader is a developer calling the unit from their own application, at more depth than the published adk.dev documentation carries. Use when asked to \"write a unit guide for {class}\", \"document how to use {feature}\", \"add a guide for {file}\", or after shipping a user-facing class, node, or plugin. Don't use for internals documentation aimed at someone changing or extending the unit — that is a design document under `docs/design/` (use `adk-unit-design`). Don't use to write a runnable sample under `contributing/samples/` (use `adk-sample-creator`)."},"isInternal":false,"tokens":2377,"sizeBytes":10229},{"name":"SKILL.md","path":".agents/skills/adk-verify-snippets/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/.agents/skills/adk-verify-snippets/SKILL.md","title":"adk-verify-snippets","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=.agents%2Fskills%2Fadk-verify-snippets%2FSKILL.md -->","frontmatter":{"name":"adk-verify-snippets","description":"Checks that every Python code block in a Markdown file actually compiles and runs, by extracting each block to a temporary file, executing it in an isolated subprocess, and writing a pass/fail report with per-snippet coverage. Use when the user asks to verify, test, or validate the code samples in a README, a guide, or a documentation page; wants to know which snippets in a Markdown file are broken or out of date; or asks for a snippet verification report. Don't use for running the project's test suite (run pytest directly), for checking code style or formatting (use `adk-style`), or for authoring a new runnable sample agent (use `adk-sample-creator`)."},"isInternal":false,"tokens":1333,"sizeBytes":5669},{"name":"SKILL.md","path":"contributing/samples/environment_and_skills/e2b_env_skill_toolset/skills/calc-skill/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/e2b_env_skill_toolset/skills/calc-skill/SKILL.md","title":"calc-skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fe2b_env_skill_toolset%2Fskills%2Fcalc-skill%2FSKILL.md -->","frontmatter":{"name":"calc-skill","description":"A math calculation skill that runs Python scripts to perform arithmetic."},"isInternal":false,"tokens":66,"sizeBytes":305},{"name":"SKILL.md","path":"contributing/samples/environment_and_skills/e2b_env_skill_toolset/skills/text-skill/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/e2b_env_skill_toolset/skills/text-skill/SKILL.md","title":"text-skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fe2b_env_skill_toolset%2Fskills%2Ftext-skill%2FSKILL.md -->","frontmatter":{"name":"text-skill","description":"A text processing skill that formats strings using shell scripts."},"isInternal":false,"tokens":52,"sizeBytes":240},{"name":"SKILL.md","path":"contributing/samples/environment_and_skills/local_env_skill_toolset/skills/calc-skill/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/local_env_skill_toolset/skills/calc-skill/SKILL.md","title":"calc-skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Flocal_env_skill_toolset%2Fskills%2Fcalc-skill%2FSKILL.md -->","frontmatter":{"name":"calc-skill","description":"A math calculation skill that runs Python scripts to perform arithmetic."},"isInternal":false,"tokens":66,"sizeBytes":305},{"name":"SKILL.md","path":"contributing/samples/environment_and_skills/local_env_skill_toolset/skills/text-skill/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/local_env_skill_toolset/skills/text-skill/SKILL.md","title":"text-skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Flocal_env_skill_toolset%2Fskills%2Ftext-skill%2FSKILL.md -->","frontmatter":{"name":"text-skill","description":"A text processing skill that formats strings using shell scripts."},"isInternal":false,"tokens":52,"sizeBytes":240},{"name":"weather_info.md","path":"contributing/samples/environment_and_skills/local_environment_skill/skills/weather-skill/references/weather_info.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/local_environment_skill/skills/weather-skill/references/weather_info.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Flocal_environment_skill%2Fskills%2Fweather-skill%2Freferences%2Fweather_info.md -->","isInternal":false,"tokens":79,"sizeBytes":289},{"name":"SKILL.md","path":"contributing/samples/environment_and_skills/local_environment_skill/skills/weather-skill/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/local_environment_skill/skills/weather-skill/SKILL.md","title":"weather-skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Flocal_environment_skill%2Fskills%2Fweather-skill%2FSKILL.md -->","frontmatter":{"name":"weather-skill","description":"A skill that provides weather information based on reference data."},"isInternal":false,"tokens":92,"sizeBytes":382},{"name":"weather_info.md","path":"contributing/samples/environment_and_skills/skills_agent/skills/weather-skill/references/weather_info.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/skills_agent/skills/weather-skill/references/weather_info.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills_agent%2Fskills%2Fweather-skill%2Freferences%2Fweather_info.md -->","isInternal":false,"tokens":42,"sizeBytes":157},{"name":"SKILL.md","path":"contributing/samples/environment_and_skills/skills_agent/skills/weather-skill/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/skills_agent/skills/weather-skill/SKILL.md","title":"weather-skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills_agent%2Fskills%2Fweather-skill%2FSKILL.md -->","frontmatter":{"name":"weather-skill","description":"A skill that provides weather information based on reference data.","metadata":{"adk_additional_tools":["get_wind_speed"]}},"isInternal":false,"tokens":106,"sizeBytes":437},{"name":"SKILL.md","path":"contributing/samples/environment_and_skills/skills_inject_state/skills/code-review-skill/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/skills_inject_state/skills/code-review-skill/SKILL.md","title":"code-review-skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills_inject_state%2Fskills%2Fcode-review-skill%2FSKILL.md -->","frontmatter":{"name":"code-review-skill","description":"Reviews code with feedback tailored to the developer's profile in session state.","metadata":{"adk_inject_state":true}},"isInternal":false,"tokens":200,"sizeBytes":912},{"name":"README.md","path":"contributing/samples/environment_and_skills/skills/README.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/skills/README.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2FREADME.md -->","isInternal":false,"tokens":810,"sizeBytes":3529},{"name":"weather_info.md","path":"contributing/samples/environment_and_skills/skills/skills/weather-skill/references/weather_info.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/skills/skills/weather-skill/references/weather_info.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Fskills%2Fweather-skill%2Freferences%2Fweather_info.md -->","isInternal":false,"tokens":42,"sizeBytes":157},{"name":"SKILL.md","path":"contributing/samples/environment_and_skills/skills/skills/weather-skill/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/skills/skills/weather-skill/SKILL.md","title":"weather-skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Fskills%2Fweather-skill%2FSKILL.md -->","frontmatter":{"name":"weather-skill","description":"A skill that provides weather information based on reference data and scripts.","metadata":{"adk_additional_tools":["get_wind_speed"]}},"isInternal":false,"tokens":109,"sizeBytes":462},{"name":"current_humidity.json","path":"contributing/samples/environment_and_skills/skills/tests/current_humidity.json","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/skills/tests/current_humidity.json","title":"Tests Skill","category":"anthropic-skill","format":"json","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Ftests%2Fcurrent_humidity.json -->","frontmatter":{"events":[{"author":"user","content":{"parts":[{"text":"Can you fetch the current humidity for Mountain View?"}],"role":"user"},"id":"e-1","invocationId":"i-1","nodeInfo":{"path":""}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{},"id":"fc-1","name":"list_skills"}}],"role":"model"},"finishReason":"STOP","id":"e-2","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-1","name":"list_skills","response":{"result":"<available_skills>\n<skill>\n<name>\nsupport-hours-skill\n</name>\n<description>\nA skill to check customer support hours for a given location.\n</description>\n</skill>\n<skill>\n<name>\nweather-skill\n</name>\n<description>\nA skill that provides weather information based on reference data and scripts.\n</description>\n</skill>\n</available_skills>"}}}],"role":"user"},"id":"e-3","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{"skill_name":"weather-skill"},"id":"fc-2","name":"load_skill"}}],"role":"model"},"finishReason":"STOP","id":"e-4","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"actions":{"stateDelta":{"_adk_activated_skill_skills_agent":["weather-skill"]}},"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-2","name":"load_skill","response":{"frontmatter":{"allowed_tools":null,"compatibility":null,"description":"A skill that provides weather information based on reference data and scripts.","license":null,"metadata":{"adk_additional_tools":["get_wind_speed"]},"name":"weather-skill"},"instructions":"Step 1: Check 'references/weather_info.md' for the current weather.\nStep 2: If humidity is requested, run 'scripts/get_humidity.py' with the `location` argument.\nStep 3: If wind speed is requested, use the `get_wind_speed` tool.\nStep 4: Provide the complete weather update to the user.","skill_name":"weather-skill"}}}],"role":"user"},"id":"e-5","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{"args":{"location":"Mountain View"},"file_path":"scripts/get_humidity.py","skill_name":"weather-skill"},"id":"fc-3","name":"run_skill_script"}}],"role":"model"},"finishReason":"STOP","id":"e-6","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-3","name":"run_skill_script","response":{"file_path":"scripts/get_humidity.py","skill_name":"weather-skill","status":"success","stderr":"","stdout":"Fetching live humidity for Mountain View...\n45% (Simulated)\n"}}}],"role":"user"},"id":"e-7","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"text":"The current humidity in Mountain View is 45% (Simulated)."}],"role":"model"},"finishReason":"STOP","id":"e-8","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}}]},"isInternal":false,"tokens":1287,"sizeBytes":5246},{"name":"current_weather.json","path":"contributing/samples/environment_and_skills/skills/tests/current_weather.json","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/skills/tests/current_weather.json","title":"Tests Skill","category":"anthropic-skill","format":"json","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Ftests%2Fcurrent_weather.json -->","frontmatter":{"events":[{"author":"user","content":{"parts":[{"text":"What is the current weather in SF?"}],"role":"user"},"id":"e-1","invocationId":"i-1","nodeInfo":{"path":""}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{},"id":"fc-1","name":"list_skills"}}],"role":"model"},"finishReason":"STOP","id":"e-2","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-1","name":"list_skills","response":{"result":"<available_skills>\n<skill>\n<name>\nsupport-hours-skill\n</name>\n<description>\nA skill to check customer support hours for a given location.\n</description>\n</skill>\n<skill>\n<name>\nweather-skill\n</name>\n<description>\nA skill that provides weather information based on reference data and scripts.\n</description>\n</skill>\n</available_skills>"}}}],"role":"user"},"id":"e-3","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{"skill_name":"weather-skill"},"id":"fc-2","name":"load_skill"}}],"role":"model"},"finishReason":"STOP","id":"e-4","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"actions":{"stateDelta":{"_adk_activated_skill_skills_agent":["weather-skill"]}},"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-2","name":"load_skill","response":{"frontmatter":{"allowed_tools":null,"compatibility":null,"description":"A skill that provides weather information based on reference data and scripts.","license":null,"metadata":{"adk_additional_tools":["get_wind_speed"]},"name":"weather-skill"},"instructions":"Step 1: Check 'references/weather_info.md' for the current weather.\nStep 2: If humidity is requested, run 'scripts/get_humidity.py' with the `location` argument.\nStep 3: If wind speed is requested, use the `get_wind_speed` tool.\nStep 4: Provide the complete weather update to the user.","skill_name":"weather-skill"}}}],"role":"user"},"id":"e-5","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{"file_path":"references/weather_info.md","skill_name":"weather-skill"},"id":"fc-3","name":"load_skill_resource"}}],"role":"model"},"finishReason":"STOP","id":"e-6","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-3","name":"load_skill_resource","response":{"content":"# Weather Information\n\n- **Location:** San Francisco, CA\n- **Condition:** Sunny ☀️\n- **Temperature:** 72°F (22°C)\n- **Forecast:** Clear skies all day.\n","file_path":"references/weather_info.md","skill_name":"weather-skill"}}}],"role":"user"},"id":"e-7","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"text":"The current weather in San Francisco, CA is Sunny with a temperature of 72°F (22°C). The forecast is clear skies all day."}],"role":"model"},"finishReason":"STOP","id":"e-8","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}}]},"isInternal":false,"tokens":1320,"sizeBytes":5271},{"name":"support_hours.json","path":"contributing/samples/environment_and_skills/skills/tests/support_hours.json","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/skills/tests/support_hours.json","title":"Tests Skill","category":"anthropic-skill","format":"json","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Ftests%2Fsupport_hours.json -->","frontmatter":{"events":[{"author":"user","content":{"parts":[{"text":"What are the support hours for Tokyo?"}],"role":"user"},"id":"e-1","invocationId":"i-1","nodeInfo":{"path":""}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{},"id":"fc-1","name":"list_skills"}}],"role":"model"},"finishReason":"STOP","id":"e-2","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-1","name":"list_skills","response":{"result":"<available_skills>\n<skill>\n<name>\nsupport-hours-skill\n</name>\n<description>\nA skill to check customer support hours for a given location.\n</description>\n</skill>\n<skill>\n<name>\nweather-skill\n</name>\n<description>\nA skill that provides weather information based on reference data and scripts.\n</description>\n</skill>\n</available_skills>"}}}],"role":"user"},"id":"e-3","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{"skill_name":"support-hours-skill"},"id":"fc-2","name":"load_skill"}}],"role":"model"},"finishReason":"STOP","id":"e-4","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"actions":{"stateDelta":{"_adk_activated_skill_skills_agent":["support-hours-skill"]}},"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-2","name":"load_skill","response":{"frontmatter":{"allowed_tools":null,"compatibility":null,"description":"A skill to check customer support hours for a given location.","license":null,"metadata":{"adk_additional_tools":["get_timezone"]},"name":"support-hours-skill"},"instructions":"Step 1: Look up the timezone for the user's location using 'get_timezone'. Step 2: Read 'references/support_policy.txt' to understand support hours policy. Step 3: Explain the support hours relative to the location's timezone.","skill_name":"support-hours-skill"}}}],"role":"user"},"id":"e-5","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{"location":"Tokyo"},"id":"fc-3","name":"get_timezone"}}],"role":"model"},"finishReason":"STOP","id":"e-6","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-3","name":"get_timezone","response":{"result":"The timezone for Tokyo is UTC-08:00."}}}],"role":"user"},"id":"e-7","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{"file_path":"references/support_policy.txt","skill_name":"support-hours-skill"},"id":"fc-4","name":"load_skill_resource"}}],"role":"model"},"finishReason":"STOP","id":"e-8","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-4","name":"load_skill_resource","response":{"content":"Customer support is available Monday through Friday, from 9:00 AM to 5:00 PM local time.","file_path":"references/support_policy.txt","skill_name":"support-hours-skill"}}}],"role":"user"},"id":"e-9","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"text":"Customer support in Tokyo is available Monday through Friday, from 9:00 AM to 5:00 PM local time (UTC-08:00)."}],"role":"model"},"finishReason":"STOP","id":"e-10","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}}]},"isInternal":false,"tokens":1513,"sizeBytes":6115},{"name":"wind_speed.json","path":"contributing/samples/environment_and_skills/skills/tests/wind_speed.json","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/environment_and_skills/skills/tests/wind_speed.json","title":"Tests Skill","category":"anthropic-skill","format":"json","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fenvironment_and_skills%2Fskills%2Ftests%2Fwind_speed.json -->","frontmatter":{"events":[{"author":"user","content":{"parts":[{"text":"What is the wind speed in Seattle?"}],"role":"user"},"id":"e-1","invocationId":"i-1","nodeInfo":{"path":""}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{},"id":"fc-1","name":"list_skills"}}],"role":"model"},"finishReason":"STOP","id":"e-2","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-1","name":"list_skills","response":{"result":"<available_skills>\n<skill>\n<name>\nsupport-hours-skill\n</name>\n<description>\nA skill to check customer support hours for a given location.\n</description>\n</skill>\n<skill>\n<name>\nweather-skill\n</name>\n<description>\nA skill that provides weather information based on reference data and scripts.\n</description>\n</skill>\n</available_skills>"}}}],"role":"user"},"id":"e-3","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{"skill_name":"weather-skill"},"id":"fc-2","name":"load_skill"}}],"role":"model"},"finishReason":"STOP","id":"e-4","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"actions":{"stateDelta":{"_adk_activated_skill_skills_agent":["weather-skill"]}},"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-2","name":"load_skill","response":{"frontmatter":{"allowed_tools":null,"compatibility":null,"description":"A skill that provides weather information based on reference data and scripts.","license":null,"metadata":{"adk_additional_tools":["get_wind_speed"]},"name":"weather-skill"},"instructions":"Step 1: Check 'references/weather_info.md' for the current weather.\nStep 2: If humidity is requested, run 'scripts/get_humidity.py' with the `location` argument.\nStep 3: If wind speed is requested, use the `get_wind_speed` tool.\nStep 4: Provide the complete weather update to the user.","skill_name":"weather-skill"}}}],"role":"user"},"id":"e-5","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionCall":{"args":{"location":"Seattle"},"id":"fc-3","name":"get_wind_speed"}}],"role":"model"},"finishReason":"STOP","id":"e-6","invocationId":"i-1","longRunningToolIds":[],"nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"functionResponse":{"id":"fc-3","name":"get_wind_speed","response":{"result":"The wind speed in Seattle is 10 mph."}}}],"role":"user"},"id":"e-7","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}},{"author":"skills_agent","content":{"parts":[{"text":"The wind speed in Seattle is 10 mph."}],"role":"model"},"finishReason":"STOP","id":"e-8","invocationId":"i-1","nodeInfo":{"path":"skills_agent@1"}}]},"isInternal":false,"tokens":1210,"sizeBytes":4851},{"name":"index.md","path":"docs/guides/live/tools/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/live/tools/index.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Flive%2Ftools%2Findex.md -->","isInternal":false,"tokens":1198,"sizeBytes":6158},{"name":"index.md","path":"docs/guides/plugins/reflect_retry_model_plugin/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/plugins/reflect_retry_model_plugin/index.md","title":"Reflect_retry_model_plugin Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fplugins%2Freflect_retry_model_plugin%2Findex.md -->","isInternal":false,"tokens":1545,"sizeBytes":6893},{"name":"index.md","path":"docs/guides/plugins/reflect_retry_tool_plugin/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/plugins/reflect_retry_tool_plugin/index.md","title":"Reflect_retry_tool_plugin Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fplugins%2Freflect_retry_tool_plugin%2Findex.md -->","isInternal":false,"tokens":1800,"sizeBytes":8228},{"name":"index.md","path":"docs/guides/plugins/tool_call_integrity_plugin/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/plugins/tool_call_integrity_plugin/index.md","title":"Tool_call_integrity_plugin Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fplugins%2Ftool_call_integrity_plugin%2Findex.md -->","isInternal":false,"tokens":3149,"sizeBytes":14879},{"name":"index.md","path":"docs/guides/skills/skill_registry/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/skills/skill_registry/index.md","title":"Skill_registry Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fskills%2Fskill_registry%2Findex.md -->","isInternal":false,"tokens":2434,"sizeBytes":10797},{"name":"index.md","path":"docs/guides/skills/skill/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/skills/skill/index.md","title":"Skill Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fskills%2Fskill%2Findex.md -->","isInternal":false,"tokens":5452,"sizeBytes":23209},{"name":"index.md","path":"docs/guides/tools/agent_tool/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/agent_tool/index.md","title":"Agent_tool Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fagent_tool%2Findex.md -->","isInternal":false,"tokens":2872,"sizeBytes":12753},{"name":"index.md","path":"docs/guides/tools/base_tool/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/base_tool/index.md","title":"Base_tool Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fbase_tool%2Findex.md -->","isInternal":false,"tokens":2688,"sizeBytes":11706},{"name":"index.md","path":"docs/guides/tools/base_toolset/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/base_toolset/index.md","title":"Base_toolset Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fbase_toolset%2Findex.md -->","isInternal":false,"tokens":3116,"sizeBytes":13412},{"name":"index.md","path":"docs/guides/tools/function_tool/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/function_tool/index.md","title":"Function_tool Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Ffunction_tool%2Findex.md -->","isInternal":false,"tokens":2622,"sizeBytes":12386},{"name":"index.md","path":"docs/guides/tools/long_running_tool/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/long_running_tool/index.md","title":"Long_running_tool Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Flong_running_tool%2Findex.md -->","isInternal":false,"tokens":2694,"sizeBytes":11895},{"name":"index.md","path":"docs/guides/tools/mcp_tool/agent_to_mcp/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/mcp_tool/agent_to_mcp/index.md","title":"Agent_to_mcp Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmcp_tool%2Fagent_to_mcp%2Findex.md -->","isInternal":false,"tokens":1559,"sizeBytes":6487},{"name":"index.md","path":"docs/guides/tools/mcp_tool/mcp_toolset/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/mcp_tool/mcp_toolset/index.md","title":"Mcp_toolset Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmcp_tool%2Fmcp_toolset%2Findex.md -->","isInternal":false,"tokens":2944,"sizeBytes":12822},{"name":"index.md","path":"docs/guides/tools/model_consult/force_escalation_plugin/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/model_consult/force_escalation_plugin/index.md","title":"Force_escalation_plugin Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmodel_consult%2Fforce_escalation_plugin%2Findex.md -->","isInternal":false,"tokens":3415,"sizeBytes":16836},{"name":"index.md","path":"docs/guides/tools/model_consult/model_consult_tool/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/model_consult/model_consult_tool/index.md","title":"Model_consult_tool Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmodel_consult%2Fmodel_consult_tool%2Findex.md -->","isInternal":false,"tokens":3472,"sizeBytes":18130},{"name":"index.md","path":"docs/guides/tools/model_consult/observability_plugin/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/model_consult/observability_plugin/index.md","title":"Observability_plugin Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fmodel_consult%2Fobservability_plugin%2Findex.md -->","isInternal":false,"tokens":5056,"sizeBytes":23173},{"name":"index.md","path":"docs/guides/tools/node_tool/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/node_tool/index.md","title":"Node_tool Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fnode_tool%2Findex.md -->","isInternal":false,"tokens":1911,"sizeBytes":9394},{"name":"index.md","path":"docs/guides/tools/skill_toolset/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/skill_toolset/index.md","title":"Skill_toolset Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Fskill_toolset%2Findex.md -->","isInternal":false,"tokens":3161,"sizeBytes":13776},{"name":"index.md","path":"docs/guides/tools/tool_args_matcher/tool_args_matcher/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/tool_args_matcher/tool_args_matcher/index.md","title":"Tool_args_matcher Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Ftool_args_matcher%2Ftool_args_matcher%2Findex.md -->","isInternal":false,"tokens":2232,"sizeBytes":9381},{"name":"index.md","path":"docs/guides/tools/tool_confirmation/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/tool_confirmation/index.md","title":"Tool_confirmation Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Ftool_confirmation%2Findex.md -->","isInternal":false,"tokens":2909,"sizeBytes":12934},{"name":"index.md","path":"docs/guides/tools/tool_context/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/tools/tool_context/index.md","title":"Tool_context Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Ftools%2Ftool_context%2Findex.md -->","isInternal":false,"tokens":2990,"sizeBytes":12905},{"name":"ai_agg.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_agg.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_agg.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_agg.md -->","isInternal":false,"tokens":858,"sizeBytes":3621},{"name":"ai_causal_effect.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_causal_effect.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_causal_effect.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_causal_effect.md -->","isInternal":false,"tokens":1991,"sizeBytes":11447},{"name":"ai_classify.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_classify.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_classify.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_classify.md -->","isInternal":false,"tokens":817,"sizeBytes":3800},{"name":"ai_detect_anomalies.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_detect_anomalies.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_detect_anomalies.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_detect_anomalies.md -->","isInternal":false,"tokens":1077,"sizeBytes":4657},{"name":"ai_evaluate.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_evaluate.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_evaluate.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_evaluate.md -->","isInternal":false,"tokens":596,"sizeBytes":2228},{"name":"ai_forecast.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_forecast.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_forecast.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_forecast.md -->","isInternal":false,"tokens":1221,"sizeBytes":6671},{"name":"ai_generate_embedding.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate_embedding.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate_embedding.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_generate_embedding.md -->","isInternal":false,"tokens":429,"sizeBytes":1686},{"name":"ai_generate_table.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate_table.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate_table.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_generate_table.md -->","isInternal":false,"tokens":786,"sizeBytes":3017},{"name":"ai_generate.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_generate.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_generate.md -->","isInternal":false,"tokens":882,"sizeBytes":4040},{"name":"ai_if.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_if.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_if.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_if.md -->","isInternal":false,"tokens":434,"sizeBytes":2051},{"name":"ai_key_drivers.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_key_drivers.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_key_drivers.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_key_drivers.md -->","isInternal":false,"tokens":1471,"sizeBytes":7300},{"name":"ai_score.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_score.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_score.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_score.md -->","isInternal":false,"tokens":420,"sizeBytes":1922},{"name":"ai_search.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_search.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_search.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_search.md -->","isInternal":false,"tokens":833,"sizeBytes":3753},{"name":"ai_similarity.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_similarity.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ai_similarity.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fai_similarity.md -->","isInternal":false,"tokens":639,"sizeBytes":2812},{"name":"ml_contribution_analysis.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_contribution_analysis.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_contribution_analysis.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_contribution_analysis.md -->","isInternal":false,"tokens":1212,"sizeBytes":4944},{"name":"ml_correlation.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_correlation.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_correlation.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_correlation.md -->","isInternal":false,"tokens":2012,"sizeBytes":9115},{"name":"ml_detect_change_points.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_detect_change_points.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_detect_change_points.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_detect_change_points.md -->","isInternal":false,"tokens":1145,"sizeBytes":5650},{"name":"ml_seasonality.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_seasonality.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_seasonality.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_seasonality.md -->","isInternal":false,"tokens":1332,"sizeBytes":6783},{"name":"ml_trend.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_trend.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/ml_trend.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fml_trend.md -->","isInternal":false,"tokens":914,"sizeBytes":4161},{"name":"remote_models.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/remote_models.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/remote_models.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fremote_models.md -->","isInternal":false,"tokens":204,"sizeBytes":872},{"name":"vector_search.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/vector_search.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/references/vector_search.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2Freferences%2Fvector_search.md -->","isInternal":false,"tokens":981,"sizeBytes":4147},{"name":"SKILL.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-ai-ml/SKILL.md","title":"bigquery-ai-ml","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-ai-ml%2FSKILL.md -->","frontmatter":{"name":"bigquery-ai-ml","license":"Apache-2.0","metadata":{"author":"google-adk","version":"1.0"},"description":"Leverages BigQuery's built-in machine learning and GenAI capabilities for advanced data analytics. Use when you need to write SQL queries that perform time-series forecasting, predict values, detect outliers or anomalies, find key drivers, perform semantic search or vector search, classify text, calculate similarity, summarize content, translate language, evaluate models, filter by semantic conditions, measure the causal effect of an intervention, compute correlations between columns, detect change points or structural breaks, extract trend or seasonality components, or leverage generative AI capabilities in BigQuery. Do not use for general BigQuery dataset, table, or job management requests."},"isInternal":false,"tokens":924,"sizeBytes":3783},{"name":"best_practices.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/best_practices.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/best_practices.md","title":"Graph_schema Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fgraph_schema%2Fbest_practices.md -->","isInternal":false,"tokens":1399,"sizeBytes":6543},{"name":"ddl_reference.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/ddl_reference.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/ddl_reference.md","title":"Graph_schema Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fgraph_schema%2Fddl_reference.md -->","isInternal":false,"tokens":1708,"sizeBytes":7664},{"name":"feature_parity.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/feature_parity.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/feature_parity.md","title":"Graph_schema Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fgraph_schema%2Ffeature_parity.md -->","isInternal":false,"tokens":1090,"sizeBytes":4903},{"name":"graph_schema_ddl_advisor.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/graph_schema_ddl_advisor.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-graph/references/graph_schema/graph_schema_ddl_advisor.md","title":"bq-graph-schema","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fgraph_schema%2Fgraph_schema_ddl_advisor.md -->","frontmatter":{"name":"bq-graph-schema","description":"Assists in defining, correcting, and optimizing BigQuery Property Graph and Semantic Graph schemas (DDL). Guides users in schema best practices (scoping properties, PK/FK constraints, safe aliases) and semantic extensions (measures, options).\n"},"isInternal":false,"tokens":981,"sizeBytes":4203},{"name":"semantic_queries.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-graph/references/semantic_queries.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-graph/references/semantic_queries.md","title":"semantic-graph-guidelines","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2Freferences%2Fsemantic_queries.md -->","frontmatter":{"name":"semantic-graph-guidelines","description":"Guidelines for generating SQL queries against a semantic graph. Use for tabular analysis, reporting, or BI metrics on graph data."},"isInternal":false,"tokens":722,"sizeBytes":3143},{"name":"SKILL.md","path":"src/google/adk/integrations/bigquery/skills/bigquery-graph/SKILL.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/integrations/bigquery/skills/bigquery-graph/SKILL.md","title":"bigquery-graph","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fintegrations%2Fbigquery%2Fskills%2Fbigquery-graph%2FSKILL.md -->","frontmatter":{"name":"bigquery-graph","license":"Apache-2.0","metadata":{"author":"google-adk","version":"1.0"},"description":"Skill for Graph Query Language (GQL) or SQL/PGQ queries against a property graph.\nIncludes path finding, multi-hop traversal, topological connection, shortest path,\nnode reachability, edge connectivity, and semantic graph queries.\n"},"isInternal":false,"tokens":4696,"sizeBytes":19395},{"name":"README.md","path":"src/google/adk/skills/README.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/src/google/adk/skills/README.md","title":"Skills Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=src%2Fgoogle%2Fadk%2Fskills%2FREADME.md -->","isInternal":false,"tokens":69,"sizeBytes":334},{"name":"index.md","path":"docs/guides/agents/base_agent/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/agents/base_agent/index.md","title":"Subagent: index","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fbase_agent%2Findex.md -->","isInternal":false,"tokens":2075,"sizeBytes":10053},{"name":"index.md","path":"docs/guides/agents/config/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/agents/config/index.md","title":"Subagent: index","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fconfig%2Findex.md -->","isInternal":false,"tokens":2016,"sizeBytes":9041},{"name":"index.md","path":"docs/guides/agents/context/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/agents/context/index.md","title":"Subagent: index","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fcontext%2Findex.md -->","isInternal":false,"tokens":3537,"sizeBytes":16831},{"name":"index.md","path":"docs/guides/agents/invocation_context/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/agents/invocation_context/index.md","title":"Subagent: index","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Finvocation_context%2Findex.md -->","isInternal":false,"tokens":3915,"sizeBytes":19145},{"name":"index.md","path":"docs/guides/agents/llm_agent/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/agents/llm_agent/index.md","title":"Subagent: index","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fllm_agent%2Findex.md -->","isInternal":false,"tokens":3514,"sizeBytes":16884},{"name":"single_turn.md","path":"docs/guides/agents/llm_agent/single_turn.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/agents/llm_agent/single_turn.md","title":"Subagent: single_turn","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fllm_agent%2Fsingle_turn.md -->","isInternal":false,"tokens":1596,"sizeBytes":7571},{"name":"task.md","path":"docs/guides/agents/llm_agent/task.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/agents/llm_agent/task.md","title":"Subagent: task","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fllm_agent%2Ftask.md -->","isInternal":false,"tokens":1231,"sizeBytes":5689},{"name":"index.md","path":"docs/guides/agents/managed_agent/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/agents/managed_agent/index.md","title":"Subagent: index","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fmanaged_agent%2Findex.md -->","isInternal":false,"tokens":2144,"sizeBytes":9804},{"name":"task.md","path":"docs/guides/agents/remote_a2a_agent/task.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/agents/remote_a2a_agent/task.md","title":"Subagent: task","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Fremote_a2a_agent%2Ftask.md -->","isInternal":false,"tokens":2133,"sizeBytes":10402},{"name":"index.md","path":"docs/guides/agents/run_config/index.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/docs/guides/agents/run_config/index.md","title":"Subagent: index","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=docs%2Fguides%2Fagents%2Frun_config%2Findex.md -->","isInternal":false,"tokens":5405,"sizeBytes":24772},{"name":"README.md","path":"contributing/samples/plugins/plugin_basic/README.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/plugins/plugin_basic/README.md","title":"plugin_basic Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fplugins%2Fplugin_basic%2FREADME.md -->","isInternal":false,"tokens":496,"sizeBytes":2200},{"name":"README.md","path":"contributing/samples/plugins/plugin_reflect_tool_retry/README.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/plugins/plugin_reflect_tool_retry/README.md","title":"plugin_reflect_tool_retry Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Fplugins%2Fplugin_reflect_tool_retry%2FREADME.md -->","isInternal":false,"tokens":613,"sizeBytes":2537},{"name":"README.md","path":"contributing/samples/tools/built_in_multi_tools/README.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/tools/built_in_multi_tools/README.md","title":"built_in_multi_tools Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Fbuilt_in_multi_tools%2FREADME.md -->","isInternal":false,"tokens":179,"sizeBytes":703},{"name":"README.md","path":"contributing/samples/tools/function_tools/README.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/tools/function_tools/README.md","title":"function_tools Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Ffunction_tools%2FREADME.md -->","isInternal":false,"tokens":642,"sizeBytes":2828},{"name":"README.md","path":"contributing/samples/tools/long_running_functions/README.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/tools/long_running_functions/README.md","title":"long_running_functions Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Flong_running_functions%2FREADME.md -->","isInternal":false,"tokens":498,"sizeBytes":2160},{"name":"README.md","path":"contributing/samples/tools/model_consult/README.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/tools/model_consult/README.md","title":"model_consult Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Fmodel_consult%2FREADME.md -->","isInternal":false,"tokens":1609,"sizeBytes":6900},{"name":"README.md","path":"contributing/samples/tools/output_schema_with_tools/README.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/tools/output_schema_with_tools/README.md","title":"output_schema_with_tools Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Foutput_schema_with_tools%2FREADME.md -->","isInternal":false,"tokens":328,"sizeBytes":1490},{"name":"README.md","path":"contributing/samples/tools/parallel_functions/README.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/tools/parallel_functions/README.md","title":"parallel_functions Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Fparallel_functions%2FREADME.md -->","isInternal":false,"tokens":892,"sizeBytes":4002},{"name":"README.md","path":"contributing/samples/tools/pydantic_argument/README.md","rawUrl":"https://raw.githubusercontent.com/google/adk-python/HEAD/contributing/samples/tools/pydantic_argument/README.md","title":"pydantic_argument Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/google/adk-python?path=contributing%2Fsamples%2Ftools%2Fpydantic_argument%2FREADME.md -->","isInternal":false,"tokens":1055,"sizeBytes":4900}],"systemPromptSnippet":"<yakaai_skills repo=\"google/adk-python\">\n<!-- File: AGENTS.md (Tokens: ~317 | Category: root-instruction) -->\n## Project Overview\n\nThe Agent Development Kit (ADK) is an open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents.\n\n### Key Components\n\n- **Agent**: Blueprint defining identity, instructions, and tools.\n- **Runner**: Stateless execution engine that orchestrates agent execution.\n- **Tool**: Functions/capabilities agents can call.\n- **Session**: Conversation state management.\n- **Memory**: Long-term recall across sessions.\n- **Workflow** (ADK 2.0): Graph-based orchestration of complex, multi-step agent interactions.\n- **BaseNode** (ADK 2.0): Contract for all nodes, supporting output streaming and human-in-the-loop steps.\n- **Context** (ADK 2.0): Holds execution state and telemetry context mapped 1:1 to nodes.\n\nFor details on how the Runner works and the invocation lifecycle, please refer to the `adk-architecture` skill and the referenced documentation therein.\n\n## ADK Knowledge, Architecture, and Style\n\nSkills related to ADK development are in `.agents/skills/`.\n\n## Project Architecture\n\nFor detailed architecture patterns, component descriptions, and core interfaces, please refer to the **`adk-architecture`** skill at `.agents/skills/adk-architecture/SKILL.md`.\n\n## Development Setup\n\nThe project uses `uv` for package management and Python 3.11+. Please refer to\nthe **`adk-setup`** skill at `.agents/skills/adk-setup/SKILL.md` for detailed\ninstructions.\n\n<!-- File: llms-full.txt (Tokens: ~141 | Category: root-instruction) -->\n# Agent Development Kit (ADK) - New llms-full.txt location\n\n> The machine-readable llms.txt documentation for Agent Development Kit (ADK) is\n> no longer hosted statically in this repository. It is now automatically\n> generated and hosted on the ADK documentation site. Please use the links below\n> to access the source of truth.\n\n- [llms.txt](https://adk.dev/llms.txt)\n- [llms-full.txt](https://adk.dev/llms-full.txt)\n- [ADK Documentation](https://adk.dev/)\n- [ADK - Coding with AI](https://adk.dev/tutorials/coding-with-ai/)\n\n<!-- File: llms.txt (Tokens: ~140 | Category: root-instruction) -->\n# Agent Development Kit (ADK) - New llms.txt location\n\n> The machine-readable llms.txt documentation for Agent Development Kit (ADK) is\n> no longer hosted statically in this repository. It is now automatically\n> generated and hosted on the ADK documentation site. Please use the links below\n> to access the source of truth.\n\n- [llms.txt](https://adk.dev/llms.txt)\n- [llms-full.txt](https://adk.dev/llms-full.txt)\n- [ADK Documentation](https://adk.dev/)\n- [ADK - Coding with AI](https://adk.dev/tutorials/coding-with-ai/)\n\n<!-- File: .agents/skills/adk-agent-builder/references/advanced-patterns.md (Tokens: ~2265 | Category: anthropic-skill) -->\n# Advanced Workflow Patterns\n\nNested workflows, retries, timeouts, custom node classes, and the graph\nvalidation rules that reject a malformed graph at construction time.\n\n```python\nfrom google.adk import Context, Event, Workflow\nfrom google.adk.workflow import BaseNode, Edge, FunctionNode, RetryConfig, START\n```\n\n## Nested workflows\n\nA `Workflow` is both an agent and a node, so one can sit inside another. The\ninner workflow takes the predecessor's output as its `START` input, and its\nterminal output flows on to the next node outside.\n\n```python\ninner = Workflow(name='inner_pipeline', edges=[('START', step_a, step_b)])\n\nouter = Workflow(\n    name='outer_pipeline',\n    edges=[('START', pre_process, inner, post_process)],\n)\n```\n\n## Retries\n\nEvery `RetryConfig` field defaults to `None`, which means \"use the built-in\nfallback\" rather than \"disabled\":\n\n| Field | Fallback | Meaning |\n|---|---|---|\n| `max_attempts` | 5 | Total attempts including the first; 0 or 1 disables retrying |\n| `initial_delay` | 1.0 | Seconds before the first retry |\n| `max_delay` | 60.0 | Ceiling on the computed delay |\n| `backoff_factor` | 2.0 | Multiplier applied per attempt |\n| `jitter` | 1.0 | Randomness factor; 0.0 removes it |\n| `exceptions` | all | Exception classes or class-name strings to retry on |\n\n```python\napi_node = FunctionNode(\n    func=flaky_api_call,\n    name='api_call',\n    retry_config=RetryConfig(max_attempts=5, exceptions=[TimeoutError]),\n)\n```\n\nDelay for attempt *n* is\n`min(initial_delay * backoff_factor ** n, max_delay) * (1 + random(0, jitter))`.\n\nRead the current try from the context — it is 1 on the first attempt:\n\n```python\ndef my_node(ctx: Context, node_input: str) -> str:\n  if ctx.attempt_count > 1:\n    logger.warning('retry %d', ctx.attempt_count)\n  return 'result'\n```\n\n## Timeouts\n\n`timeout` is a per-node wall-clock limit in seconds. Exceeding it raises\n`NodeTimeoutError` (importable from `google.adk.workflow`), which the retry\nmachinery treats like any other exception.\n\n## Execution cancellation\n\nPass an `asyncio.Event` as `abort_signal` to `runner.run_async` to halt an\nin-flight workflow or agent cleanly. Setting the signal cancels active tasks,\nemits a terminal `Event(error_code='INVOCATION_ABORTED')`, seals any dangling\n`FunctionCall` events with a synthetic `FunctionResponse`, and still runs\n`after_run` plugin callbacks.\n\n```python\nimport asyncio\n\nabort_signal = asyncio.Event()\n\nasync for event in runner.run_async(\n    user_id='u1',\n    session_id='s1',\n    new_message=user_content,\n    abort_signal=abort_signal,\n):\n  if should_stop(event):\n    abort_signal.set()\n```\n\nInside a node or tool, `ctx.is_aborted` reflects whether the signal has been\nset.\n\n## Custom node classes\n\n`BaseNode` is a Pydantic model. Declare fields as fields, and override\n`_run_impl` — **not** `run`, which is `@final` and does the normalization of\nyielded values into events.\n\n```python\nfrom typing import Any, AsyncGenerator\n\nfrom typing_extensions import override\n\n\nclass BatchProcessorNode(BaseNode):\n  \"\"\"Processes a list of items in fixed-size batches.\"\"\"\n\n  batch_size: int = 10\n\n  @override\n  async def _run_impl(\n      self, *, ctx: Context, node_input: Any\n  ) -> AsyncGenerator[Any, None]:\n    items = node_input if isinstance(node_input, list) else [node_input]\n    results = []\n    for i in range(0, len(items), self.batch_size):\n      results.extend(await process_batch(items[i:i + self.batch_size]))\n    yield Event(output=results)\n\n\nbatcher = BatchProcessorNode(name='batch_processor', batch_size=25)\n```\n\n`_run_impl` may yield an `Event`, a `RequestInput`, a bare value (wrapped as\n`Event(output=...)`), or `None` (skipped). There is no `get_name()` to override\n— the node's name is the `name` field.\n\n### `BaseNode` fields\n\n| Field | Default | Purpose |\n|---|---|---|\n| `name` | required | Node identity within the graph; must be unique |\n| `description` | `''` | Human-readable label |\n| `rerun_on_resume` | `False` | Re-run after an interrupt instead of taking the answer as output |\n| `wait_for_output` | `False` | Finishing without an output event leaves the node WAITING, not COMPLETED |\n| `retry_config` | `None` | Retry policy |\n| `timeout` | `None` | Seconds before `NodeTimeoutError` |\n| `input_schema` | `None` | Validates and coerces `node_input` |\n| `output_schema` | `None` | Validates and coerces `event.output` |\n| `state_schema` | `None` | Validates `ctx.state` writes; `app:`, `user:`, `temp:` keys bypass it |\n\n### `wait_for_output`\n\nWith `wait_for_output=True`, a node that completes without emitting an output\nevent moves to WAITING rather than COMPLETED, and no downstream node fires. An\nupstream predecessor can trigger it again later — useful for a node that\naccumulates across several triggers before producing one answer.\n\n```python\nclass CollectorNode(BaseNode):\n  wait_for_output: bool = True\n\n  @override\n  async def _run_impl(self, *, ctx, node_input):\n    collected = ctx.state.get('collected', []) + [node_input]\n    yield Event(state={'collected': collected})\n    if len(collected) >= 3:\n      yield Event(output=collected)  # now COMPLETED, downstream fires\n```\n\n`JoinNode` reaches a similar result by a different mechanism — it sets\n`_requires_all_predecessors`, so the orchestrator holds it until every\npredecessor has run and then hands it all their outputs at once.\n\n## Wrapping a tool as a node\n\n`_ToolNode` is private and keyword-only. Its input must be a dict of tool\narguments, or `None`.\n\n```python\nfrom google.adk.tools import FunctionTool\nfrom google.adk.workflow._tool_node import _ToolNode\n\n\ndef search(query: str) -> str:\n  \"\"\"Search for information.\"\"\"\n  return f'Results for: {query}'\n\n\ntool_node = _ToolNode(tool=FunctionTool(search), name='search_node')\n\nagent = Workflow(\n    name='with_tool',\n    edges=[('START', prepare_query, tool_node, process_results)],\n)\n```\n\n## Graph validation\n\n`Workflow` validates the graph when it is constructed, in this order. Each check\nraises `ValueError` naming the offending node or edge.\n\n1. No duplicate node names.\n2. A `START` node exists.\n3. No edge leaving `START` carries a route.\n4. Every node is reachable from `START`, and `START` has no incoming edges.\n5. No two edges share both a source and a target — routes are not part of edge\n   identity.\n6.  A `route` list on one edge never mixes `__DEFAULT__` with named routes.\n7. No unconditional cycle — a cycle needs at least one routed edge.\n8. Where a source declares `output_schema` and its target declares\n   `input_schema`, the two must be the same schema.\n9. No edge into a `mode='chat'` `LlmAgent` from anything but `START`, because a\n   chat agent reads conversation history rather than a node input.\n\nNodes with no outgoing edges are the graph's terminals; their outputs become the\nworkflow's own output.\n\n## Ways to declare edges\n\n```python\nedges = [\n    ('START', node_a),                 # simple\n    (node_a, node_b, 'route'),         # routed\n    (node_a, (node_b, node_c)),        # fan-out\n    ((node_b, node_c), join_node),     # fan-in\n    ('START', node_a, node_b, node_c), # chain of three edges\n    (classifier, {'ok': handler_a, 'err': handler_b}),  # routing map\n]\n```\n\n`Edge` objects are the explicit form. It is a Pydantic model, so its fields are\nkeyword-only:\n\n```python\nedges = [\n    Edge(from_node=START, to_node=node_a),\n    Edge(from_node=node_a, to_node=node_b, route='success'),\n]\n```\n\nTo build the graph yourself, pass `graph=` instead of `edges=`:\n\n```python\nfrom google.adk.workflow._graph import Graph\n\ngraph = Graph.from_edge_items([('START', node_a), (node_a, node_b)])\nagent = Workflow(name='my_workflow', graph=graph)\n```\n\n## Where the code lives\n\n| Component | File |\n|---|---|\n| `Workflow` | `src/google/adk/workflow/_workflow.py` |\n| `Graph`, `Edge`, `DEFAULT_ROUTE` | `src/google/adk/workflow/_graph.py` |\n| graph validation rules | `src/google/adk/workflow/utils/_graph_validation.py` |\n| `BaseNode`, `START` | `src/google/adk/workflow/_base_node.py` |\n| `FunctionNode` | `src/google/adk/workflow/_function_node.py` |\n| `@node`, `Node` | `src/google/adk/workflow/_node.py` |\n| `JoinNode` | `src/google/adk/workflow/_join_node.py` |\n| `_ParallelWorker` | `src/google/adk/workflow/_parallel_worker.py` |\n| `_ToolNode` | `src/google/adk/workflow/_tool_node.py` |\n| `RetryConfig` | `src/google/adk/workflow/_retry_config.py` |\n| running an `LlmAgent` as a node | `src/google/adk/workflow/_llm_agent_wrapper.py` |\n| dynamic node scheduling | `src/google/adk/workflow/_dynamic_node_scheduler.py` |\n| `Context` | `src/google/adk/agents/context.py` |\n| `Event` | `src/google/adk/events/event.py` |\n| `RequestInput` | `src/google/adk/events/request_input.py` |\n\n<!-- File: .agents/skills/adk-agent-builder/references/best-practices.md (Tokens: ~1399 | Category: anthropic-skill) -->\n# ADK Workflow Rules That Bite\n\nThe failure modes that account for most broken ADK workflows. Each one is a\nsilent or confusing failure, not a clear error — that is why they are collected\nhere rather than left to be discovered.\n\n## Type everything with Pydantic models\n\nUse a `BaseModel` for node inputs, node outputs, LLM `output_schema`,\n`RequestInput.response_schema`, and structured state values. `dict[str, Any]`\ngives up validation, IDE completion, and the automatic dict-to-model conversion\nthat `FunctionNode` performs from type hints.\n\n```python\n# Loses validation and downstream typing\ndef lookup_flights(node_input: dict[str, Any]) -> dict[str, Any]:\n  return {'flight_cost': 500, 'details': 'Economy'}\n\n\n# Validated on the way in and on the way out\nclass FlightInfo(BaseModel):\n  flight_cost: int\n  details: str\n\n\ndef lookup_flights(node_input: Itinerary) -> FlightInfo:\n  return FlightInfo(flight_cost=500, details='Economy')\n```\n\n## `event.output` is plumbing; `event.content` is the UI\n\nThe web UI renders `event.content` only. A node that produces something a human\nshould read must emit it as a message, in addition to the output the next node\nconsumes.\n\n```python\ndef final_output(node_input: str):\n  yield Event(message=node_input)  # rendered in the web UI\n  yield Event(output=node_input)   # passed to the downstream node\n```\n\n`Event(message=...)` is a constructor convenience that writes `event.content`.\nStream partial text with `Event(message='chunk', partial=True)`. LLM agents emit\ntheir content events automatically; function nodes do not.\n\n## Write state through `Event(state=...)`, not `ctx.state[key] = ...`\n\nState passed to the `Event` constructor lands in `event.actions.state_delta`, so\nit is part of event history and survives the replay that non-resumable\nhuman-in-the-loop performs. A direct `ctx.state` mutation is a side effect that\nreplay does not reproduce.\n\n```python\n# Recorded in event history\ndef save(node_input: str):\n  return Event(output=node_input, state={'user_request': node_input})\n\n\n# Lost on replay\ndef save(ctx: Context, node_input: str) -> str:\n  ctx.state['user_request'] = node_input\n  return node_input\n```\n\nReading is always `ctx.state[...]`.\n\n## At most one output event per node execution\n\nA node may yield many events, but only one of them may carry `output`. Two\noutput events are merged into a list, which silently changes the downstream\nnode's `node_input` type from `str` to `list[str]`. The same limit applies to\n`route`, except there a second routed event raises `ValueError`.\n\n```python\ndef my_node(node_input: str):\n  yield Event(message='Processing...')   # display only\n  yield Event(state={'status': 'done'})  # state only\n  yield Event(output='final result')     # the one output\n```\n\nThis holds for function nodes, LLM agent nodes, and nested workflows alike.\n\n## A function either yields or returns — never both\n\nPython turns any function containing `yield` into a generator and discards its\n`return value`. Mixing the two loses the output with no error.\n\n```python\n# Generator: every event is yielded\ndef my_node(node_input: str):\n  yield Event(state={'key': 'value'})\n  yield Event(output='result')\n\n\n# Plain function: one return, which may be an Event or a bare value\ndef my_node(node_input: str):\n  return Event(output='result', state={'key': 'value'})\n\n\n# Broken: the return is silently ignored\ndef my_node(node_input: str):\n  yield Event(state={'key': 'value'})\n  return Event(output='result')\n```\n\n## `{var}` in an instruction reads state, never `node_input`\n\nInstruction placeholders resolve against `ctx.state` only. `node_input` is\ndelivered to the model as the user message, so `{node_input}` is just a missing\nstate key and raises `KeyError` at call time.\n\n```python\n# KeyError: 'node_input' is not a state key\nAgent(name='summarizer', instruction='Summarize this: {node_input}')\n\n# The predecessor's output is already the user message\nAgent(name='summarizer', instruction='Summarize the text in one sentence.')\n\n# Anything else the instruction needs must be in state first\nAgent(name='writer', instruction='Write about \"{topic}\". Feedback: {feedback?}')\n```\n\n`{var?}` substitutes an empty string when the key is absent; `{var}` raises.\n\n## A chat-mode agent can only follow `START`\n\nGraph validation rejects an edge into an `LlmAgent` with `mode='chat'` from any\nnode other than `START`, because a chat agent reads conversational history\nrather than a node input. Set `mode='single_turn'` (the default for an\nauto-wrapped agent) or `mode='task'` on agents used mid-graph.\n\n## Everything in `event.output` must be JSON-serializable\n\n- `FunctionNode` converts a returned `BaseModel` with `model_dump()`, so\n  returning a model is safe. A `types.Content` or any other non-serializable\n  object is not.\n- An LLM agent with `output_schema` validates then dumps, so\n  `ctx.state[output_key]` is a plain `dict`, never a model instance. Read it\n  with `data['field']`, or rebuild the model with `MyModel(**data)`.\n- This bites hardest at a `JoinNode`, which parks partial inputs in session\n  state while waiting. If a predecessor is an LLM agent without `output_schema`,\n  the parked value is a `types.Content` and `DatabaseSessionService` raises\n  `TypeError` on write.\n\n## Give every loop iteration its own `interrupt_id`\n\nA node that yields `RequestInput` inside a loop must vary the `interrupt_id` per\niteration. Reusing one id makes event-based state reconstruction match an\nearlier iteration's answer to the current interrupt, which restarts the loop\nforever.\n\n```python\nreview_count = ctx.state.get('review_count', 0)\nyield RequestInput(\n    interrupt_id=f'review_{review_count}',\n    message='Approve?',\n)\n```\n\nIncrement the counter through `Event(state={'review_count': review_count + 1})`\nwhen the response arrives.\n\n<!-- File: .agents/skills/adk-agent-builder/references/callbacks-and-plugins.md (Tokens: ~1300 | Category: anthropic-skill) -->\n# Callbacks and Plugins\n\nCallbacks hook one agent; plugins hook every agent under an `App`. Both follow\nthe same contract: **return `None` to let the normal thing happen, return a\nvalue to replace it.**\n\n```python\nfrom google.adk.agents.callback_context import CallbackContext\nfrom google.adk.models.llm_request import LlmRequest\nfrom google.adk.models.llm_response import LlmResponse\nfrom google.adk.tools import BaseTool, ToolContext\n```\n\n`CallbackContext` and `ToolContext` are both aliases for `Context`.\n\n## The eight agent callbacks\n\n| Field | Arguments | Return to override |\n|---|---|---|\n| `before_agent_callback` | `(CallbackContext)` | `types.Content` — skips the agent entirely |\n| `after_agent_callback` | `(CallbackContext)` | `types.Content` — replaces the agent's output |\n| `before_model_callback` | `(CallbackContext, LlmRequest)` | `LlmResponse` — skips the model call |\n| `after_model_callback` | `(CallbackContext, LlmResponse)` | `LlmResponse` — replaces the response |\n| `on_model_error_callback` | `(CallbackContext, LlmRequest, Exception)` | `LlmResponse` — suppresses the error |\n| `before_tool_callback` | `(BaseTool, dict, ToolContext)` | `dict` — skips the tool call |\n| `after_tool_callback` | `(BaseTool, dict, ToolContext, dict)` | `dict` — replaces the tool result |\n| `on_tool_error_callback` | `(BaseTool, dict, ToolContext, Exception)` | `dict` — suppresses the error |\n\nEvery one may be sync or async, and every one accepts either a single callable\nor a list. A list runs in order and stops at the first callback that returns\nsomething other than `None`.\n\n## Examples\n\nBlocking a request before it reaches the model:\n\n```python\ndef guard(\n    callback_context: CallbackContext, llm_request: LlmRequest\n) -> LlmResponse | None:\n  for content in llm_request.contents:\n    for part in content.parts or []:\n      if part.text and 'unsafe' in part.text:\n        return LlmResponse(content=types.ModelContent('I cannot process that.'))\n  return None\n\n\nagent = LlmAgent(\n    name='guarded', model='gemini-3.5-flash', before_model_callback=guard\n)\n```\n\nObserving without changing anything — note the explicit `return None`:\n\n```python\ndef log_response(\n    callback_context: CallbackContext, llm_response: LlmResponse\n) -> LlmResponse | None:\n  logger.info('model said: %s', llm_response.content)\n  return None\n```\n\nAuditing and repairing tool calls:\n\n```python\ndef audit(tool: BaseTool, args: dict, tool_context: ToolContext) -> dict | None:\n  logger.info('calling %s with %s', tool.name, args)\n  return None\n\n\ndef repair(\n    tool: BaseTool, args: dict, tool_context: ToolContext, tool_response: dict\n) -> dict | None:\n  if 'error' in tool_response:\n    return {'result': 'Tool execution failed, please try again.'}\n  return None\n\n\nagent = LlmAgent(\n    name='audited',\n    model='gemini-3.5-flash',\n    tools=[my_tool],\n    before_tool_callback=audit,\n    after_tool_callback=repair,\n)\n```\n\nDegrading gracefully on failure:\n\n```python\ndef handle_model_error(\n    callback_context: CallbackContext,\n    llm_request: LlmRequest,\n    error: Exception,\n) -> LlmResponse | None:\n  return LlmResponse(content=types.ModelContent('Service unavailable.'))\n\n\nagent = LlmAgent(\n    name='resilient',\n    model='gemini-3.5-flash',\n    on_model_error_callback=handle_model_error,\n)\n```\n\n## Plugins\n\nA plugin is the same set of hooks applied to every agent, tool, and model call\nin an app, plus a few that only make sense at app scope. All hooks are async and\nkeyword-only.\n\n```python\nfrom google.adk.plugins.base_plugin import BasePlugin\n\n\nclass MyPlugin(BasePlugin):\n\n  def __init__(self):\n    super().__init__(name='my_plugin')\n\n  async def before_agent_callback(self, *, agent, callback_context):\n    return None\n\n  async def before_model_callback(self, *, callback_context, llm_request):\n    return None\n```\n\nBeyond the eight agent-level hooks, `BasePlugin` adds\n`on_user_message_callback`, `before_run_callback`, `on_event_callback`,\n`after_run_callback`, `on_agent_error_callback`, and `on_run_error_callback`.\n\nRegister plugins on the `App`:\n\n```python\nfrom google.adk.apps import App\nfrom google.adk.plugins.context_filter_plugin import ContextFilterPlugin\n\napp = App(\n    name='my_app',\n    root_agent=root_agent,\n    plugins=[ContextFilterPlugin(num_invocations_to_keep=3)],\n)\n```\n\n## Built-in plugins\n\n| Plugin | Module under `google.adk.plugins` | Purpose |\n|---|---|---|\n| `ContextFilterPlugin` | `context_filter_plugin` | Trims history to the last N invocations |\n| `SaveFilesAsArtifactsPlugin` | `save_files_as_artifacts_plugin` | Stores file outputs as session artifacts |\n| `GlobalInstructionPlugin` | `global_instruction_plugin` | Prepends an instruction to every agent |\n| `LoggingPlugin` | `logging_plugin` | Logs the invocation lifecycle |\n| `DebugLoggingPlugin` | `debug_logging_plugin` | Verbose request and response logging |\n| `ReflectAndRetryToolPlugin` | `reflect_retry_tool_plugin` | Retries a failed tool call after letting the model reflect |\n| `MultimodalToolResultsPlugin` | `multimodal_tool_results_plugin` | Routes non-text tool results into content |\n| `AutoTracingPlugin` | `auto_tracing_plugin` | Emits tracing spans automatically |\n| `BigQueryAgentAnalyticsPlugin` | `bigquery_agent_analytics_plugin` | Exports invocation analytics to BigQuery |\n\n<!-- File: .agents/skills/adk-agent-builder/references/dynamic-nodes.md (Tokens: ~938 | Category: anthropic-skill) -->\n# Dynamic Node Scheduling\n\n`await ctx.run_node(...)` runs another node from inside a node and returns its\noutput. It turns graph control flow into ordinary Python: loops, conditionals,\nand early exits, written as loops, conditionals, and early exits.\n\n```python\nfrom google.adk import Agent, Context, Event, Workflow\nfrom google.adk.workflow import FunctionNode, node\n```\n\n## Example\n\n```python\nclass Feedback(BaseModel):\n  grade: str\n\n\ngenerate_headline = Agent(\n    name='generate_headline',\n    instruction='Write a headline about the topic \"{topic}\".',\n)\n\nevaluate_headline = Agent(\n    name='evaluate_headline',\n    mode='single_turn',\n    instruction='Grade whether the headline is tech-related.',\n    output_schema=Feedback,\n)\n\n\n@node(rerun_on_resume=True)\nasync def orchestrate(ctx: Context, node_input: str) -> str:\n  yield Event(state={'topic': node_input})\n  while True:\n    headline = await ctx.run_node(generate_headline)\n    feedback = Feedback.model_validate(\n        await ctx.run_node(evaluate_headline, node_input=headline)\n    )\n    if feedback.grade == 'tech-related':\n      yield headline\n      break\n\n\nroot_agent = Workflow(name='root_agent', edges=[('START', orchestrate)])\n```\n\n## `ctx.run_node` arguments\n\n```python\nawait ctx.run_node(\n    node,                     # a function, Agent, BaseTool, or BaseNode\n    node_input=None,\n    *,\n    use_as_output=False,\n    run_id=None,\n    use_sub_branch=False,\n    override_branch=None,\n)\n```\n\n| Argument | Effect |\n|---|---|\n| `use_as_output` | The child's output becomes the parent's output; the parent's own output events are suppressed |\n| `run_id` | Names this execution instead of auto-numbering it |\n| `use_sub_branch` | Appends `node_name@run_id` to the branch, isolating events from sibling runs |\n| `override_branch` | Uses a specific branch instead of the parent's |\n\n## Rules the framework enforces\n\n**The calling node needs `rerun_on_resume=True`.** Calling `run_node` without it\nraises immediately. The reason is resumption: a dynamically scheduled child may\ninterrupt for user input, and the only way the parent can receive the answer is\nto be re-run from the top.\n\n**An explicit `run_id` must contain a non-digit.** Auto-generated ids are plain\nnumbers (`\"1\"`, `\"2\"`, ...), so an all-digit custom id would collide with one.\n`ValueError` names the offending id.\n\n**`use_as_output=True` at most once per parent execution.** A second call raises\n`Node {path} already has a use_as_output delegate.` (A `Workflow` calling\n`run_node` is exempt.)\n\n**`await` the call directly.** Wrapping it in `asyncio.create_task()` leaves the\nchild unsupervised: its errors are swallowed and it is not cancelled when the\nparent is interrupted.\n\n## Imperative workflows\n\nStandard Python replaces routed edges entirely:\n\n```python\nasync def orchestrator(ctx: Context, node_input: str):\n  res_a = await ctx.run_node(step_a, node_input=node_input)\n  if 'success' in res_a:\n    return await ctx.run_node(step_b, node_input=res_a)\n  return await ctx.run_node(step_c, node_input=res_a)\n```\n\n### Three traps in this style\n\n**A raw function's parameters bind from state, not from `node_input`.** Node\nparameter binding defaults to `'state'`, so a value passed as\n`run_node(fn, node_input=x)` reaches the function only through a parameter\nliterally named `node_input`.\n\n```python\ndef my_worker(node_input: str):  # this name, or the value never arrives\n  return f'Done: {node_input}'\n```\n\n**A child that itself calls `run_node` is a parent too**, so it also needs\n`rerun_on_resume=True`. Raw functions default to `False`, so wrap it:\n\n```python\ninner = FunctionNode(func=inner_orchestrator, rerun_on_resume=True)\n```\n\n**A generator cannot `return` a value.** In a node that uses `yield`, produce\nthe result with `yield Event(output=...)`; `return value` is a syntax error in\nan async generator and silently ignored in a sync one.\n\n<!-- File: .agents/skills/adk-agent-builder/references/function-nodes.md (Tokens: ~1555 | Category: anthropic-skill) -->\n# Function Nodes\n\nAny Python function can be a workflow node. Put the callable straight into\n`edges` and the framework wraps it in a `FunctionNode`.\n\n```python\nfrom google.adk import Context, Event\nfrom google.adk.workflow import FunctionNode, RetryConfig, node\n```\n\n## Parameter resolution\n\n`FunctionNode` inspects the signature and fills each parameter by name:\n\n| Parameter name | Bound to |\n|---|---|\n| `ctx` | the workflow `Context` |\n| `node_input` | the predecessor node's output |\n| anything else | `ctx.state[param_name]`, falling back to the default |\n\n```python\ndef with_context(ctx: Context, node_input: str) -> str:\n  return f'Session {ctx.session.id}: {node_input}'\n\n\ndef input_only(node_input: str) -> str:\n  return node_input.upper()\n\n\ndef from_state(node_input: str, user_name: str) -> str:\n  # user_name comes from ctx.state['user_name']\n  return f'{user_name}: {node_input}'\n\n\ndef no_params() -> str:\n  return 'hello'\n```\n\nPass `parameter_binding='node_input'` to `FunctionNode` or `@node` to bind every\nparameter from a `node_input` dict instead of from state. That mode also infers\n`input_schema` and `output_schema` from the signature, and is what the framework\nuses when a node is exposed as an agent's tool.\n\n## Return values\n\nA plain return is wrapped in `Event(output=...)`. Returning `None` emits no\nevent, so no downstream node fires.\n\n```python\ndef process(node_input: str) -> str:\n  return f'Processed: {node_input}'\n\n\nasync def fetch_data(node_input: str) -> dict:\n  return {'data': await some_api_call(node_input)}\n\n\ndef maybe_output(node_input: str) -> str | None:\n  if not node_input:\n    return None  # downstream stays idle\n  return f'Got: {node_input}'\n```\n\nGenerators may yield `Event` objects or bare values — a bare yield is wrapped\nthe same way a return is.\n\n```python\nasync def multi(ctx: Context):\n  yield Event(message='working...')\n  yield 'output value'  # becomes Event(output='output value')\n```\n\n## Input coercion\n\n`FunctionNode` runs each argument through a Pydantic `TypeAdapter` built from\nthe annotation, so the annotation is both documentation and a coercion rule:\n\n| Annotation | Incoming value | Result |\n|---|---|---|\n| a `BaseModel` subclass | `dict` | validated model instance |\n| `list[Model]`, `dict[K, Model]` | nested dicts | recursively converted |\n| `str` (including `str \\| None`) | `types.Content` | concatenated text of the text parts |\n| anything else | any | validated by `TypeAdapter`, `TypeError` on mismatch |\n\nThe `types.Content` to `str` rule drops non-text parts (inline data, file data,\nexecutable code) and logs a warning when it does.\n\n```python\nclass Order(BaseModel):\n  item: str\n  quantity: int\n\n\ndef process_order(node_input: Order) -> str:\n  # {'item': 'widget', 'quantity': 3} arrives as Order(item='widget', quantity=3)\n  return f'Order: {node_input.quantity}x {node_input.item}'\n```\n\nA union annotation (`list | dict`) accepts anything — the adapter is satisfied\nby any member, so wrong types reach the body. Use `isinstance` checks inside the\nfunction when a union is unavoidable.\n\n## What `node_input` will actually be\n\n| Predecessor | `node_input` |\n|---|---|\n| function returning `str` / `dict` | that value |\n| function returning `Event(output=X)` | `X` |\n| `LlmAgent` without `output_schema` | `str` — the model's concatenated text |\n| `LlmAgent` with `output_schema` | `dict` — the validated model, dumped |\n| `JoinNode` | `dict[str, Any]` keyed by predecessor node name |\n| a `parallel_worker=True` node | `list` of per-item results |\n| `START` without `input_schema` | `types.Content` (the user's message) |\n| `START` with `input_schema` | the parsed schema type |\n\n## Explicit `FunctionNode`\n\nConstruct one directly when you need to override its properties. Every argument\nis keyword-only, including `func`.\n\n```python\napi_node = FunctionNode(\n    func=flaky_api_call,\n    name='api_call',             # defaults to func.__name__\n    rerun_on_resume=True,        # re-run after a human-in-the-loop interrupt\n    retry_config=RetryConfig(max_attempts=3, initial_delay=1.0),\n    timeout=30.0,\n)\n```\n\n## The `@node` decorator\n\n`@node` is the same thing with less ceremony, and it also accepts an already-made\nnode, an agent, or a tool.\n\n```python\n@node\ndef plain(node_input: str) -> str:\n  return node_input\n\n\n@node(name='custom_name', rerun_on_resume=True)\nasync def renamed(node_input: str) -> str:\n  return node_input\n\n\n# Called as a function on an existing callable\nmy_node = node(some_func, name='renamed')\n\n# Fan a list out across parallel workers\nworker = node(some_func, parallel_worker=True)\n```\n\nAccepted keywords: `name`, `rerun_on_resume`, `retry_config`, `timeout`,\n`parallel_worker`, `max_parallel_workers`, `auth_config`,\n`require_confirmation`, `parameter_binding`.\n\n## Routing and state from a node\n\n```python\ndef classify(node_input: str):\n  route = 'urgent' if 'urgent' in node_input else 'normal'\n  return Event(output=node_input, route=route)\n\n\ndef update_counter(node_input: str):\n  return Event(output=node_input, state={'last_input': node_input})\n```\n\n## Requiring confirmation before a node runs\n\n`require_confirmation` pauses the workflow with an `adk_request_input`\ninterrupt (`wf_tool_confirmation:{node_path}`) before the function runs. It\naccepts a `bool` or a sync/async predicate callable receiving the function's\nbound arguments (plus `ctx` or `node_input` if declared). `@node` automatically\ndefaults `rerun_on_resume=True` when `require_confirmation` is set.\n\n```python\n@node(require_confirmation=True)\ndef delete_file(path: str) -> str:\n  return f'Deleted {path}'\n\n\n@node(require_confirmation=lambda amount: amount > 1000)\ndef transfer_funds(amount: int) -> str:\n  return f'Transferred {amount}'\n```\n\n## Requiring authentication before a node runs\n\n`auth_config` makes the framework request user credentials before the node's\nfirst execution. It requires `rerun_on_resume=True`, because the node runs again\nonce the credential arrives.\n\n```python\nsecured = FunctionNode(\n    func=call_private_api,\n    auth_config=my_auth_config,\n    rerun_on_resume=True,\n)\n```\n\nInside the node, read the credential with\n`AuthHandler(auth_config).get_auth_response(ctx.state)`\n(`google.adk.auth.auth_handler.AuthHandler`).\n\n<!-- File: .agents/skills/adk-agent-builder/references/getting-started.md (Tokens: ~2218 | Category: anthropic-skill) -->\n# Getting Started: Creating ADK Agents\n\nEnvironment, the agent directory convention, a first LLM agent, and the jump to\ngraph workflows.\n\n## CLI commands\n\n| Command | What it does |\n|---|---|\n| `adk create {agent_name}` | Scaffolds an agent directory |\n| `adk run {agent_dir}` | Runs the agent in the terminal |\n| `adk web {agent_dir}` | Dev server on `http://localhost:8000` (development only) |\n| `adk api_server {agent_dir}` | HTTP API for the agent |\n\n## 1. Environment\n\n```bash\nuv venv --python python3.11 .venv\nsource .venv/bin/activate\nuv pip install google-adk\n```\n\n`pip install google-adk` in a `python -m venv` works too. ADK requires Python\n3.10 or newer.\n\n## 2. API keys\n\nPut a `.env` file in the **agent directory**, not its parent — the loader looks\nbeside `agent.py`. Do not commit it.\n\nGoogle AI Studio (get a key at https://aistudio.google.com/app/apikey):\n\n```bash\nGOOGLE_GENAI_USE_ENTERPRISE=FALSE\nGOOGLE_API_KEY=YOUR_API_KEY\n```\n\nVertex AI, after `gcloud auth application-default login`:\n\n```bash\nGOOGLE_GENAI_USE_ENTERPRISE=TRUE\nGOOGLE_CLOUD_PROJECT=your-project-id\nGOOGLE_CLOUD_LOCATION=us-central1\n```\n\nVertex AI express mode swaps the project/location pair for an API key:\n\n```bash\nGOOGLE_GENAI_USE_ENTERPRISE=TRUE\nGOOGLE_API_KEY=YOUR_EXPRESS_MODE_KEY\n```\n\n`GOOGLE_GENAI_USE_VERTEXAI` is the old name for the same switch. It still works\nbut emits a `DeprecationWarning`; `GOOGLE_GENAI_USE_ENTERPRISE` wins when both\nare set.\n\n## 3. Directory layout\n\nThe CLI discovers agents by convention:\n\n```text\nmy_agent/\n├── __init__.py    # from . import agent\n├── agent.py       # defines root_agent (and optionally app)\n└── .env\n```\n\n`__init__.py` must re-export the module, or the agent will not appear in\n`adk web`:\n\n```python\nfrom . import agent\n```\n\n## 4. A basic LLM agent\n\n`LlmAgent` (aliased as `Agent`) binds a model, an instruction, and tools.\n\n```python\nfrom google.adk import Agent\n\n\ndef get_weather(city: str) -> dict:\n  \"\"\"Returns the current weather for a specified city.\"\"\"\n  return {\n      'status': 'success',\n      'city': city,\n      'weather': 'sunny',\n      'temperature': '72F',\n  }\n\n\nroot_agent = Agent(\n    model='gemini-3.5-flash',\n    name='root_agent',\n    description='An assistant that reports the weather.',\n    instruction=(\n        'You are a helpful assistant. Use get_weather to look up the'\n        ' weather in any city. Be concise.'\n    ),\n    tools=[get_weather],\n)\n```\n\n| Field | Purpose |\n|---|---|\n| `model` | Model id, e.g. `'gemini-3.5-flash'`, `'gemini-3.1-pro-preview'` |\n| `instruction` | System prompt; `{var}` placeholders resolve from session state |\n| `tools` | Python callables; name, docstring, and type hints become the tool schema |\n| `description` | How a parent agent decides to route to this one |\n| `output_key` | Session-state key to store the agent's final text under |\n\nA tool function needs a docstring and type hints on every parameter — the LLM\nsees only those. Return a `dict` or a `str`.\n\n## 5. Running it programmatically\n\n```python\nimport asyncio\n\nfrom google.adk.runners import InMemoryRunner\nfrom google.genai import types\n\nfrom my_agent import agent\n\n\nasync def main():\n  runner = InMemoryRunner(app_name='my_app', agent=agent.root_agent)\n  session = await runner.session_service.create_session(\n      app_name='my_app', user_id='user1'\n  )\n  message = types.Content(\n      role='user',\n      parts=[types.Part.from_text(text=\"What's the weather in Paris?\")],\n  )\n  async for event in runner.run_async(\n      user_id='user1', session_id=session.id, new_message=message\n  ):\n    if event.content and event.content.parts:\n      text = event.content.parts[0].text\n      if text:\n        print(f'{event.author}: {text}')\n\n\nasyncio.run(main())\n```\n\n## 6. From one agent to a workflow\n\nA `Workflow` replaces \"one LLM decides everything\" with an explicit graph. The\nsmallest one has a single edge from `START`:\n\n```python\nfrom google.adk import Workflow\n\n\ndef greet(node_input: str) -> str:\n  return f'Hello! You said: {node_input}'\n\n\nroot_agent = Workflow(name='my_workflow', edges=[('START', greet)])\n```\n\n### Sequential pipeline of LLM agents\n\n> **Deprecated.** `SequentialAgent` is deprecated in favour of `Workflow`.\n> Prefer the explicit edge list above for new code; this form is documented\n> because existing agents still use it.\n\n`SequentialAgent` generates `START -> a -> b -> c` for you. Each agent's\n`output_key` publishes to session state, and the next agent reads it through an\ninstruction placeholder.\n\n```python\nfrom google.adk.agents import LlmAgent, SequentialAgent\n\nwriter = LlmAgent(\n    name='CodeWriterAgent',\n    model='gemini-3.5-flash',\n    instruction=(\n        'Write Python code that fulfills the user request. Output only the'\n        ' code block.'\n    ),\n    description='Writes initial Python code from a specification.',\n    output_key='generated_code',\n)\n\nreviewer = LlmAgent(\n    name='CodeReviewerAgent',\n    model='gemini-3.5-flash',\n    instruction=(\n        'Review this code and reply with a bulleted list of issues, or \"No'\n        ' major issues found.\" if it is clean:\\n\\n{generated_code}'\n    ),\n    description='Reviews code and provides feedback.',\n    output_key='review_comments',\n)\n\nrefactorer = LlmAgent(\n    name='CodeRefactorerAgent',\n    model='gemini-3.5-flash',\n    instruction=(\n        'Improve this code:\\n\\n{generated_code}\\n\\nAddressing these'\n        ' comments:\\n\\n{review_comments}\\n\\nOutput only the final code'\n        ' block.'\n    ),\n    description='Refactors code based on review comments.',\n    output_key='refactored_code',\n)\n\nroot_agent = SequentialAgent(\n    name='CodePipelineAgent',\n    sub_agents=[writer, reviewer, refactorer],\n    description='Writes, reviews, and refactors Python code.',\n)\n```\n\n### Graph with conditional routing\n\nA node returns `Event(route=...)` and the edge dict picks the branch.\n\n```python\nfrom google.adk import Event, Workflow\n\n\ndef parse_input(node_input: str) -> dict:\n  return {'text': node_input, 'word_count': len(node_input.split())}\n\n\ndef classify(node_input: dict):\n  route = 'long' if node_input['word_count'] > 10 else 'short'\n  return Event(output=node_input, route=route)\n\n\ndef handle_short(node_input: dict) -> str:\n  return f\"Short ({node_input['word_count']} words): {node_input['text']}\"\n\n\ndef handle_long(node_input: dict) -> str:\n  return f\"Long ({node_input['word_count']} words): {node_input['text'][:50]}...\"\n\n\nroot_agent = Workflow(\n    name='classifier_workflow',\n    input_schema=str,\n    edges=[\n        ('START', parse_input, classify),\n        (classify, {'short': handle_short, 'long': handle_long}),\n    ],\n)\n```\n\n### Parallel list processing\n\n`parallel_worker=True` makes a node run once per item of a list input and\nreturn a list of results.\n\n```python\nfrom google.adk import Workflow\nfrom google.adk.workflow import node\n\n\ndef split_input(node_input: str) -> list:\n  return [item.strip() for item in node_input.split(',')]\n\n\n@node(parallel_worker=True)\ndef process_item(node_input: str) -> dict:\n  return {'item': node_input, 'upper': node_input.upper()}\n\n\ndef format_results(node_input: list) -> str:\n  return '\\n'.join(f\"- {r['item']} -> {r['upper']}\" for r in node_input)\n\n\nroot_agent = Workflow(\n    name='parallel_processor',\n    input_schema=str,\n    edges=[('START', split_input, process_item, format_results)],\n)\n```\n\n### Mixing function nodes and an LLM agent\n\n```python\nfrom google.adk import Workflow\nfrom google.adk.agents import LlmAgent\n\n\ndef get_weather(city: str) -> dict:\n  \"\"\"Get the current weather for a city.\"\"\"\n  return {'city': city, 'temp': '72F', 'condition': 'sunny'}\n\n\ndef extract_city(node_input: str) -> str:\n  return node_input.strip()\n\n\nweather_agent = LlmAgent(\n    name='weather_reporter',\n    model='gemini-3.5-flash',\n    instruction='Use get_weather, then give a natural-language report.',\n    tools=[get_weather],\n)\n\n\ndef sign_off(node_input: str) -> str:\n  return f'{node_input}\\n\\nHave a great day!'\n\n\nroot_agent = Workflow(\n    name='weather_workflow',\n    input_schema=str,\n    edges=[('START', extract_city, weather_agent, sign_off)],\n)\n```\n\n## Troubleshooting\n\n| Symptom | Cause |\n|---|---|\n| `No module named 'google.adk'` | Virtual environment not activated, or `google-adk` not installed in it |\n| Agent missing from the `adk web` dropdown | `__init__.py` lacks `from . import agent`, or `agent.py` defines no `root_agent` |\n| API key errors | `.env` sits in the parent directory instead of the agent directory |\n| Model not found | Typo in the model id; non-Google models (Anthropic, LiteLLM) need extra dependencies |\n\n<!-- File: .agents/skills/adk-agent-builder/references/human-in-the-loop.md (Tokens: ~1397 | Category: anthropic-skill) -->\n# Human-in-the-Loop\n\nPause a workflow to ask the user something, then continue with their answer.\n\n```python\nfrom google.adk import Context, Event, Workflow\nfrom google.adk.apps import App, ResumabilityConfig\nfrom google.adk.events import RequestInput\nfrom google.adk.workflow import FunctionNode\n```\n\n## Asking\n\nYield or return a `RequestInput`. The node's output stream is normalized, so\neither works — a plain function can return one directly without becoming a\ngenerator.\n\n```python\nasync def approval_gate(ctx: Context, node_input: str):\n  yield RequestInput(message='Please approve this action:')\n\n\ndef evaluate_request(request: TimeOffRequest):\n  if request.days <= 1:\n    return TimeOffDecision(approved=True)  # no interrupt at all\n  return RequestInput(\n      interrupt_id='manager_approval',\n      message='Please review this time off request.',\n      payload=request,\n      response_schema=TimeOffDecision,\n  )\n```\n\nThe workflow emits an `adk_request_input` function call and stops. Responding to\nthat function call resumes it.\n\n| Field | Type | Notes |\n|---|---|---|\n| `interrupt_id` | `str` | Auto-generated UUID when omitted |\n| `message` | `str \\| None` | Shown to the user |\n| `payload` | `Any` | Arbitrary data carried along with the request |\n| `response_schema` | Pydantic class, Python type, or JSON-schema dict | Expected response shape |\n\n## Resuming: `rerun_on_resume`\n\nThe flag on the interrupted node decides what happens when the answer arrives.\n\n**`rerun_on_resume=False`** (the default for `FunctionNode`) — the node is *not*\nre-executed; the user's response becomes its output and flows downstream.\n\n```python\napproval_node = FunctionNode(func=ask_approval, rerun_on_resume=False)\n```\n\n**`rerun_on_resume=True`** (the default for an `LlmAgent` used as a node) — the\nnode runs again from the top, with answers available in `ctx.resume_inputs`,\nkeyed by `interrupt_id`.\n\n```python\nasync def interactive_node(ctx: Context, node_input: str):\n  if ctx.resume_inputs:\n    answer = list(ctx.resume_inputs.values())[0]\n    yield Event(output=f'User said: {answer}')\n  else:\n    yield RequestInput(message='What should I do?')\n```\n\n## Several questions from one node\n\nBecause a re-run node sees every answer so far, one node can walk a form:\n\n```python\nasync def multi_step_form(ctx: Context, node_input: str):\n  if not ctx.resume_inputs:\n    yield RequestInput(interrupt_id='ask_name', message='What is your name?')\n    return\n\n  if 'ask_email' not in ctx.resume_inputs:\n    yield RequestInput(interrupt_id='ask_email', message='What is your email?')\n    return\n\n  yield Event(output={\n      'name': ctx.resume_inputs['ask_name'],\n      'email': ctx.resume_inputs['ask_email'],\n  })\n```\n\n## Feeding the answer back into a loop\n\nA review-and-revise cycle turns the answer into a route. Vary the\n`interrupt_id` per iteration (see the best-practices reference for why) and read\n`ctx.resume_inputs` with the same id:\n\n```python\nasync def review(ctx: Context, node_input: Any):\n  review_count = ctx.state.get('review_count', 0)\n  interrupt_id = f'review_{review_count}'\n\n  response = ctx.resume_inputs.get(interrupt_id)\n  if response:\n    yield Event(\n        output=response,\n        route='approved' if response.get('approved') else 'rejected',\n        state={'review_count': review_count + 1},\n    )\n    return\n\n  yield RequestInput(\n      interrupt_id=interrupt_id,\n      message='Approve this plan?',\n      response_schema=ApprovalSchema,\n  )\n```\n\nWire the routes back with `(review, {'rejected': revise, 'approved': publish})`.\n\n## Resumable versus replayed\n\n**Replay (the default).** With no `App`, or with `is_resumable=False`, each\nresponse replays the workflow from `START`. Completed nodes are skipped and\nstate is rebuilt from event history, so only the interrupted node actually runs\nagain. Fine for a single interrupt; the replay cost grows with the graph.\n\n**Resumable.** Export an `App` with `is_resumable=True` and the workflow\ncheckpoints its progress into `event.actions.agent_state` and resumes at the\ninterrupted node instead of replaying.\n\n```python\nroot_agent = Workflow(name='my_workflow', edges=[...])\n\napp = App(\n    name='my_app',\n    root_agent=root_agent,\n    resumability_config=ResumabilityConfig(is_resumable=True),\n)\n```\n\nExport both `root_agent` and `app` from `agent.py` — the loader prefers `app`\nbut other tooling looks for `root_agent`. Use resumable mode for multi-step\ninterrupts, for `LongRunningFunctionTool`, and for any graph large enough that\nreplaying it is wasteful.\n\n## Human-in-the-loop from an LLM agent\n\nAn `LlmAgent` pauses through a `LongRunningFunctionTool` rather than\n`RequestInput`:\n\n```python\nfrom google.adk.tools import LongRunningFunctionTool\n\n\ndef approval_tool(request: str) -> str:\n  \"\"\"Request human approval for an action.\"\"\"\n  return f'Approved: {request}'\n\n\nllm_agent = LlmAgent(\n    name='agent_with_approval',\n    model='gemini-3.5-flash',\n    instruction='When you need approval, use approval_tool.',\n    tools=[LongRunningFunctionTool(func=approval_tool)],\n)\n```\n\nThe agent node already defaults to `rerun_on_resume=True`, so it picks the\nconversation back up on its own.\n\n## Answering from client code\n\n```python\nfrom google.genai import types\n\nfunction_call_id = interrupt_event.content.parts[0].function_call.id\n\nresponse = types.Content(\n    role='user',\n    parts=[types.Part(\n        function_response=types.FunctionResponse(\n            id=function_call_id,\n            name='adk_request_input',\n            response={'result': \"User's answer here\"},\n        )\n    )],\n)\n\nasync for event in runner.run_async(\n    user_id=user_id, session_id=session_id, new_message=response\n):\n  ...\n```\n\nA non-dict answer is carried under the `result` key as shown; a dict response\nmatching `response_schema` is passed through as-is.\n\n<!-- File: .agents/skills/adk-agent-builder/references/import-paths.md (Tokens: ~1617 | Category: anthropic-skill) -->\n# ADK Import Paths\n\nPrefer the canonical short import. The verbose module path is listed only where\nthere is no short form, or where the short form does not exist yet.\n\n## Canonical imports\n\nThese cover most agent code:\n\n```python\nfrom google.adk import Agent, Context, Event, Runner, Workflow\nfrom google.adk.events import Event, EventActions, RequestInput\nfrom google.adk.workflow import BaseNode, Edge, FunctionNode, JoinNode, node\nfrom google.adk.workflow import DEFAULT_ROUTE, RetryConfig, START\n```\n\n`google.adk` and `google.adk.workflow` load their members lazily, so importing\nfrom them does not pull in the whole package.\n\n## Agents\n\n| Component | Import |\n|---|---|\n| `Agent` (alias of `LlmAgent`) | `from google.adk import Agent` |\n| `LlmAgent` | `from google.adk.agents import LlmAgent` |\n| `BaseAgent` | `from google.adk.agents import BaseAgent` |\n| `SequentialAgent` (deprecated — use `Workflow`) | `from google.adk.agents import SequentialAgent` |\n| `ParallelAgent` (deprecated — use `Workflow`) | `from google.adk.agents import ParallelAgent` |\n| `LoopAgent` (deprecated — use `Workflow`) | `from google.adk.agents import LoopAgent` |\n| `RunConfig` | `from google.adk.agents import RunConfig` |\n\n## Workflow graph\n\n| Component | Import |\n|---|---|\n| `Workflow` | `from google.adk.workflow import Workflow` |\n| `Edge` | `from google.adk.workflow import Edge` |\n| `DEFAULT_ROUTE` (`\"__DEFAULT__\"`) | `from google.adk.workflow import DEFAULT_ROUTE` |\n| `START` | `from google.adk.workflow import START` |\n| `Graph` | `from google.adk.workflow._graph import Graph` |\n\n`Graph` is private; construct workflows from `edges=[...]` unless you need\n`Graph.from_edge_items(...)` directly.\n\n## Nodes\n\n| Component | Import |\n|---|---|\n| `BaseNode` | `from google.adk.workflow import BaseNode` |\n| `FunctionNode` | `from google.adk.workflow import FunctionNode` |\n| `Node` | `from google.adk.workflow import Node` |\n| `@node` decorator | `from google.adk.workflow import node` |\n| `JoinNode` | `from google.adk.workflow import JoinNode` |\n| `RetryConfig` | `from google.adk.workflow import RetryConfig` |\n| `NodeTimeoutError` | `from google.adk.workflow import NodeTimeoutError` |\n| `_ToolNode` (private) | `from google.adk.workflow._tool_node import _ToolNode` |\n\nParallel-worker behavior has no importable class. Set `parallel_worker=True` on\n`@node` or on an `LlmAgent`; the framework wraps it with an internal\n`_ParallelWorker`.\n\n## Events and context\n\n| Component | Import |\n|---|---|\n| `Event` | `from google.adk import Event` |\n| `EventActions` | `from google.adk.events import EventActions` |\n| `RequestInput` | `from google.adk.events import RequestInput` |\n| `Context` | `from google.adk import Context` |\n| `CallbackContext` (alias of `Context`) | `from google.adk.agents.callback_context import CallbackContext` |\n| `ReadonlyContext` | `from google.adk.agents.readonly_context import ReadonlyContext` |\n| `ToolContext` (alias of `Context`) | `from google.adk.tools import ToolContext` |\n\n## Task delegation\n\n| Component | Import |\n|---|---|\n| `FinishTaskTool` | `from google.adk.agents.llm.task._finish_task_tool import FinishTaskTool` |\n| `TaskRequest`, `TaskResult` | `from google.adk.agents.llm.task._task_models import TaskRequest, TaskResult` |\n\nAll three are private. Setting `mode='task'` attaches `FinishTaskTool`\nautomatically — there is no reason to import it in agent code.\n\n## Tools\n\n| Component | Import |\n|---|---|\n| `FunctionTool` | `from google.adk.tools import FunctionTool` |\n| `BaseTool` | `from google.adk.tools import BaseTool` |\n| `BaseToolset` | `from google.adk.tools.base_toolset import BaseToolset` |\n| `LongRunningFunctionTool` | `from google.adk.tools import LongRunningFunctionTool` |\n| `AgentTool` | `from google.adk.tools import AgentTool` |\n| `McpToolset` | `from google.adk.tools.mcp_tool import McpToolset` |\n| `StdioConnectionParams` | `from google.adk.tools.mcp_tool import StdioConnectionParams` |\n| `SseConnectionParams` | `from google.adk.tools.mcp_tool import SseConnectionParams` |\n| `StreamableHTTPConnectionParams` | `from google.adk.tools.mcp_tool import StreamableHTTPConnectionParams` |\n| `OpenAPIToolset`, `RestApiTool` | `from google.adk.tools.openapi_tool import OpenAPIToolset, RestApiTool` |\n\n`MCPToolset` (all caps) still resolves but raises a deprecation warning — use\n`McpToolset`.\n\n## Runner, sessions, app\n\n| Component | Import |\n|---|---|\n| `Runner` | `from google.adk import Runner` |\n| `InMemoryRunner` | `from google.adk.runners import InMemoryRunner` |\n| `InMemorySessionService` | `from google.adk.sessions import InMemorySessionService` |\n| `DatabaseSessionService` | `from google.adk.sessions import DatabaseSessionService` |\n| `VertexAiSessionService` | `from google.adk.sessions import VertexAiSessionService` |\n| `App`, `ResumabilityConfig` | `from google.adk.apps import App, ResumabilityConfig` |\n| `BasePlugin` | `from google.adk.plugins.base_plugin import BasePlugin` |\n\n`DatabaseSessionService` needs the `db` extra; importing it without\n`sqlalchemy` installed raises a \"missing extra\" error rather than\n`ImportError`.\n\n## Live streaming\n\n| Component | Import |\n|---|---|\n| `LiveRequestQueue` | `from google.adk.live import LiveRequestQueue` |\n| `LiveRequest` | `from google.adk.live import LiveRequest` |\n\n## Models\n\n| Component | Import |\n|---|---|\n| `BaseLlm` | `from google.adk.models.base_llm import BaseLlm` |\n| `LiteLlm` | `from google.adk.models.lite_llm import LiteLlm` |\n| `LlmRequest` | `from google.adk.models.llm_request import LlmRequest` |\n| `LlmResponse` | `from google.adk.models.llm_response import LlmResponse` |\n\n## Code executors\n\n| Component | Import |\n|---|---|\n| `BuiltInCodeExecutor` | `from google.adk.code_executors.built_in_code_executor import BuiltInCodeExecutor` |\n\n## google-genai types\n\n| Component | Import |\n|---|---|\n| `types` | `from google.genai import types` |\n| `Content`, `ModelContent`, `Part` | `from google.genai.types import Content, ModelContent, Part` |\n| `GenerateContentConfig` | `from google.genai.types import GenerateContentConfig` |\n\n<!-- File: .agents/skills/adk-agent-builder/references/llm-agent-nodes.md (Tokens: ~1367 | Category: anthropic-skill) -->\n# LLM Agents as Workflow Nodes\n\nPut an `LlmAgent` straight into `edges` and the framework runs it as a node,\nconverting the model's answer into the next node's `node_input`.\n\n```python\nfrom google.adk import Workflow\nfrom google.adk.agents import LlmAgent\n```\n\nThere is no wrapper class to import or subclass — the wrapping is internal.\n\n## Basic usage\n\n```python\nwriter = LlmAgent(\n    name='writer',\n    model='gemini-3.5-flash',\n    instruction=\"Write a short story based on the user's prompt.\",\n)\n\nreviewer = LlmAgent(\n    name='reviewer',\n    model='gemini-3.5-flash',\n    instruction='Review the following story and provide feedback.',\n)\n\nagent = Workflow(name='story_pipeline', edges=[('START', writer, reviewer)])\n```\n\n## What the next node receives\n\n| Agent config | `node_input` for the next node |\n|---|---|\n| no `output_schema` | `str` — the model's text parts, concatenated, thoughts excluded |\n| `output_schema=MyModel` | `dict` — the validated model, `model_dump(exclude_none=True)` |\n\n```python\nclass CodeOutput(BaseModel):\n  code: str\n  language: str\n\n\nwriter = LlmAgent(\n    name='writer',\n    model='gemini-3.5-flash',\n    instruction=\"Write code. Return JSON with 'code' and 'language'.\",\n    output_schema=CodeOutput,\n)\n\n\ndef process_code(node_input: dict) -> str:\n  return node_input['code']\n```\n\nSet `output_schema` whenever the downstream node needs fields rather than prose,\nand always when the agent feeds a `JoinNode` — the join parks partial results in\nsession state, and a raw `types.Content` there breaks a database-backed session\nservice.\n\n### Do not assert on `event.output` for an LLM agent's own event\n\nThe wrapper sets `event.output` internally, but the runner clears it on a copy\nbefore the event reaches your loop, so the same text is not rendered twice.\n`event.output` is therefore `None` when you read the agent's own event out of\n`runner.run_async(...)`. Assert on the downstream node's output, on\n`session.state[output_key]`, or on `event.content.parts[*].text` instead.\n\n## Auto-wrapping defaults\n\nAn `LlmAgent` placed in a workflow gets `mode='single_turn'` if `mode` is unset,\n`rerun_on_resume=True`, and its own content branch so parallel agents do not see\neach other's turns. Change the behavior on the agent, not on the wrapper:\n\n```python\n# single_turn (the default here): isolated, no session history\nclassifier = LlmAgent(\n    name='classifier',\n    model='gemini-3.5-flash',\n    instruction='Classify the input as positive, negative, or neutral.',\n    output_schema=ClassificationResult,\n)\n\n# task: multi-turn within the delegated task, supports human-in-the-loop\ntask_agent = LlmAgent(\n    name='task_agent',\n    model='gemini-3.5-flash',\n    mode='task',\n    instruction='Process the request.',\n)\n```\n\n`mode='chat'` is only legal directly after `START` — see the graph validation\nrules in the advanced-patterns reference.\n\n## Instruction as a function\n\nFor an instruction that depends on more than placeholder substitution, pass a\ncallable taking a `ReadonlyContext`:\n\n```python\nfrom google.adk.agents.readonly_context import ReadonlyContext\n\n\ndef build_instruction(ctx: ReadonlyContext) -> str:\n  agents = ctx.state.get('active_agents', [])\n  return f\"Coordinate these agents: {', '.join(agents)}\"\n\n\nagent = LlmAgent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    instruction=build_instruction,\n)\n```\n\n## Storing output in state\n\n`output_key` writes the agent's output into session state, where a later\ninstruction template or a state-bound function parameter can read it:\n\n```python\nagent = LlmAgent(\n    name='writer',\n    model='gemini-3.5-flash',\n    instruction='Write a draft.',\n    output_key='draft',  # lands in state['draft']\n)\n```\n\n## Controlling history\n\n`include_contents='none'` runs the agent without session history, which is what\nyou want for a classifier or extractor that should judge only the current input:\n\n```python\nagent = LlmAgent(\n    name='stateless',\n    model='gemini-3.5-flash',\n    instruction='Process this input independently.',\n    include_contents='none',\n)\n```\n\n## Tools\n\n```python\ndef search_database(query: str) -> str:\n  \"\"\"Search the database for relevant records.\"\"\"\n  return f'Results for: {query}'\n\n\nagent = LlmAgent(\n    name='assistant',\n    model='gemini-3.5-flash',\n    instruction='Help the user with their request.',\n    tools=[search_database],\n)\n```\n\n`tools` accepts plain callables (wrapped as `FunctionTool`), `BaseTool`\ninstances, and `BaseToolset` instances.\n\n## Generation config\n\nModel-level knobs go in `generate_content_config`. Instructions, tools, and\nresponse schema do **not** — set those as agent fields, or they are ignored.\n\n```python\nfrom google.genai import types\n\nagent = LlmAgent(\n    name='creative',\n    model='gemini-3.5-flash',\n    instruction='Write creative stories.',\n    generate_content_config=types.GenerateContentConfig(\n        temperature=0.9,\n        top_p=0.95,\n        max_output_tokens=2048,\n    ),\n)\n```\n\n## Transfer between agents\n\nAn `LlmAgent` with `sub_agents` can hand control to one of them by reasoning\nabout their `description` fields:\n\n```python\nspecialist = LlmAgent(\n    name='specialist',\n    model='gemini-3.5-flash',\n    description='Handles specialized requests.',\n    instruction='Answer specialized questions.',\n)\n\ncoordinator = LlmAgent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    instruction='Route requests to the specialist when needed.',\n    sub_agents=[specialist],\n)\n```\n\n`disallow_transfer_to_parent=True` and `disallow_transfer_to_peers=True` close\noff the return path and sideways moves respectively.\n\n<!-- File: .agents/skills/adk-agent-builder/references/multi-agent.md (Tokens: ~954 | Category: anthropic-skill) -->\n# Multi-Agent Hierarchies\n\nComposing agents when the composition is a tree of agents rather than a graph of\nnodes.\n\n## Chat transfer\n\nGive a coordinator `sub_agents` and the model decides, from their `description`\nfields, when to hand over. Control passes to the sub-agent and comes back the\nsame way.\n\n```python\nfrom google.adk import Agent\n\nresearcher = Agent(\n    name='researcher',\n    description='Researches topics and reports findings.',\n    instruction='You research topics and provide findings.',\n    tools=[search_tool],\n)\n\nwriter = Agent(\n    name='writer',\n    description='Writes prose from research findings.',\n    instruction='You write content based on research.',\n)\n\nroot_agent = Agent(\n    model='gemini-3.5-flash',\n    name='coordinator',\n    instruction='Delegate research to the researcher and writing to the writer.',\n    sub_agents=[researcher, writer],\n)\n```\n\n- Only the root needs `model=`; a sub-agent without one resolves to the nearest\n  `LlmAgent` ancestor's model.\n- The `description` is the only thing the routing model sees, so make each one\n  say what the agent is *for* and how it differs from its peers. Ambiguous\n  descriptions are the usual cause of the wrong agent picking up a request.\n- `disallow_transfer_to_parent=True` blocks the way back;\n  `disallow_transfer_to_peers=True` blocks sideways moves. Both default to\n  `False`, so a sub-agent can normally return control on its own.\n\nFor schema-validated delegation rather than free-form transfer, set\n`mode='task'` or `mode='single_turn'` on the sub-agent — that is a different\nmechanism with its own tool and completion protocol.\n\n## Orchestration agents\n\n> **Deprecated.** `SequentialAgent`, `ParallelAgent`, and `LoopAgent` are all\n> deprecated in favour of `Workflow` and will be removed in a future version.\n> Build new orchestration as a `Workflow` graph instead — the getting-started\n> reference shows the equivalent edge lists. The one thing they still do that\n> `Workflow` cannot: a `Workflow` cannot yet be used as an `LlmAgent`\n> sub-agent, so reach for these only when you need model-driven transfer into\n> an orchestrated block.\n\nThese three run their `sub_agents` without asking a model what to do next.\n\n```python\nfrom google.adk.agents import LoopAgent, ParallelAgent, SequentialAgent\n\n# One after another\nroot_agent = SequentialAgent(\n    name='pipeline',\n    sub_agents=[step1_agent, step2_agent, step3_agent],\n)\n\n# All at once\nroot_agent = ParallelAgent(\n    name='fan_out',\n    sub_agents=[task_a, task_b, task_c],\n)\n```\n\n`LoopAgent` repeats its sub-agents until one calls `exit_loop` or escalates.\n`max_iterations` is optional; without it the only way out is `exit_loop`, so set\none unless a sub-agent reliably calls the tool.\n\n```python\nfrom google.adk.agents import LoopAgent\nfrom google.adk.tools import exit_loop\n\nchecker = Agent(\n    name='checker',\n    tools=[exit_loop],\n    instruction='Check the result and call exit_loop when it is good enough.',\n)\n\nroot_agent = LoopAgent(\n    name='retry_loop',\n    sub_agents=[worker_agent, checker],\n    max_iterations=5,\n)\n```\n\n## Models\n\nThe built-in default when no agent in the chain sets `model=` is\n`LlmAgent.DEFAULT_MODEL`, currently `'gemini-3.5-flash'`. Override the default\nprocess-wide with `LlmAgent.set_default_model('gemini-3.1-pro-preview')`.\n\nNon-Gemini models go through LiteLLM, with the provider as a prefix:\n\n```python\nfrom google.adk.models.lite_llm import LiteLlm\n\nroot_agent = Agent(model=LiteLlm(model='openai/gpt-4o'), ...)\n```\n\n## Common failures\n\n| Symptom | Cause |\n|---|---|\n| A sub-agent takes over and never gives control back | It has no path home; check `disallow_transfer_to_parent` and say in its instruction when to return |\n| The wrong agent answers | Two `description` fields overlap; sharpen the boundary between them |\n| `ImportError` on agent definitions | Circular imports between per-agent modules; define the tree in one `agent.py` or put shared sub-agents in their own module |\n\n<!-- File: .agents/skills/adk-agent-builder/references/parallel-and-fanout.md (Tokens: ~1121 | Category: anthropic-skill) -->\n# Parallel Execution, Fan-Out, and Fan-In\n\nTwo different things share the word \"parallel\": **fan-out** runs several\n*different* nodes on the same input, and a **parallel worker** runs one node\nacross every item of a list. Mixing them up is the most common failure here.\n\n```python\nfrom google.adk import Agent, Workflow\nfrom google.adk.workflow import JoinNode, node\n```\n\n## Fan-out: several nodes, same input\n\nA tuple of targets sends the same output to each of them concurrently:\n\n```python\nagent = Workflow(\n    name='fan_out',\n    edges=[('START', (analyze_text, translate_text, summarize_text))],\n)\n```\n\n## Fan-in: `JoinNode`\n\nA `JoinNode` waits for every predecessor, then fires once with all their outputs\nin a dict keyed by predecessor node name:\n\n```python\njoin = JoinNode(name='collect_results')\n\nagent = Workflow(\n    name='fan_out_fan_in',\n    edges=[\n        ('START', (analyze_text, translate_text, summarize_text)),\n        ((analyze_text, translate_text, summarize_text), join),\n        (join, final_processor),\n    ],\n)\n\n\ndef final_processor(node_input: dict) -> str:\n  # {'analyze_text': ..., 'translate_text': ..., 'summarize_text': ...}\n  return f\"Combined: {node_input['analyze_text']}\"\n```\n\nWhile waiting, the join holds partial inputs in session state. If a predecessor\nis an `LlmAgent` without `output_schema`, what it holds is a `types.Content`,\nwhich a database-backed session service cannot serialize — set `output_schema`\non every LLM agent feeding a join.\n\n## Multi-trigger: fan-out into one shared successor\n\nPoint several branches at one node with no join and that node runs once per\nbranch:\n\n```python\nagent = Workflow(\n    name='root_agent',\n    input_schema=str,\n    edges=[(\n        'START',\n        (make_uppercase, count_characters, reverse_string),\n        send_message,\n    )],\n)\n```\n\n`send_message` fires three times here. A `JoinNode` in the same position would\nfire once with a merged dict. Pick by whether the downstream work is per-branch\nor needs all branches at once.\n\n## Parallel workers: one node, every item of a list\n\n`parallel_worker=True` wraps a node so it receives a list, runs an instance per\nitem concurrently, and emits a list of results in input order.\n\n```python\n@node(parallel_worker=True)\ndef process_item(node_input: int) -> int:\n  return node_input * 2\n\n\ndef produce_list(node_input: str) -> list:\n  return [1, 2, 3, 4, 5]\n\n\nagent = Workflow(\n    name='parallel_processing',\n    edges=[('START', produce_list, process_item)],\n)\n# process_item emits [2, 4, 6, 8, 10]\n```\n\nThere is no `ParallelWorker` class to import; the wrapper is internal.\n\nBehavior worth knowing:\n\n- A non-list input is wrapped in a one-element list rather than rejected.\n- An empty list produces an empty list, and downstream still fires.\n- `rerun_on_resume` is forced to `True`.\n- `max_parallel_workers=N` caps concurrency; unset means unbounded. It must be\n  at least 1.\n\n### On an agent\n\nSet the flag on the `LlmAgent` itself — each item is handled by a clone:\n\n```python\nexplain_topic = Agent(\n    name='explain_topic',\n    instruction='Explain how this topic relates to \"{topic}\".',\n    output_schema=TopicExplanation,\n    parallel_worker=True,\n)\n\nagent = Workflow(\n    name='parallel_analysis',\n    edges=[('START', process_input, find_related_topics, explain_topic, aggregate)],\n)\n```\n\n### Do not put `parallel_worker=True` on a fan-out branch\n\nFan-out edges already run their targets concurrently. Adding the flag makes the\nbranch expect a list and iterate it; handed a single value it iterates once, and\nhanded `None` it emits nothing — so a downstream join waits forever.\n\n## Diamond\n\n```python\njoin = JoinNode(name='merge')\n\n\ndef combiner(node_input: dict) -> str:\n  return f\"{node_input['branch_a']} + {node_input['branch_b']}\"\n\n\nagent = Workflow(\n    name='diamond',\n    edges=[\n        ('START', splitter),\n        (splitter, (branch_a, branch_b)),\n        ((branch_a, branch_b), join),\n        (join, combiner),\n    ],\n)\n```\n\n## `SequentialAgent` and `ParallelAgent`\n\n> **Deprecated.** Both are deprecated in favour of `Workflow` and will be\n> removed in a future version. Use the edge forms above for new code.\n\nShorthands for the two commonest graphs, when you have agents rather than\nfunctions:\n\n```python\nfrom google.adk.agents import ParallelAgent, SequentialAgent\n\n# START -> writer -> reviewer -> editor\npipeline = SequentialAgent(\n    name='pipeline',\n    sub_agents=[writer_agent, reviewer_agent, editor_agent],\n)\n\n# START -> (analyzer, translator, summarizer)\nparallel = ParallelAgent(\n    name='concurrent',\n    sub_agents=[analyzer_agent, translator_agent, summarizer_agent],\n)\n```\n\n<!-- File: .agents/skills/adk-agent-builder/references/routing-and-conditions.md (Tokens: ~1021 | Category: anthropic-skill) -->\n# Routing and Conditional Branching\n\nA node emits `Event(route=...)`; the edges leaving that node decide which\nsuccessors fire.\n\n## Dict routing — the default form\n\nMap route values to targets in one edge tuple:\n\n```python\nfrom google.adk import Event, Workflow\n\n\ndef classify(node_input: str):\n  route = 'error' if 'error' in node_input else 'success'\n  return Event(output=node_input, route=route)\n\n\nagent = Workflow(\n    name='router',\n    edges=[\n        ('START', classify),\n        (classify, {'success': handle_success, 'error': handle_error}),\n    ],\n)\n```\n\nThe three-tuple form `(classify, handle_success, 'success')` does the same thing\none target at a time. Reach for it only when a single edge must match several\nroutes (see below), since the dict form maps one route to one target.\n\n## Sequence shorthand\n\nA tuple of more than two elements becomes a chain:\n\n```python\nedges = [('START', step_a, step_b, step_c)]\n# equivalent to [('START', step_a), (step_a, step_b), (step_b, step_c)]\n```\n\nChains and dict routing compose:\n\n```python\nedges = [\n    ('START', process_input, classify),\n    (classify, {'approved': send, 'rejected': discard}),\n]\n```\n\n## Route values\n\nA route is a `str`, `bool`, or `int`, or a list of those.\n\n```python\n(decision, {'approve': path_a, 'reject': path_b})   # strings\n(decision, {True: yes_path, False: no_path})        # booleans\n(decision, {0: path_0, 1: path_1})                  # integers\n```\n\n## Default route\n\n`'__DEFAULT__'` (exported as `DEFAULT_ROUTE`) fires when no other route on that\nnode matches:\n\n```python\nedges = [\n    ('START', classify),\n    (classify, {\n        'success': handler_a,\n        'error': handler_b,\n        '__DEFAULT__': fallback_handler,\n    }),\n]\n```\n\nThe default fans out like any other route, so `'__DEFAULT__': (a, b)` triggers\nboth. `'__DEFAULT__'` may not appear inside a list of routes on one edge — give\nit its own edge.\n\n## One edge, several routes\n\nPassing a list matches any value in it:\n\n```python\nedges = [\n    ('START', classifier),\n    (classifier, {'route_z': handler_b}),\n    (classifier, handler_a, ['route_x', 'route_y']),\n]\n```\n\n## Several routes from one node\n\nA node can emit a list of routes to fire multiple branches at once:\n\n```python\ndef fan_out_router(node_input: str):\n  return Event(output=node_input, route=['path_a', 'path_b'])\n\n\nagent = Workflow(\n    name='multi_route',\n    edges=[\n        ('START', fan_out_router),\n        (fan_out_router, {'path_a': branch_a, 'path_b': branch_b}),\n    ],\n)\n```\n\n## Self-loop\n\n```python\ndef guess_number(target_number: int):\n  guess = random.randint(0, 10)\n  yield Event(message=f'Guessing {guess}...')\n  if guess == target_number:\n    yield Event(message='Correct!')\n  else:\n    yield Event(route='guessed_wrong')\n\n\nagent = Workflow(\n    name='root_agent',\n    edges=[\n        ('START', validate_input, guess_number),\n        (guess_number, {'guessed_wrong': guess_number}),\n    ],\n)\n```\n\n## Revision loop\n\n```python\nedges = [\n    ('START', process_input, draft_email, human_review),\n    (human_review, {\n        'revise': draft_email,\n        'approved': send,\n        'rejected': discard,\n    }),\n]\n```\n\n## Constraints the graph validator enforces\n\n- **A cycle needs at least one routed edge.** An entirely unconditional cycle is\n  rejected at construction time, because nothing could ever break out of it.\n- **Edges leaving `START` may not carry a route.** `START` never runs, so it\n  never emits one.\n- **No two edges may share a source and a target**, even with different routes.\n  To reach one destination from both a named route and `__DEFAULT__`, point the\n  default at a thin wrapper function.\n\n## Unrouted edges always fire\n\nAn edge with no route fires on every output event from its source, whatever\nroute that event carries. So if a node routes at all, give *every* one of its\noutgoing edges a route — otherwise the unrouted one fires alongside the branch\nyou selected.\n\n```python\nedges = [\n    ('START', node_a),  # unconditional\n    (node_a, node_b),   # fires on every node_a output\n]\n```\n\n<!-- File: .agents/skills/adk-agent-builder/references/session-and-state.md (Tokens: ~668 | Category: anthropic-skill) -->\n# Sessions, Artifacts, and Memory\n\nWhere data lives beyond a single node: the session store, binary artifacts, and\nlong-term memory.\n\n## State key scopes\n\nA prefix on the key decides how far the value travels and how long it lives.\n\n| Key form | Scope |\n|---|---|\n| `key` | This session |\n| `app:key` | The whole app — shared across every session and user |\n| `user:key` | This user, across their sessions |\n| `temp:key` | This invocation only; never persisted |\n\nA `state_schema` on a node validates writes, but prefixed keys bypass that\nvalidation.\n\nMutate, do not rebind — `state['key'] = value` records a delta, whereas\n`state = {'key': value}` just rebinds a local name and is lost.\n\n```python\ndef my_tool(tool_context: ToolContext):\n  tool_context.state['user_name'] = 'Alice'\n  tool_context.state['app:feature_flag'] = True\n```\n\nIn parallel branches, two nodes writing the same key race. Give each branch its\nown key, or write to a shared `app:`-scoped key deliberately.\n\n## Session services\n\n| Service | Use for | Import |\n|---|---|---|\n| `InMemorySessionService` | local development and tests | `from google.adk.sessions import InMemorySessionService` |\n| `DatabaseSessionService` | production on SQLite or PostgreSQL | `from google.adk.sessions import DatabaseSessionService` |\n| `VertexAiSessionService` | Vertex AI Agent Engine | `from google.adk.sessions import VertexAiSessionService` |\n\n```python\nfrom google.adk import Runner\nfrom google.adk.sessions import InMemorySessionService\n\nrunner = Runner(\n    agent=root_agent,\n    app_name='my_app',\n    session_service=InMemorySessionService(),\n)\n```\n\n`DatabaseSessionService` needs the `db` extra (`pip install \"google-adk[db]\"`).\nIt also serializes everything you put in state and in a `JoinNode`'s parked\ninputs, so a non-JSON-serializable value that works in memory fails here.\n\n## Artifacts\n\nArtifacts hold bytes — images, files, generated documents — keyed by filename\nand versioned per session.\n\n```python\nfrom google.genai import types\n\n\nasync def save_chart(tool_context: ToolContext):\n  part = types.Part.from_bytes(data=generate_chart(), mime_type='image/png')\n  version = await tool_context.save_artifact('chart.png', part)\n\n\nasync def get_chart(tool_context: ToolContext):\n  part = await tool_context.load_artifact('chart.png')\n  return part.inline_data.data\n```\n\n## Memory\n\nMemory is recall across sessions, as opposed to state, which is recall within\none.\n\n```python\nfrom google.adk.memory.in_memory_memory_service import InMemoryMemoryService\n\nrunner = Runner(\n    agent=root_agent,\n    app_name='my_app',\n    session_service=InMemorySessionService(),\n    memory_service=InMemoryMemoryService(),\n)\n```\n\nAgents reach it through the `load_memory` and `preload_memory` tools, or\ndirectly with `await ctx.search_memory(query)`.\n\n<!-- File: .agents/skills/adk-agent-builder/references/state-and-events.md (Tokens: ~1156 | Category: anthropic-skill) -->\n# Context and Events\n\nThe two objects a node touches: `Context`, which is how it reads its\nsurroundings, and `Event`, which is how it says anything.\n\n```python\nfrom google.adk import Context, Event\n```\n\n## Getting a `Context`\n\nDeclare a parameter named `ctx`. Nodes that do not need it can omit it.\n\n```python\ndef my_node(ctx: Context, node_input: str) -> str:\n  value = ctx.state.get('key', 'default')\n  return f'{ctx.session.id}: {value}'\n```\n\n## Context properties\n\nAvailable everywhere (a `Context` is also what a callback and a tool receive —\n`CallbackContext` and `ToolContext` are aliases for this same class):\n\n| Property | Type | Notes |\n|---|---|---|\n| `state` | `State` | Delta-aware session state; reads and writes like a dict |\n| `session` | `Session` | Current session, with the workflow's local events merged in |\n| `invocation_id` | `str` | This invocation |\n| `user_id` | `str` | Read-only |\n| `user_content` | `types.Content \\| None` | The message that started the invocation |\n| `agent_name` | `str` | Agent currently running |\n| `run_config` | `RunConfig \\| None` | Read-only |\n| `actions` | `EventActions` | State and artifact deltas being accumulated |\n| `branch` | `str \\| None` | Event-isolation branch |\n| `function_call_id` | `str \\| None` | Set when running as a tool |\n\nMeaningful only inside a workflow node:\n\n| Property | Type | Notes |\n|---|---|---|\n| `node` | `BaseNode \\| None` | The node being executed |\n| `node_path` | `str` | Full path, e.g. `'WorkflowA/node1'` |\n| `run_id` | `str` | This node-run, e.g. `'1'`, `'2'` |\n| `attempt_count` | `int` | 1 on the first try, higher on a retry |\n| `resume_inputs` | `dict[str, Any]` | Human-in-the-loop answers, keyed by `interrupt_id` |\n| `error`, `error_node_path` | `Exception \\| None`, `str` | Set after a node fails |\n\n## Context methods\n\n| Method | Purpose |\n|---|---|\n| `await run_node(node, node_input=None, *, use_as_output=False, run_id=None, use_sub_branch=False, override_branch=None)` | Run a node dynamically; the caller needs `rerun_on_resume=True` |\n| `await save_artifact(filename, part)` / `await load_artifact(filename)` | Session artifacts |\n| `await search_memory(query)` | Long-term memory lookup |\n| `get_auth_response(auth_config)` / `request_credential(auth_config)` | Credentials |\n| `get_invocation_context()` | Escape hatch to the underlying `InvocationContext` |\n\n## Parameters resolved from state\n\nAny function-node parameter that is not `ctx` or `node_input` is looked up in\n`ctx.state` by name, and coerced to its annotation. If the key is absent, the\nparameter's default is used.\n\n```python\n# With state {'user_name': 'Alice', 'threshold': 0.5}\ndef my_node(node_input: str, user_name: str, threshold: float) -> str:\n  return f'{user_name}: {node_input} (threshold={threshold})'\n```\n\nResolution order: `ctx` → `node_input` → `ctx.state[name]` → default.\n\n## Event fields\n\n`Event` extends `LlmResponse`. Three of its constructor arguments are\nconveniences that write somewhere else:\n\n| Constructor argument | Where it lands |\n|---|---|\n| `output=` | `event.output` — data for the next node |\n| `message=` | `event.content` — what the UI renders |\n| `state=` | `event.actions.state_delta` |\n| `route=` | `event.actions.route` |\n\n`message` and `content` are mutually exclusive; passing both raises. `message`\naccepts a string, a `types.Part`, a list of parts, or a `types.Content`.\n\nOther fields you will read: `author`, `content`, `partial`, `branch`,\n`node_info` (with `node_info.path` identifying the emitting node), and\n`is_final_response()`.\n\n## Emitting user-visible messages\n\n```python\nyield Event(message='Processing step 1...')\n\n# Multimodal\nfrom google.genai import types\n\nyield Event(message=[\n    types.Part.from_text(text='Here is the result:'),\n    types.Part.from_bytes(data=image_bytes, mime_type='image/png'),\n])\n\n# Streaming chunks, then the real output\nasync def verbose_node(ctx: Context, node_input: str):\n  yield Event(message='Processing step 1...', partial=True)\n  yield Event(message='Processing step 2...', partial=True)\n  yield Event(output='final result')\n```\n\n## What the workflow itself outputs\n\nAfter every node settles, the workflow emits one more event of its own. Its\nvalue comes from the terminal nodes — those with no outgoing edges. That event\nis authored by the workflow, with `node_info.path` set to the workflow's own\npath, which is why filtering test assertions on `event.author` picks up the\nwrapper rather than the node you meant.\n\n<!-- File: .agents/skills/adk-agent-builder/references/task-mode.md (Tokens: ~1502 | Category: anthropic-skill) -->\n# Task Delegation (`mode='task'` / `mode='single_turn'`)\n\nHand a sub-agent a schema-validated job and get a schema-validated answer back,\ninstead of transferring the whole conversation to it.\n\n## The three modes\n\n`LlmAgent.mode` is `'chat'`, `'task'`, `'single_turn'`, or unset. Unset means\n`'chat'` when the agent is a sub-agent, `'single_turn'` when it is a workflow\nnode.\n\n| Mode | How the parent reaches it | User interaction | How it finishes |\n|---|---|---|---|\n| `chat` | the `transfer_to_agent` tool | full conversation | transfers back |\n| `task` | a tool named after the sub-agent | can ask the user for clarification | calls `finish_task` |\n| `single_turn` | a tool named after the sub-agent | none — told no reply is coming | calls `finish_task` |\n\nThe delegation tool takes the sub-agent's **`name`** verbatim. An agent called\n`researcher` is exposed to the coordinator as a tool called `researcher`, and\nits `description` becomes the tool description, so write the description for a\nmodel deciding whether to call it.\n\n## Task mode\n\n```python\nfrom google.adk import Agent\nfrom pydantic import BaseModel\n\n\nclass ResearchInput(BaseModel):\n  topic: str\n  depth: str = 'standard'\n\n\nclass ResearchOutput(BaseModel):\n  summary: str\n  key_findings: str\n  confidence: str\n\n\ndef search_web(query: str) -> str:\n  \"\"\"Search the web for information.\"\"\"\n  return f'Results for \"{query}\": ...'\n\n\nresearcher = Agent(\n    name='researcher',\n    mode='task',\n    input_schema=ResearchInput,\n    output_schema=ResearchOutput,\n    description='Researches topics using web search and analysis.',\n    instruction=(\n        'Research the given topic with search_web. If the user asks for'\n        ' changes, adjust. When done, call finish_task with summary,'\n        ' key_findings, and confidence.'\n    ),\n    tools=[search_web],\n)\n\nroot_agent = Agent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    sub_agents=[researcher],\n    instruction=(\n        'When the user asks for research, call the researcher tool. Summarize'\n        ' its result for the user.'\n    ),\n)\n```\n\nSequence: the coordinator calls the `researcher` tool with structured input; the\nresearcher works, possibly talking to the user; the researcher calls\n`finish_task` with structured output; the coordinator gets the result.\n\n## Single-turn mode\n\nSame shape, no conversation. The framework appends a nudge to the sub-agent's\ninput telling it no further user replies will arrive, so it must finish from the\ninput alone.\n\n```python\nclass SummaryOutput(BaseModel):\n  summary: str\n  word_count: int\n\n\nsummarizer = Agent(\n    name='summarizer',\n    mode='single_turn',\n    output_schema=SummaryOutput,\n    description='Summarizes documents autonomously.',\n    instruction='Summarize the document with extract_text, then finish_task.',\n    tools=[extract_text],\n)\n\nroot_agent = Agent(\n    name='coordinator',\n    model='gemini-3.5-flash',\n    sub_agents=[summarizer],\n    instruction='Delegate summarization to the summarizer tool.',\n)\n```\n\n## Schemas\n\n`input_schema` types the delegation tool's parameters; `output_schema` types\n`finish_task`'s parameters. Both are optional.\n\n```python\nagent = Agent(\n    name='worker',\n    mode='task',\n    input_schema=TaskInput,    # validates the delegation call\n    output_schema=TaskOutput,  # validates the finish_task call\n    ...\n)\n```\n\nWithout them the defaults are a single string each:\n\n```python\n# delegation tool parameters\n{'request': str}   # \"Detailed instructions or context for the task sub-agent.\"\n\n# finish_task parameters\n{'result': str}\n```\n\nA schema violation is not fatal — `finish_task` returns a validation-error\nmessage and the model gets to retry.\n\n## `finish_task`\n\n`mode='task'` attaches a tool called `finish_task` to the sub-agent\nautomatically, and injects an instruction telling the model to complete the work\nbefore calling it. Its parameters come from `output_schema`, or `{'result':\nstr}` when there is none. There is nothing to import or register.\n\n## Mixed modes under one coordinator\n\n```python\nflight_searcher = Agent(\n    name='flight_searcher',\n    mode='task',                # interactive: can discuss options\n    input_schema=FlightSearchInput,\n    output_schema=FlightSearchOutput,\n    description='Searches and books flights interactively.',\n    instruction='Search flights, discuss with the user, then finish_task.',\n    tools=[search_flights, book_flight],\n)\n\nweather_checker = Agent(\n    name='weather_checker',\n    mode='single_turn',         # autonomous\n    output_schema=WeatherOutput,\n    description='Checks weather for a destination.',\n    instruction='Check the weather and call finish_task.',\n    tools=[get_weather],\n)\n\nroot_agent = Agent(\n    name='travel_planner',\n    model='gemini-3.5-flash',\n    sub_agents=[flight_searcher, weather_checker],\n    instruction=(\n        'Plan trips. Use weather_checker for weather and flight_searcher for'\n        ' booking.'\n    ),\n)\n```\n\n## Rules worth knowing\n\n- Only the coordinator needs `model=`; sub-agents inherit it from the nearest\n  `LlmAgent` ancestor.\n- Every delegating sub-agent needs a `description` — it is the entire tool\n  description the coordinator's model sees.\n- The delegation tool is marked as deferring its response and its description\n  tells the model **not** to call it in parallel with other tools. Do not build\n  a prompt that asks for several delegations in one turn.\n- A `mode='chat'` sub-agent gets neither a delegation tool nor `finish_task`; it\n  stays a `transfer_to_agent` target.\n\n## Task mode versus chat transfer\n\n| | chat (`transfer_to_agent`) | task / single_turn |\n|---|---|---|\n| input | free-form conversation | schema-validated |\n| output | free-form conversation | schema-validated |\n| control returns when | the agent transfers back | the agent calls `finish_task` |\n| user interaction | full chat | `task`: multi-turn, `single_turn`: none |\n\n## Where the code lives\n\n| Component | File |\n|---|---|\n| `mode`, `input_schema`, `output_schema` | `src/google/adk/agents/llm_agent.py` |\n| delegation tools, default input schema | `src/google/adk/tools/agent_tool.py` |\n| `finish_task` | `src/google/adk/agents/llm/task/_finish_task_tool.py` |\n| `TaskRequest`, `TaskResult` | `src/google/adk/agents/llm/task/_task_models.py` |\n\n<!-- File: .agents/skills/adk-agent-builder/references/testing.md (Tokens: ~1898 | Category: anthropic-skill) -->\n# Testing Workflow Agents\n\n`pytest` plus `InMemoryRunner`. Everything below uses the published\n`google-adk` package — no test-internal helpers.\n\n## Setup\n\n```bash\nuv add \"google-adk>=2.0\" pytest pytest-asyncio\n```\n\n```toml\n[tool.pytest.ini_options]\nasyncio_mode = \"auto\"\n```\n\n`asyncio_mode = \"auto\"` saves marking every test `@pytest.mark.asyncio`. Omit it\nif you prefer explicit marks.\n\n## Imports\n\n```python\nimport pytest\nfrom google.adk import Workflow\nfrom google.adk.agents import LlmAgent\nfrom google.adk.apps import App, ResumabilityConfig\nfrom google.adk.events import Event, RequestInput\nfrom google.adk.runners import InMemoryRunner\nfrom google.genai import types\n```\n\n## Two helpers worth having\n\n```python\nasync def run(agent, text='hi', app_name='test_app'):\n    runner = InMemoryRunner(agent=agent, app_name=app_name)\n    session = await runner.session_service.create_session(\n        app_name=app_name, user_id='u1'\n    )\n    msg = types.Content(role='user', parts=[types.Part(text=text)])\n    events = []\n    async for event in runner.run_async(\n        user_id='u1', session_id=session.id, new_message=msg\n    ):\n        events.append(event)\n    return runner, session, events\n\n\ndef node_name(event):\n    \"\"\"'workflow@1/step@1' -> 'step'.\"\"\"\n    if not event.node_info:\n        return None\n    return event.node_info.path.split('/')[-1].split('@')[0]\n```\n\n`event.author` is the *enclosing workflow's* name, not the node's, so filtering\non it silently matches the wrong events. `event.node_info.path` is the one that\nidentifies the node.\n\n## A workflow\n\n```python\nasync def test_simple_workflow():\n    def step_one(node_input: str) -> str:\n        return 'step 1 done'\n\n    def step_two(node_input: str) -> str:\n        return 'step 2 done'\n\n    agent = Workflow(\n        name='test_workflow', edges=[('START', step_one, step_two)]\n    )\n\n    _, _, events = await run(agent)\n    final = [e for e in events if node_name(e) == 'step_two' and e.output][-1]\n    assert final.output == 'step 2 done'\n```\n\n## Routing\n\n```python\nasync def test_routing():\n    def router(node_input: str):\n        route = 'error' if 'error' in node_input else 'success'\n        return Event(output=node_input, route=route)\n\n    agent = Workflow(\n        name='routing_test',\n        edges=[\n            ('START', router),\n            (router, {'success': success_handler, 'error': error_handler}),\n        ],\n    )\n\n    _, _, ok = await run(agent, text='all good')\n    assert any(node_name(e) == 'success_handler' for e in ok)\n\n    _, _, err = await run(agent, text='error case')\n    assert any(node_name(e) == 'error_handler' for e in err)\n```\n\n## Pause and resume\n\n```python\nasync def test_hitl_workflow():\n    async def ask_user(ctx, node_input: str):\n        yield RequestInput(message='Approve?', interrupt_id='ask')\n\n    def after_approval(node_input) -> str:\n        return f'Approved: {node_input}'\n\n    agent = Workflow(\n        name='hitl_test', edges=[('START', ask_user, after_approval)]\n    )\n    app = App(\n        name='hitl_test_app',\n        root_agent=agent,\n        resumability_config=ResumabilityConfig(is_resumable=True),\n    )\n    runner = InMemoryRunner(app=app)\n    session = await runner.session_service.create_session(\n        app_name='hitl_test_app', user_id='u1'\n    )\n\n    msg = types.Content(role='user', parts=[types.Part(text='start')])\n    paused = [\n        e\n        async for e in runner.run_async(\n            user_id='u1', session_id=session.id, new_message=msg\n        )\n    ]\n    fc_events = [e for e in paused if e.get_function_calls()]\n    assert fc_events, 'expected an interrupt function call'\n    fc = fc_events[-1].get_function_calls()[0]\n\n    response = types.Content(\n        role='user',\n        parts=[types.Part(function_response=types.FunctionResponse(\n            id=fc.id, name=fc.name, response={'result': 'yes'},\n        ))],\n    )\n    resumed = [\n        e\n        async for e in runner.run_async(\n            user_id='u1', session_id=session.id, new_message=response\n        )\n    ]\n    final = [e for e in resumed if node_name(e) == 'after_approval'][-1]\n    assert final.output == 'Approved: yes'\n```\n\n## State\n\nPrefer reading the session back after the run over inspecting state mid-flight.\n\n```python\nasync def test_state_management():\n    def writer(node_input: str):\n        return Event(output=node_input, state={'counter': 1})\n\n    def reader(ctx, node_input):\n        return f\"counter={ctx.state['counter']}\"\n\n    agent = Workflow(name='state_test', edges=[('START', writer, reader)])\n\n    runner, session, events = await run(agent)\n    final = [e for e in events if node_name(e) == 'reader' and e.output][-1]\n    assert final.output == 'counter=1'\n\n    after = await runner.session_service.get_session(\n        app_name='test_app', user_id='u1', session_id=session.id\n    )\n    assert after.state['counter'] == 1\n```\n\n## Parallel workers\n\n```python\nfrom google.adk.workflow import node\n\n\nasync def test_parallel_worker():\n    def produce(node_input: str) -> list:\n        return [1, 2, 3]\n\n    @node(parallel_worker=True)\n    def double(node_input: int) -> int:\n        return node_input * 2\n\n    def collect(node_input: list) -> str:\n        return f'results: {node_input}'\n\n    agent = Workflow(\n        name='parallel_test', edges=[('START', produce, double, collect)]\n    )\n\n    _, _, events = await run(agent)\n    final = [e for e in events if node_name(e) == 'collect' and e.output][-1]\n    assert final.output == 'results: [2, 4, 6]'\n```\n\n## Faking the model\n\n`BaseLlm` has exactly one abstract method, so a fake is short:\n\n```python\nfrom google.adk.models.base_llm import BaseLlm\nfrom google.adk.models.llm_response import LlmResponse\n\n\nclass FakeLlm(BaseLlm):\n\n    def __init__(self, *, responses: list[str]):\n        super().__init__(model='fake')\n        self._responses = list(responses)\n\n    async def generate_content_async(self, llm_request, stream=False):\n        yield LlmResponse(content=types.Content(\n            role='model', parts=[types.Part(text=self._responses.pop(0))],\n        ))\n\n\nasync def test_llm_agent_with_fake():\n    agent = LlmAgent(name='x', model=FakeLlm(responses=['ok']), instruction='Help.')\n    _, _, events = await run(agent, text='hi')\n    assert events[-1].content.parts[0].text == 'ok'\n```\n\nTo assert on the request shape instead, `monkeypatch` the agent's\n`canonical_model.generate_content_async`.\n\nDo **not** assert on `event.output` for an LLM agent's own event — the runner\nclears it before you see it. Assert on the downstream node's output, on\n`session.state[output_key]`, or on `event.content.parts[*].text`.\n\n## Tests that hit a real model\n\n```python\nimport os\n\nimport pytest\n\n\n@pytest.fixture(scope='session', autouse=True)\ndef adk_env():\n    if 'GOOGLE_API_KEY' not in os.environ:\n        pytest.skip('GOOGLE_API_KEY not set')\n    os.environ.setdefault('GOOGLE_GENAI_USE_ENTERPRISE', 'FALSE')\n\n\n@pytest.mark.integration\nasync def test_real_model():\n    ...\n```\n\n`pytest -m integration` runs them; `pytest -m \"not integration\"` skips them.\n\n## Habits that avoid flakes\n\n- One `InMemoryRunner` and one session per test — runners carry state.\n- A unique `app_name` per test (`request.node.name` works) so parallel pytest\n  workers do not collide.\n- `event.is_final_response()` filters for \"the agent's last word\".\n- Any LLM agent feeding a `JoinNode` needs `output_schema=`, or the join buffer\n  fails to serialize under `DatabaseSessionService`.\n- `pytest -xvs` while iterating: stop at the first failure, verbose, show prints.\n\n<!-- File: .agents/skills/adk-agent-builder/references/tool-catalog.md (Tokens: ~1540 | Category: anthropic-skill) -->\n# Tool Catalog\n\nEvery way to give an agent a capability, from a plain Python function to a whole\nremote API.\n\n## Python functions\n\nPass callables straight to `tools=`. The name, docstring, and type hints become\nthe schema the model sees, so all three are load-bearing — an undocumented or\nuntyped parameter is invisible to the model.\n\n```python\ndef get_weather(city: str, unit: str = 'celsius') -> str:\n  \"\"\"Get the current weather for a city.\n\n  Args:\n    city: The city name to look up.\n    unit: Temperature unit, 'celsius' or 'fahrenheit'.\n\n  Returns:\n    A string with the weather information.\n  \"\"\"\n  return f'Sunny, 22 degrees {unit} in {city}'\n\n\nroot_agent = Agent(tools=[get_weather], ...)\n```\n\nSync and async both work.\n\n### Getting the context inside a tool\n\nAdd a parameter annotated with `ToolContext` (or `Context` / `CallbackContext` —\nthey are all the same class). It is matched **by annotation**, not by name, and\nexcluded from the schema the model sees. A parameter literally named\n`tool_context` is used as a fallback when no annotation matches.\n\n```python\nfrom google.adk.tools import ToolContext\n\n\nasync def my_tool(query: str, tool_context: ToolContext) -> str:\n  tool_context.state['key'] = 'value'\n  await tool_context.save_artifact('f.txt', part)\n  results = await tool_context.search_memory('q')\n  return 'done'\n```\n\nA parameter named `input_stream` is also excluded, for streaming tools.\n\n## Built-in tools\n\n| Tool | Import from `google.adk.tools` |\n|---|---|\n| `google_search` | Google Search grounding |\n| `url_context` | Fetch and ground on URLs in the prompt |\n| `load_artifacts` | Pull session artifacts into context |\n| `load_memory` / `preload_memory` | Query long-term memory |\n| `exit_loop` | Break out of a `LoopAgent` |\n| `transfer_to_agent` | Hand control to another agent |\n| `get_user_choice` | Ask the user to pick an option |\n| `google_maps_grounding`, `enterprise_web_search` | Other grounding sources |\n\n## Long-running tools\n\n`LongRunningFunctionTool` returns its result asynchronously against the original\n`function_call_id`, which is how an agent pauses for a human.\n\n```python\nfrom google.adk.tools import LongRunningFunctionTool\n\n\ndef approve_expense(amount: float) -> dict:\n  \"\"\"Submit an expense for approval.\"\"\"\n  return {'status': 'pending', 'id': 'exp-123'}\n\n\nroot_agent = Agent(tools=[LongRunningFunctionTool(approve_expense)], ...)\n```\n\n## Workflows and `@node` functions as tools\n\nPass a `Workflow` (with `input_schema` set to a Pydantic `BaseModel`) or a\n`@node`-decorated function straight into `tools=`. The agent runs it in an\nisolated sub-branch (`{tool_name}@{function_call_id}`) and receives the\nterminal output.\n\nSet `ctx.actions.skip_summarization = True` inside the node when its output\nshould be emitted directly as the final user-visible text response without a\nfollow-up LLM summarization turn:\n\n```python\nfrom google.adk import Agent, Context\nfrom google.adk.workflow import node\n\n\n@node\ndef generate_report(project: str, ctx: Context) -> str:\n  \"\"\"Generate a status report for a project.\"\"\"\n  ctx.actions.skip_summarization = True\n  return f'Report for {project}: Ready'\n\n\nroot_agent = Agent(tools=[generate_report], ...)\n```\n\n## MCP servers\n\n```python\nfrom google.adk.tools.mcp_tool import McpToolset, StdioConnectionParams\nfrom mcp import StdioServerParameters\n\nroot_agent = Agent(\n    tools=[\n        McpToolset(\n            connection_params=StdioConnectionParams(\n                server_params=StdioServerParameters(\n                    command='npx',\n                    args=['-y', '@modelcontextprotocol/server-filesystem', '/path'],\n                ),\n                timeout=5,\n            ),\n            tool_filter=['read_file', 'list_directory'],\n        )\n    ],\n    ...\n)\n```\n\nConnection classes: `StdioConnectionParams`, `SseConnectionParams`,\n`StreamableHTTPConnectionParams`.\n\nNeeds `pip install mcp`. `StdioServerParameters` comes from that package, not\nfrom ADK. Use `McpToolset`; the all-caps `MCPToolset` still resolves but warns.\n\n## OpenAPI specs\n\n```python\nfrom google.adk.tools.openapi_tool import OpenAPIToolset\n\ntoolset = OpenAPIToolset(spec_str=open('openapi.yaml').read(), spec_str_type='yaml')\nroot_agent = Agent(tools=[toolset], ...)\n```\n\n`spec_str_type` is `'json'` (the default) or `'yaml'`. Pass `spec_dict=` instead\nto skip parsing. `RestApiTool` from the same module wraps a single endpoint.\n\n## Google API toolsets\n\nGenerated from Google's API discovery documents. `BigQueryToolset`,\n`CalendarToolset`, and their siblings all take the same arguments.\n\n```python\nfrom google.adk.tools.google_api_tool.google_api_toolsets import BigQueryToolset\n\nbigquery = BigQueryToolset(\n    client_id='...',\n    client_secret='...',\n    tool_filter=['bigquery_datasets_list'],\n)\n```\n\nAlso accepted: `service_account=` instead of the OAuth pair, and\n`tool_name_prefix=` to namespace the generated tool names.\n\n## Code execution\n\nThe code executor is its own agent field, not a tool.\n\n```python\nfrom google.adk.code_executors.built_in_code_executor import BuiltInCodeExecutor\n\nroot_agent = Agent(code_executor=BuiltInCodeExecutor(), ...)\n```\n\n## Custom `BaseTool`\n\n```python\nfrom google.adk.tools import BaseTool\nfrom google.genai import types\n\n\nclass MyTool(BaseTool):\n\n  def __init__(self):\n    super().__init__(name='my_tool', description='Does something.')\n\n  def _get_declaration(self):\n    return types.FunctionDeclaration(\n        name=self.name,\n        description=self.description,\n        parameters_json_schema={\n            'type': 'object',\n            'properties': {'param': {'type': 'string'}},\n            'required': ['param'],\n        },\n    )\n\n  async def run_async(self, *, args, tool_context):\n    return {'result': args['param']}\n```\n\n## Custom `BaseToolset`\n\nA toolset supplies tools dynamically, so the set can depend on context.\n\n```python\nfrom google.adk.tools.base_toolset import BaseToolset\n\n\nclass MyToolset(BaseToolset):\n\n  def __init__(self):\n    super().__init__(tool_filter=None, tool_name_prefix='my')\n\n  async def get_tools(self, readonly_context=None):\n    return [ToolA(), ToolB()]\n\n  async def process_llm_request(self, *, tool_context, llm_request):\n    llm_request.append_instructions(['Custom instruction'])\n```\n\n`tool_filter` is a list of tool names or a `ToolPredicate` callable;\n`tool_name_prefix` renames every tool the toolset returns, which is how you keep\ntwo toolsets from colliding.\n\n<!-- File: .agents/skills/adk-agent-builder/SKILL.md (Tokens: ~880 | Category: anthropic-skill) -->\n---\nname: adk-agent-builder\ndescription: >-\n  Builds ADK (Agent Development Kit) Python agents: LLM agents with tools,\n  graph workflows of function and agent nodes, conditional routing, fan-out and\n  join, schema-validated delegation between agents, human-in-the-loop pauses,\n  and pytest coverage for all of it. Use when asked to create an agent or a\n  workflow, add a tool to one, branch or loop between nodes, run steps in\n  parallel, pause for user approval, or test an agent. Don't use for explaining\n  how ADK works internally or designing its core components (use\n  `adk-architecture`), for an agent that already runs but misbehaves (use\n  `adk-debug`), for authoring a sample under `contributing/` (use\n  `adk-sample-creator`), or for naming, typing, and formatting conventions (use\n  `adk-style`).\n---\n\n# ADK Agent Builder\n\nRead only the reference that matches the task. Loading the whole tree costs\ncontext and buries the part that matters.\n\nEvery API below was checked against `google-adk` 2.6.2. If a symbol is missing\nat runtime, read the source under `src/google/adk/` rather than guessing a\nneighbouring name.\n\n## Start here\n\n| Task | Reference |\n|---|---|\n| First agent, environment, `adk` CLI | [getting-started.md](references/getting-started.md) |\n| Which import path is the canonical one | [import-paths.md](references/import-paths.md) |\n| The rules that cause most runtime failures | [best-practices.md](references/best-practices.md) |\n\n## Building blocks\n\n- [tool-catalog.md](references/tool-catalog.md) — function tools, MCP, OpenAPI,\n  Google API toolsets, built-in tools, custom `BaseTool` and `BaseToolset`.\n- [function-nodes.md](references/function-nodes.md) — plain functions as nodes:\n  parameter resolution, generators, `node_input` typing rules.\n- [llm-agent-nodes.md](references/llm-agent-nodes.md) — an `LlmAgent` used as a\n  workflow node: output types, instruction templates, `output_schema`,\n  auto-wrapping behavior.\n- [task-mode.md](references/task-mode.md) — `mode='task'` and\n  `mode='single_turn'` delegation with schema-validated input and output.\n\n## Graph orchestration\n\n- [routing-and-conditions.md](references/routing-and-conditions.md) — routed\n  edges, dict routing maps, default routes, self-loops, revision loops.\n- [parallel-and-fanout.md](references/parallel-and-fanout.md) — fan-out edges,\n  `JoinNode` fan-in, `parallel_worker=True` list processing.\n- [dynamic-nodes.md](references/dynamic-nodes.md) — scheduling nodes at runtime\n  with `ctx.run_node()` and imperative workflow construction.\n- [human-in-the-loop.md](references/human-in-the-loop.md) — `RequestInput`,\n  resume behavior, resumable vs replayed sessions.\n- [advanced-patterns.md](references/advanced-patterns.md) — nested workflows,\n  retries, custom `BaseNode` subclasses, graph validation rules.\n- [multi-agent.md](references/multi-agent.md) — chat-transfer hierarchies, and\n  the deprecated `SequentialAgent` / `LoopAgent` / `ParallelAgent` shells that\n  `Workflow` replaces.\n\n## Runtime and verification\n\n- [state-and-events.md](references/state-and-events.md) — the `Context` object,\n  `Event` fields, and how state flows between nodes.\n- [session-and-state.md](references/session-and-state.md) — session services,\n  artifacts, memory, and state key scoping.\n- [callbacks-and-plugins.md](references/callbacks-and-plugins.md) — the six\n  agent callbacks and app-level plugins.\n- [testing.md](references/testing.md) — `pytest` with `InMemoryRunner`, faking a\n  model, asserting on node output.\n\n<!-- File: .agents/skills/adk-architecture/references/api-principles.md (Tokens: ~595 | Category: anthropic-skill) -->\n# API Principles\n\nGuidelines for designing and maintaining the ADK public API surface.\n\n## Public API Surface\n\nThe public API surface of ADK includes:\n- All public classes, methods, and functions in the `google.adk` namespace.\n- The names, required parameters, and expected behavior of all built-in Tools.\n- The structure and schema of persisted data (Sessions, Memory, Evaluation datasets).\n- The JSON request/response format of the ADK API server.\n- The command-line interface (CLI) commands, arguments, and flags.\n- The expected file structure for agent definitions (e.g., `agent.py` convention loaded by CLI).\n\n## Design Principles\n\n### 1. Stability and Backward Compatibility\n- ADK adheres to Semantic Versioning 2.0.0.\n- Any change that forces a developer to alter their existing code to upgrade is a **breaking change** and necessitates a MAJOR version bump.\n- Avoid breaking changes whenever possible by using optional parameters and deprecation cycles.\n- **Session, Artifact, and Memory Service Changes**:\n  Adding any new field to `Session`, `ArtifactVersion`, `MemoryEntry`, or to their associated service base classes (`BaseSessionService`, `BaseArtifactService`, `BaseMemoryService`) and database schemas is strictly considered a **breaking change** (even if the new field is optional or provides a default value).\n  - *Rationale*: There are numerous downstream implementations and custom subclasses across diverse storage backends (e.g. relational databases, key-value stores, document databases, external cloud backends). Downstream storage implementations do not automatically persist, deserialize, or round-trip fields they were not compiled or migrated to handle, leading to silent state loss or deserialization errors when sessions or memories are reloaded.\n\n### 2. Self-Containment\n- Each package should be as self-contained as possible to reduce coupling.\n- Within the ADK framework, importing from a package's `__init__.py` is **not allowed**. Import from the specific module directly.\n\n### 3. Explicit Exports\n- The public API of a package must be explicitly exported in `__init__.py`.\n- **Only public names** should be imported into `__init__.py`. This keeps `__init__.py` minimal and prevents accidental exposure of internal implementation details.\n\n### 4. Intuitive Naming\n- Public method and class names should be concise and intuitive.\n- Private method names can be longer and more self-explanatory to reduce the need for comments.\n\n#### Examples\n\n**Public Naming**\n- **Good**: `Runner.run()`, `Session.get_events()`\n- **Bad**: `Runner.orchestrate_agent_invocation_loop()`, `Session.retrieve_all_events_from_storage()`\n\n**Private Naming**\n- **Good**: `_prepare_context_for_llm()`, `_should_trim_history()`\n- **Bad**: `_prep()`, `_trim()`\n\n<!-- File: .agents/skills/adk-architecture/references/architecture-checkpoint-resume.md (Tokens: ~615 | Category: anthropic-skill) -->\n# Checkpoint and Resume Lifecycle\n\nHITL (Human-in-the-Loop) follows this pattern:\n\n1. **Interrupt**: Node yields an event with `long_running_tool_ids`.\n   Each ancestor propagates the interrupt upward via `ctx.interrupt_ids`.\n2. **Persist**: Only the leaf node's interrupt event is persisted to\n   session. Workflow sets `ctx._interrupt_ids` directly (no internal\n   event needed).\n3. **Resume**: User sends a `FunctionResponse` message. The Runner\n   scans session events to find the matching `invocation_id`, then\n   reconstructs node state from persisted events.\n4. **Continue**: The interrupted node receives the FR and continues\n   execution. Downstream nodes receive the resumed node's output.\n\n## run_id on resume\n\nResumed nodes reuse the same `run_id` from the original\nexecution. From the node's perspective, the execution never paused\n— events before and after the resume share the same run_id.\n\nFresh dispatches (first run, loop re-trigger) get a new run_id.\n\n## Resume behavior by `rerun_on_resume`\n\nA node with multiple interrupt IDs may receive partial FRs (only\nsome resolved). The behavior depends on `rerun_on_resume`:\n\n**`rerun_on_resume=True`** (Workflow, orchestration nodes):\n\n| FRs received | Status | Behavior |\n|---|---|---|\n| Partial | PENDING | Re-execute immediately with partial `resume_inputs`. Node handles remaining interrupts internally (e.g., Workflow dispatches resolved children, keeps unresolved as WAITING). |\n| All | PENDING | Re-execute with all `resume_inputs`. |\n\nThis is critical for Workflow — when one child's FR arrives, it\nre-runs immediately to dispatch that resolved child. It doesn't\nwait for all children's FRs.\n\n**`rerun_on_resume=False`** (leaf nodes, simple HITL):\n\n| FRs received | Status | Behavior |\n|---|---|---|\n| Partial | WAITING | Stay waiting. Need all FRs. |\n| All | COMPLETED | Auto-complete. Output = aggregated `resolved_responses`. No re-execution. |\n\n## Resume with prior output and interrupts\n\nA node can produce output AND interrupt in the same execution (e.g.,\na Workflow where child A completes with output and child B interrupts).\nOn resume:\n\n- Some interrupt IDs are resolved (provided in `resume_inputs`)\n- Remaining interrupt IDs carry forward via `prior_interrupt_ids`\n- Prior output carries forward via `prior_output`\n- NodeRunner pre-populates ctx with these values before re-executing\n\n```python\nrunner = NodeRunner(\n    node=node, parent_ctx=ctx,\n    run_id=prior_run_id,  # reuse\n    prior_output=cached_output,\n    prior_interrupt_ids={'fc-2'},  # still unresolved\n)\nchild_ctx = await runner.run(\n    node_input=input,\n    resume_inputs={'fc-1': response},\n)\n```\n\n<!-- File: .agents/skills/adk-architecture/references/architecture-context.md (Tokens: ~1040 | Category: anthropic-skill) -->\n# Context\n\n## Two scoping objects\n\n- **InvocationContext** — one per invocation. Holds shared state (session,\n  services, event queue) reachable from every node.\n- **Context** — one per node execution. Holds per-node results (output, route,\n  interrupt IDs) and is the API surface node code actually touches.\n\nEvery Context references the same InvocationContext (`_invocation_context`).\nService access (artifacts, memory, auth) is delegated through it.\n\n```text\nRoot Context                      ← created by Runner from the InvocationContext\n└── Context [runner.node]         ← the root node (e.g., Workflow)\n    ├── Context [child_a]         ← child node A\n    └── Context [child_b]         ← child node B\n        └── Context [grandchild]  ← nested child\n```\n\nThe Runner creates the root Context and passes it as `parent_ctx` to the root\nnode's `NodeRunner`. The root Context exists only to be that parent; it carries\nno node path of its own.\n\nInvocationContext holds:\n\n- `session`, `agent`, `user_content`, `branch`\n- `invocation_id`, `app_name`, `user_id`\n- Services: `artifact_service`, `memory_service`, `credential_service`\n- `run_config`, `live_request_queue`\n- `is_resumable` — derived from the app's `ResumabilityConfig`\n- `_event_queue` — private queue drained by the Runner's main loop. Nodes never\n  touch it directly; they go through `ic._enqueue_event(event)`.\n\n## 1:1 node-context mapping\n\nEvery node execution gets its own Context. The relationship is strictly 1:1,\nso the Context tree mirrors the node execution tree.\n\n`NodeRunner._create_child_context()` builds the child from the parent. The\nchild inherits:\n\n- `_invocation_context` — the same object, so session and services are shared\n  (copied with `model_copy` when the child runs on a sub-branch)\n- `node_path` — parent path plus `name@run_id`\n- `run_id` — a sequential counter string per node path, reused on resume\n- `event_author`\n- the enclosing Workflow's dynamic-node scheduler\n\nThe child does not inherit output, route or interrupt IDs — those are\nper-execution results and start empty, unless a resume carries them forward via\n`prior_output` / `prior_interrupt_ids`.\n\n## Node result properties\n\nThese are how a node hands results back to its parent:\n\n- **`ctx.output`** — the node's result. Set once per execution, either by\n  `yield value` (the framework assigns it) or by `ctx.output = X`. A second\n  write raises `ValueError`.\n- **`ctx.route`** — routing value for conditional edges, `RouteValue` or a\n  list of them. Independent of output. Workflow-specific.\n- **`ctx.interrupt_ids`** — read-only set. The framework fills it when the node\n  yields an Event carrying `long_running_tool_ids`; the getter returns a copy.\n\nOutput and interrupts can coexist in one execution — a Workflow whose child A\nfinished while child B is waiting has both. The orchestrator's `_finalize`\ndecides what to propagate upward.\n\n## Class hierarchy\n\n```text\nReadonlyContext\n  └── Context\n```\n\n**ReadonlyContext** — read-only view handed to callbacks and plugins:\n`user_content`, `invocation_id`, `agent_name`, `session`, `user_id`,\n`run_config`, and `state` as an immutable `MappingProxyType`.\n\n**Context** — full read-write context for node execution.\n\n## Property reference\n\n| Category | Members |\n|---|---|\n| State & actions | `state` (mutable `State`), `actions` (`EventActions`), `custom_metadata` |\n| Node results | `output`, `route`, `interrupt_ids` (read-only) |\n| Node identity | `node`, `parent_ctx`, `node_path`, `run_id`, `attempt_count`, `resume_inputs` |\n| Scoping | `branch`, `isolation_scope` |\n| Errors | `error`, `error_node_path` — set by NodeRunner when the node fails and retries are exhausted |\n| Telemetry | `telemetry_context` |\n| Session | `session`, `get_invocation_context()` |\n| Child execution | `run_node()` |\n| Artifacts | `load_artifact()`, `save_artifact()`, `get_artifact_version()`, `list_artifacts()` |\n| Memory | `search_memory()`, `add_session_to_memory()`, `add_events_to_memory()`, `add_memory()` |\n| Auth | `request_credential()`, `load_credential()`, `save_credential()`, `get_auth_response()` |\n| Tools | `request_confirmation()`, `tool_confirmation`, `function_call_id` |\n| UI | `render_ui_widget()` |\n\n<!-- File: .agents/skills/adk-architecture/references/architecture-llm-context-orchestration.md (Tokens: ~439 | Category: anthropic-skill) -->\n# LLM context orchestration from events\n\n## Source versus view\n\nThe event stream and the LLM context are not the same thing:\n\n- **Events are the ground truth.** Immutable records of what happened — user\n  messages, model responses, tool calls, results. They are the audit log and\n  the persistence state.\n- **LLM context is an orchestrated view.** What gets sent to a model is not a\n  dump of the event log. It is filtered and transformed for the role, task and\n  branch of the agent currently running.\n\nTreating the two as interchangeable is the root of most \"why did the model see\nthat?\" bugs.\n\n## Delegation\n\nA coordinator hands work to a sub-agent with the `transfer_to_agent` tool,\nwhich sets `actions.transfer_to_agent` on the event rather than calling the\nsub-agent inline. The alternative is `AgentTool`, which wraps an agent so it is\ninvoked as an ordinary tool and returns its result to the caller.\n\nEither way the sub-agent does not inherit the coordinator's full transcript\nverbatim; it is given the task framing plus whatever history its branch\nexposes.\n\n## Branch isolation\n\nEvents from every node and branch share one session, in chronological order.\nIsolation comes from the `branch` field, a dot-separated path built by\n`_BranchPath`: `create_sub_branch('parent', name='child', run_id='1')` yields\n`'parent.child@1'`. A node running on a sub-branch sees only events on its own\npath, which is what keeps parallel siblings from polluting each other.\n\n`Event.isolation_scope` is a separate, coarser tag — NodeRunner stamps it from\n`ctx.isolation_scope` when the event does not carry one already.\n\n## History trimming and compaction\n\nLong histories overflow the context window and drag stale retry loops back into\nthe prompt. When the app sets `events_compaction_config`, the Runner compacts\nevents after the invocation. Because compaction rewrites aged history, do not\nstore transient status on an event and expect to read it back later.\n\n<!-- File: .agents/skills/adk-architecture/references/architecture-node-runner.md (Tokens: ~1488 | Category: anthropic-skill) -->\n# NodeRunner\n\n`NodeRunner` is the per-node executor. It creates the child Context, drives\n`BaseNode.run()`, opens the node's span, enriches and enqueues events, retries\non failure, and returns the child Context to the caller.\n\n## Two communication channels\n\n- **Context** — parent ↔ child. Output, route, state, resume inputs and\n  interrupt IDs flow through `ctx`. The orchestrator reads `ctx` after the\n  child finishes to decide what happens next.\n- **Event** — persistence and streaming. Events are appended to the session and\n  streamed to the caller. They carry message content, state deltas, function\n  calls and interrupt markers.\n\nA node writes to `ctx` to talk to its parent. It yields Events to persist data\nand stream to the user.\n\n## Execution flow\n\n```text\nOrchestrator\n  │\n  ├─ NodeRunner(node=child, parent_ctx=ctx)\n  │    │\n  │    ├─ _create_child_context()       → child Context (attempt_count)\n  │    ├─ start_as_current_node_span()  → ctx._telemetry_context\n  │    ├─ _execute_node()               → iterate node.run()\n  │    │    ├─ _track_event_in_context()    → write results to ctx\n  │    │    └─ _enqueue_event()             → enrich + persist\n  │    ├─ _flush_output_and_deltas()    → emit deferred output/route/deltas\n  │    └─ return child ctx\n  │\n  └─ reads ctx.output, ctx.route, ctx.interrupt_ids, ctx.error\n```\n\n1. **Create child Context.** Shares the InvocationContext, builds `node_path`\n   from the parent, assigns `run_id`, records `attempt_count`. If the session\n   already holds events for this node path, resolved responses are rehydrated\n   into `ctx._resume_inputs` before the node runs.\n\n2. **Open the span** via `node_tracing.start_as_current_node_span`, storing the\n   resulting `TelemetryContext` on `ctx`. Opening it inside the `try` is\n   deliberate — exceptions are recorded on the span.\n\n3. **Iterate `node.run()`.** For each yielded Event:\n\n   - **Track in context** — `_track_event_in_context` copies output, route and\n     `long_running_tool_ids` onto `ctx`, which is the source of truth. Route\n     and `transfer_to_agent` are only picked up from *native* events (no\n     author, or authored by this node), so a composite parent does not\n     re-bubble a decision its own sub-agent already handled. The event ID is\n     also registered on the node's span.\n   - **Enrich** — `_enrich_event` stamps `author` (`ctx.event_author` or the\n     node name), `invocation_id`, `node_info.path`, `branch`, and\n     `isolation_scope`. For an event carrying output it also sets\n     `node_info.output_for` to this node path plus its output ancestors.\n     `node_info.run_id` is **not** stamped separately — it is a property\n     derived from the last segment of `node_info.path` (`wf@1/child@2`).\n   - **Flush deltas** — for non-partial events, pending state and artifact\n     deltas move from `ctx.actions` onto the event.\n   - **Enqueue** — `ic._enqueue_event(event)` puts it on the shared queue for\n     session persistence.\n\n4. **Flush deferred output.** If `ctx.output` or `ctx.route` were set directly\n   rather than yielded, `_flush_output_and_deltas` emits one final Event after\n   `_run_impl` returns, bundling any remaining deltas onto it.\n\n5. **Return the child ctx.** The orchestrator reads `output`, `route`,\n   `interrupt_ids` and `error`.\n\n## Timeouts, retries and errors\n\n- `node.timeout` wraps the iteration in `asyncio.wait_for`; a timeout raises\n  `NodeTimeoutError`.\n- On any exception, NodeRunner enqueues an Event with `error_code` and\n  `error_message`, then consults `node.retry_config`. A retry sleeps for the\n  configured delay, increments `attempt_count` and rebuilds the child Context\n  from scratch. Retry count is **not** persisted, so it does not survive a\n  resume.\n- A child started with `ctx.run_node()` that fails reaches the node as\n  `DynamicNodeFailError`. The child's runner already emitted the error event,\n  so NodeRunner only matches the retry policy against the child's error. A\n  node with a `retry_config` copies the run-id counters under it at the start\n  of every attempt (`_snapshot_dynamic_children`), and a retry for such a\n  failure restores that copy (`_reset_dynamic_children_for_retry`). The retry\n  then hands out the same run ids and replays the children the failed attempt\n  completed, at any depth. A retry for the node's own exception keeps the\n  counters, so its children run again.\n- A parallel worker (`_ParallelWorker`, or a `Node` with\n  `parallel_worker=True`) is not retried for an item's `DynamicNodeFailError`\n  (`_delegates_item_retry`): the item node already applied the retry policy\n  to that item.\n- A `Workflow` reports a failing child by setting `ctx.error` rather than\n  raising, and NodeRunner applies the retry policy to that error as well.\n- When retries are exhausted, the error is recorded on `ctx.error` and\n  `ctx.error_node_path` and the Context is returned normally — NodeRunner does\n  not re-raise to the orchestrator.\n- `NodeInterruptedError` from a dynamic child is swallowed here: the child's\n  interrupt IDs are already on `ctx`, so the caller just reads\n  `ctx.interrupt_ids`.\n- When `abort_signal` is triggered (`ic.is_aborted` is `True`), active node\n  tasks are cancelled without retrying. In `_node_runner_utils`, the outer\n  workflow runner loop catches the cancellation, calls\n  `runner._synthesize_abort_events_if_needed(ic)` to emit `INVOCATION_ABORTED`\n  events (and seal any dangling `FunctionCall`s), and finishes the generator\n  cleanly so `after_run` callbacks still run.\n\n## Output delegation (`use_as_output`)\n\nWhen a child is scheduled with `use_as_output=True`, its output Event also\ncounts as the parent's output. NodeRunner sets `ctx._output_delegated`, drops\nthe output field from the parent's own event (keeping the event if it still\ncarries deltas), stamps `node_info.output_for` with the ancestor paths, and\npublishes non-tool output events on the outermost explicit claiming ancestor's\nbranch (leaving tool responses such as `finish_task` paired on the child's\nbranch).\n\n<!-- File: .agents/skills/adk-architecture/references/architecture-observability.md (Tokens: ~974 | Category: anthropic-skill) -->\n# Observability\n\nNode tracing lives in `telemetry/node_tracing.py`. Spans are **scope-based**:\n`NodeRunner` opens one around each node execution with an async context\nmanager, so the span closes on the way out even if the node raises.\n\n```python\nasync with node_tracing.start_as_current_node_span(parent_ctx, node) as tel_ctx:\n  ctx._telemetry_context = tel_ctx\n  await self._execute_node(ctx, node_input)\n```\n\n## TelemetryContext, not a raw span\n\nEach `Context` exposes `ctx.telemetry_context`, a frozen dataclass:\n\n| Member | Purpose |\n|---|---|\n| `otel_context` | OTel context holding the current span. Passed to children so their spans parent correctly. |\n| `add_event(event)` | Records an event ID as belonging to this node's span. |\n\nThe parent's `otel_context` is what makes the span tree mirror the node tree —\n`start_as_current_node_span` passes `context.telemetry_context.otel_context` as\nthe explicit parent rather than relying on whatever OTel considers current.\nInside the span the child builds a fresh `TelemetryContext` from\n`context_api.get_current()`.\n\nWhen attaching OTel context by hand (`context_api.attach()`), pair it with\n`detach()` in a `finally` — an unbalanced attach leaks the context into\nwhatever coroutine runs next on the loop.\n\n## Which span a node gets\n\n`start_as_current_node_span` dispatches on node type:\n\n| Node type | Span |\n|---|---|\n| `BaseAgent` subclass | none of its own — passes through, the agent emits its own `invoke_agent {name}` span |\n| `Workflow` | `invoke_workflow {name}` |\n| anything else | `invoke_node {name}` |\n\n`invoke_agent` follows OpenTelemetry semantic conventions v1.36 for backward\ncompatibility; `invoke_workflow` follows v1.41; `invoke_node` is not in any\nsemconv release yet.\n\n## Attributes\n\n| Attribute | Set on | Value |\n|---|---|---|\n| `gen_ai.operation.name` | all | `\"invoke_workflow\"` / `\"invoke_node\"` |\n| `gen_ai.conversation.id` | all | `ctx.session.id` |\n| `gen_ai.workflow.name` | workflow | the workflow's `name`, when non-empty |\n| `gen_ai.workflow.nested` | workflow | `True` only for a nested workflow; the entrypoint workflow omits the attribute entirely |\n| `gcp.vertex.agent.associated_event_ids` | all | IDs collected via `tel_ctx.add_event()`, stamped on span close when non-empty and the span is recording |\n| `adk.experimental.response.source` | `execute_tool`, `call_llm` | Callback provenance when a callback or plugin answered or replaced the result and `ADK_EXPERIMENTAL_TELEMETRY=true` is set (`\"before_tool_callback\"`, `\"after_tool_callback\"`, `\"on_tool_error_callback\"` on `execute_tool`; `\"before_model_callback\"`, `\"on_model_error_callback\"` on `call_llm`); omitted when the tool or model itself answered |\n\nNesting is detected through an OTel context key set by the first workflow in\nthe invocation. Because the key rides on the propagated `otel_context`, an\nagent-as-tool that spins up its own runner still reports `nested=true`.\n\n## Metrics\n\nRecorded from `telemetry/_metrics.py` under the `gcp.vertex.agent` meter:\n\n| Function | Recorded when |\n|---|---|\n| `record_workflow_invocation_duration` | an `invoke_workflow` span closes, tagged with `nested` and any error |\n| `record_agent_invocation_duration` | an agent invocation completes |\n| `record_invoke_agent_inference_calls` | per agent, count of model calls |\n| `record_invoke_agent_tool_calls` | per agent, count of tool calls |\n| `record_tool_execution_duration` | a tool finishes |\n| `record_client_operation_duration`, `record_client_token_usage` | model client calls |\n\n## Python logging\n\nUse the `google_adk` logger namespace so callers can filter ADK output:\n\n```python\nlogger = logging.getLogger(\"google_adk.\" + __name__)\n\nlogger.debug(\"node %s started.\", ctx.node_path)\n```\n\nUse `%`-style arguments, not f-strings — the formatting is then skipped\nentirely when the level is disabled.\n\n`NodeRunner` already logs node start, node end, execute-loop boundaries,\nrehydrated resume inputs, retries (`warning`) and unhandled exceptions\n(`logger.exception`). Do not re-log those from inside a node.\n\n<!-- File: .agents/skills/adk-architecture/references/architecture-runner-roles.md (Tokens: ~93 | Category: anthropic-skill) -->\n# Runner vs NodeRunner vs Workflow\n\nThese three are deliberately separate:\n\n- **Runner** = lifecycle orchestrator (InvocationContext, session,\n  plugins, invocation boundaries)\n- **NodeRunner** = task scheduler (asyncio tasks, node execution,\n  completions)\n- **Workflow** = graph engine (edges, traversal, node sequencing)\n\nMerging Runner and NodeRunner would deadlock on nested workflows\n(inner workflow's NodeRunner would block the outer's Runner).\n\n<!-- File: .agents/skills/adk-architecture/references/architecture-workflow-resumability.md (Tokens: ~1671 | Category: anthropic-skill) -->\n# Workflow resumability: model and direction\n\nHow a `Workflow` node preserves and restores execution state across a\nhuman-in-the-loop pause, how that compares to peer agent frameworks, and where\nit is heading. This is the whole-workflow view; the interrupt/resume lifecycle\nof a single node is covered separately.\n\nThe first thing to be clear about: a `Workflow` reconstructs its progress from\nthe session event stream on every run, so it resumes whether or not resumability\nis configured. The `is_resumable` flag does not switch resume on or off. What it\nswitches on is **durability** — persisting loadable checkpoints and letting an\ninvocation be continued across separate runner calls.\n\n## Current model\n\n### Resume is always on: reconstruction by event replay\n\nOn every run, the workflow scans the current invocation's session events and\nrebuilds its in-memory loop state (which nodes completed, their outputs, which\nare still waiting on interrupts). Completed nodes are fast-forwarded from their\nrecovered output rather than re-executed; the interrupted node re-runs with the\nsupplied responses.\n\nThis path (`_run_impl` -> `ReplayManager.scan_workflow_events`) has no\n`is_resumable` guard — the scan matches events purely by invocation id. The loop\nstate is not persisted and the checkpoint the workflow writes is not read back\non the load path; the session event log is the source of truth. So within an\ninvocation, a workflow is inherently replay-resumable, flag or no flag.\n\n### What `is_resumable` actually adds: durability\n\n`ResumabilityConfig.is_resumable` is a durability switch. When it is set:\n\n1.  **Cross-call resume.** The runner will continue an existing invocation\n    without a fresh user message and set up a \"resumed invocation\" context,\n    rehydrating the recorded agent state and end-of-agent markers from history.\n    With the flag off, the runner requires a new message and starts a fresh\n    invocation instead.\n2.  **Checkpoint markers in the log.** Composite agents (`LlmAgent`,\n    `SequentialAgent`, `LoopAgent`, `ParallelAgent`, and `LlmAgent`s wrapped as\n    workflow nodes) write `agent_state` / `end_of_agent` events into the session\n    only when the invocation is resumable. These make progress loadable across a\n    process boundary.\n3.  **Function-response routing.** Routing an incoming function response back to\n    its originating invocation is enabled only when resumable.\n\nThe config's own definition is durability-shaped: pause an invocation on a\nlong-running call, and resume it from the last event if it paused or failed\nmidway, best-effort and at-least-once, with in-memory state lost. So the\naccurate statement is: the flag decides whether progress is persisted as\nloadable checkpoints and whether an invocation can be resumed across runner\ncalls — not whether the workflow can resume. Resumability here is really\ndurability.\n\n### The `Workflow` node writes a checkpoint, but does not read one\n\n`Workflow._emit_node_checkpoint` does persist a node-status snapshot when\n`ic.is_resumable` — an `agent_state` event carrying `{\"nodes\": {name: {status,\ninterrupts}}}` for each static child (`resume_inputs` is intentionally omitted\nfrom the checkpoint). Two sibling markers are\nwritten under the same flag: `_maybe_reemit_replayed_output` re-surfaces a\nfast-forwarded node's output, and `_emit_end_of_agent` records that the\nworkflow ran to completion.\n\nWhat is missing is the other half. Nothing loads that checkpoint: setup still\nbegins with `ReplayManager.scan_workflow_events` over the whole invocation, and\n`_run_impl` carries a `TODO: resume from checkpoint event`. So the checkpoint\nis currently an observable record of progress rather than the durable state\nresume actually runs off. Resume cost still scales with history length.\n\n## How peer frameworks do it\n\nEvery mainstream agent framework persists a **state snapshot with a position\ncursor** and, on resume, **loads that snapshot** — none reconstruct by replaying\nthe entire history.\n\nFramework                | Durable unit                                                        | Position cursor                                                                    | Resume\n------------------------ | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------\nLangGraph (graph)        | `StateSnapshot` per super-step in a pluggable checkpointer          | `next` nodes + parent-pointer chain + per-task pending writes                      | re-invoke same thread id; load latest checkpoint, re-run only the interrupted node\npydantic-graph (graph)   | `NodeSnapshot{state, node, status}` via a state-persistence backend | the snapshot's `node` = next node to run; `status` created/pending/running/success | `iter_from_persistence()` loads the next `created` snapshot\nOpenAI Agents SDK (loop) | serialized `RunState` blob                                          | run cursor inside the state; correlate by tool-call id                             | deserialize the state, apply approvals, resume the run\nPydantic AI (loop)       | message history + deferred-tool results                             | implicit in the transcript; correlate by tool-call id                              | new run over the prior message history\n\nADK's durable unit today is the event log itself, and resume is by replay over\nit. Two patterns from the peers are worth copying, both consistent across them:\n\n-   **A snapshot is the source of truth for resume.** The runtime writes a\n    snapshot as it advances and reads the latest one to continue. Resume cost is\n    bounded by the snapshot size, not by history length.\n-   **Resume re-runs the paused unit from its start** (LangGraph and\n    pydantic-graph both re-execute the whole node, not a saved program counter),\n    which keeps the durable state small and pushes an idempotency contract onto\n    the node author — the same at-least-once contract ADK already documents.\n\n## Direction: load the checkpoint instead of replaying\n\nThe write half exists. The remaining step — peer-aligned, and the one ADK's own\ncomposite agents already take — is to make that checkpoint the thing resume\nreads:\n\n-   On resume, seed the loop state from the most recent `agent_state`\n    checkpoint instead of scanning the invocation's events, then continue:\n    re-run only the interrupted node and dispatch newly-ready successors.\n-   This makes resume cost independent of history length and unifies the\n    `Workflow` node with composite agents and with LangGraph / pydantic-graph /\n    the OpenAI SDK.\n\nThis only applies when durability is on. Without `is_resumable` nothing is\npersisted, and the workflow continues to resume within an invocation by replay.\n\n## Open considerations\n\n-   **Payload completeness.** A workflow checkpoint must carry (or be able to\n    recover) each completed node's output, run id, and branch — the equivalent\n    of LangGraph's per-task pending writes — to fully replace event replay.\n-   **Partial interrupt resolution.** A node with several interrupts may receive\n    only some responses on a resume. The re-run-vs-wait behavior differs between\n    an orchestrating node (re-run to dispatch the resolved branch) and a leaf\n    node (wait for all), keyed on `rerun_on_resume`. This is decided in the\n    shared replay-interception logic and should be settled before a load path\n    relies on it.\n-   **Versioning.** Long-lived paused runs can outlive a code change. A version\n    marker on the checkpoint lets a resume route to a compatible code path (the\n    OpenAI SDK makes this an explicit recommendation).\n-   **Serialization.** Keep payloads JSON-serializable (Pydantic `model_dump`),\n    so any persistence backend works and no code objects are serialized; node\n    objects are rebound from the in-memory graph definition on resume.\n\n<!-- File: .agents/skills/adk-architecture/references/interface-agent.md (Tokens: ~501 | Category: anthropic-skill) -->\n# Agent\n\n`Agent` is a type alias for `LlmAgent` (`Agent: TypeAlias = LlmAgent`), the\nmodel-backed agent most users instantiate. It is not the same thing as\n`BaseAgent`, which is the abstract base every agent derives from.\n\n`BaseAgent` extends `BaseNode`, so an agent is a node: it can stand alone under\na Runner or sit inside a Workflow graph.\n\n## Key fields\n\n- **`name`** — unique identifier within the agent tree. Validated: must be a\n  valid Python identifier, and `\"user\"` is rejected because it is reserved for\n  end-user input.\n- **`description`** — capability description the model uses when choosing\n  which sub-agent to delegate to.\n- **`sub_agents`** — child agents. Names must be unique across the tree;\n  each child's `parent_agent` is wired up automatically.\n- **`before_agent_callback` / `after_agent_callback`** — intercept the agent\n  lifecycle.\n\n## Entrance methods\n\n| Method | Use |\n|---|---|\n| `run_async(parent_context)` | Text conversation. Yields `Event`s. Runs the before/after callbacks, the error callback, and invocation instrumentation around `_run_async_impl`. |\n| `run_live(parent_context)` | Video/audio conversation. Marked `@final` — override `_run_live_impl` instead. |\n| `run(ctx=..., node_input=...)` | Inherited from `BaseNode`, `@final`. This is what a Workflow calls when the agent is a graph node; it routes into `_run_impl`, which for `BaseAgent` delegates to `run_async`. |\n\nWhich one you call depends on the caller, not on age: a Workflow calls `run()`,\ndirect text callers use `run_async()`. Nothing in the source marks `run_async`\ndeprecated, and it remains the path every agent's real logic runs through.\n\n## Other methods\n\n- **`clone(...)`** — copy the agent, detached from its parent.\n- **`find_agent(name)` / `find_sub_agent(name)`** — search the agent tree.\n- **`root_agent`** — walk up to the top of the tree.\n- **`from_config(config, config_abs_path)`** — build an agent from a config\n  object. Both `@deprecated` and `@experimental`; do not build on it.\n\n<!-- ... +118 more modular skills available via /api/skills/google/adk-python?path={path} -->\n</yakaai_skills>"}