diff --git a/.lycheeignore b/.lycheeignore
index 72b96df258..66396b3f9c 100644
--- a/.lycheeignore
+++ b/.lycheeignore
@@ -5,7 +5,7 @@
# Google Cloud Console (requires authentication, returns HTTP/2 errors)
^https://console\.(cloud\.google|developers\.google)\.com
-# Placeholder URL with user-specific path
+# URL contains bracket placeholder (e.g. /s/[project-id])
^https://app\.phoenix\.arize\.com/s/\[
# Requires authentication
diff --git a/docs/agents/index.md b/docs/agents/index.md
index 41e9bf2e31..dff532e314 100644
--- a/docs/agents/index.md
+++ b/docs/agents/index.md
@@ -86,4 +86,5 @@ Now that you have an overview of the different agent types available in ADK, div
* [**Workflow Agents:**](workflow-agents/index.md) Learn how to orchestrate tasks using `SequentialAgent`, `ParallelAgent`, and `LoopAgent` for structured and predictable processes.
* [**Custom Agents:**](custom-agents.md) Discover the principles of extending `BaseAgent` to build agents with unique logic and integrations tailored to your specific needs.
* [**Multi-Agents:**](multi-agents.md) Understand how to combine different agent types to create sophisticated, collaborative systems capable of tackling complex problems.
+* [**Agent Routing:**](routing.md) Dynamically select between multiple agents at runtime using router functions for fallback, A/B testing, and auto-routing.
* [**Models:**](/agents/models/) Learn about the different LLM integrations available and how to select the right model for your agents.
diff --git a/docs/agents/models/index.md b/docs/agents/models/index.md
index 0b5906280e..2858bffa6f 100644
--- a/docs/agents/models/index.md
+++ b/docs/agents/models/index.md
@@ -9,25 +9,30 @@ integrate various Large Language Models (LLMs) into your agents. This section
details how to leverage Gemini and integrate other popular models effectively,
including those hosted externally or running locally.
-ADK primarily uses two mechanisms for model integration:
+ADK provides several mechanisms for model integration:
-1. **Direct String / Registry:** For models tightly integrated with Google Cloud,
- such as Gemini models accessed via Google AI Studio or Agent Platform, or models
- hosted on Agent Platform endpoints. You access these models by providing the model name or endpoint resource string and ADK's internal registry
- resolves this string to the appropriate backend client.
+1. **Direct String / Registry:** For models tightly integrated with Google
+ Cloud, such as Gemini models accessed via Google AI Studio or Agent Platform,
+ or models hosted on Agent Platform endpoints. You access these models by
+ providing the model name or endpoint resource string and ADK's internal
+ registry resolves this string to the appropriate backend client.
* [Gemini models](/agents/models/google-gemini/)
* [Claude models](/agents/models/anthropic/)
* [Agent Platform hosted models](/agents/models/agent-platform/)
-2. **Model connectors:** For broader compatibility, especially models
- outside the Google ecosystem or those requiring specific client
- configurations, such as models accessed via Apigee or LiteLLM. You instantiate a specific wrapper class, such as `ApigeeLlm` or
- `LiteLlm`, and pass this object as the `model` parameter
- to your `LlmAgent`.
+2. **Model connectors:** For broader compatibility, especially models outside
+ the Google ecosystem or those requiring specific client configurations, such
+ as models accessed via Apigee or LiteLLM. You instantiate a specific wrapper
+ class, such as `ApigeeLlm` or `LiteLlm`, and pass this object as the `model`
+ parameter to your `LlmAgent`.
* [Apigee models](/agents/models/apigee/)
* [LiteLLM models](/agents/models/litellm/)
* [Ollama model hosting](/agents/models/ollama/)
* [vLLM model hosting](/agents/models/vllm/)
* [LiteRT-LM model hosting](/agents/models/litert-lm/)
+
+3. **[Model routing](/agents/models/routing/):** For dynamically selecting
+ between multiple models at runtime using a router function, with automatic
+ failover on error.
diff --git a/docs/agents/models/routing.md b/docs/agents/models/routing.md
new file mode 100644
index 0000000000..4c51847bf6
--- /dev/null
+++ b/docs/agents/models/routing.md
@@ -0,0 +1,64 @@
+# Route between models
+
+
+ Supported in ADK TypeScript v1.0.0 Experimental
+
+
+!!! example "Experimental"
+
+ Model routing is experimental and may change in future releases. We welcome
+ your
+ [feedback](https://github.com/google/adk-js/issues/new?template=feature_request.md)!
+
+An `LlmAgent` uses a single model by default. When you need to dynamically
+select between different models for each request, you can define a routing
+function that chooses which model to use. `RoutedLlm` provides this capability,
+enabling model fallback on error, A/B testing between models, and auto-routing
+by input complexity. If the selected model fails before producing any output,
+the routing function is called again with error context so it can select a
+different model.
+
+Pass a `RoutedLlm` as an `LlmAgent`'s `model` parameter. Use `RoutedLlm` when
+only the model varies between routes. If you also need to switch instructions,
+tools, or sub-agents, use [`RoutedAgent`](../routing.md) instead.
+
+## How routing works
+
+The `LlmRouter` function receives the map of available models and the current
+`LlmRequest`, and returns the key of the model to use:
+
+=== "TypeScript"
+
+ ```typescript
+ type LlmRouter = (
+ models: Readonly>,
+ request: LlmRequest,
+ errorContext?: { failedKeys: ReadonlySet; lastError: unknown },
+ ) => Promise | string | undefined;
+ ```
+
+The `models` parameter accepts either a `Record` with explicit
+keys, or an array of `BaseLlm` instances. If an array is provided, each model's
+name is used as its key.
+
+Failover follows the same rules as
+[`RoutedAgent`](../routing.md#how-routing-works): the router is re-called with
+`errorContext` only if the selected model fails before yielding any response.
+After yielding, errors propagate without retry. The router can return
+`undefined` to stop retrying and propagate the last error.
+
+**Live connections:** `RoutedLlm.connect()` selects the model at connection
+time. Once a live connection is established, the model cannot be switched
+mid-stream.
+
+## Basic usage
+
+The following example creates a `RoutedLlm` that tries a primary model first and
+falls back to a secondary model if the primary fails. The router checks
+`errorContext.failedKeys` to avoid re-selecting the failed model:
+
+=== "TypeScript"
+
+ ```typescript
+ --8<-- "examples/typescript/snippets/agents/models/routing/basic-usage.ts:full"
+ ```
diff --git a/docs/agents/routing.md b/docs/agents/routing.md
new file mode 100644
index 0000000000..67f8e3273c
--- /dev/null
+++ b/docs/agents/routing.md
@@ -0,0 +1,126 @@
+# Route between agents
+
+
+ Supported in ADK TypeScript v1.0.0 Experimental
+
+
+!!! example "Experimental"
+
+ Agent routing is experimental and may change in future releases. We welcome
+ your
+ [feedback](https://github.com/google/adk-js/issues/new?template=feature_request.md)!
+
+When building agents for different tasks, you can define a routing function that
+selects which one handles each invocation at runtime. `RoutedAgent` provides
+this capability, enabling agent fallback on error, A/B testing, planning modes,
+and auto-routing by input complexity. If the selected agent fails before
+producing any output, the routing function is called again with error context so
+it can select a fallback.
+
+`RoutedAgent` is different from [workflow agents](workflow-agents/index.md) like
+`SequentialAgent` or `ParallelAgent`, which orchestrate multiple agents in a
+fixed pattern, and from [LLM-driven
+delegation](multi-agents.md#b-llm-driven-delegation-agent-transfer), where the
+LLM decides which agent to hand off to. With `RoutedAgent`, you write an
+explicit routing function that selects **one** agent per invocation. For
+model-level routing, see [Model routing](models/routing.md).
+
+## How routing works
+
+Both `RoutedAgent` and [`RoutedLlm`](models/routing.md) are powered by a shared
+routing utility that handles selection and failover.
+
+The router function receives the map of available agents and the current
+context, and returns the key of the agent to run. It can be synchronous or
+async:
+
+=== "TypeScript"
+
+ ```typescript
+ type AgentRouter = (
+ agents: Readonly>,
+ context: InvocationContext,
+ errorContext?: { failedKeys: ReadonlySet; lastError: unknown },
+ ) => Promise | string | undefined;
+ ```
+
+**The `agents` parameter** accepts either a `Record` with
+explicit keys, or an array of agents. If an array is provided, each agent's
+`name` property is used as its key.
+
+**Failover behavior:**
+
+- The router is first called without `errorContext` to make the initial
+ selection.
+- If the selected agent throws an error **before yielding any events**, the
+ router is called again with `errorContext` containing `failedKeys` and
+ `lastError`.
+- If the selected agent throws an error **after yielding events**, the error
+ propagates directly without retry, because partial results have already been
+ emitted.
+- A key that has already been tried cannot be re-selected. If the router returns
+ a previously failed key, the error propagates.
+- If the router returns `undefined`, routing stops and the last error is thrown.
+
+## Basic usage
+
+Create multiple agents, define a router function that returns a key, and wrap
+them in a `RoutedAgent`. The following example routes between two agents based
+on an external configuration value that can change between invocations:
+
+=== "TypeScript"
+
+ ```typescript
+ --8<-- "examples/typescript/snippets/agents/routing/basic-usage.ts:full"
+ ```
+
+Change `config.selectedAgent` to `'agent_b'` before the next invocation to
+route to a different agent.
+
+## Fallback on error
+
+When an agent fails, the router is called again with `errorContext` so it can
+select a fallback. Failover only applies if the agent fails before yielding any
+events (see [How routing works](#how-routing-works)). The following example
+checks `errorContext.failedKeys` to avoid re-selecting the failed agent:
+
+=== "TypeScript"
+
+ ```typescript
+ --8<-- "examples/typescript/snippets/agents/routing/fallback.ts:config"
+ ```
+
+## Planning mode
+
+A router can read any external state to select between agents with different
+instructions, models, and tools. This lets you implement a planning mode where
+the agent switches behavior dynamically. For example, a basic agent might have
+read and write tools, while a planning agent is restricted to read-only access
+and uses a more powerful model for analysis.
+
+The following example shows a different `RoutedAgent` configuration. See [basic
+usage](#basic-usage) for the full runner setup.
+
+=== "TypeScript"
+
+ ```typescript
+ --8<-- "examples/typescript/snippets/agents/routing/planning-mode.ts:config"
+ ```
+
+Set `planningMode = true` before an invocation to route to the planning agent
+with its restricted tool set and different instructions.
+
+## Auto-routing by complexity
+
+The router function can call a lightweight classifier model to categorize input
+and route to different agents accordingly. Because the router can be async, you
+can make LLM calls inside it before selecting an agent.
+
+The following example shows a different `RoutedAgent` configuration. See [basic
+usage](#basic-usage) for the full runner setup.
+
+=== "TypeScript"
+
+ ```typescript
+ --8<-- "examples/typescript/snippets/agents/routing/auto-routing.ts:config"
+ ```
diff --git a/docs/api-reference/cli/_sources/index.rst.txt b/docs/api-reference/cli/_sources/index.rst.txt
index f081826ccc..99a05d1f5a 100644
--- a/docs/api-reference/cli/_sources/index.rst.txt
+++ b/docs/api-reference/cli/_sources/index.rst.txt
@@ -1,7 +1,7 @@
ADK CLI documentation
=====================
-This page contains the auto-generated command-line reference for ADK 1.31.0.
+This page contains the auto-generated command-line reference for ADK 1.32.0.
.. contents::
:local:
diff --git a/docs/api-reference/cli/_static/documentation_options.js b/docs/api-reference/cli/_static/documentation_options.js
index c374b8cc0f..255a4578a8 100644
--- a/docs/api-reference/cli/_static/documentation_options.js
+++ b/docs/api-reference/cli/_static/documentation_options.js
@@ -1,5 +1,5 @@
const DOCUMENTATION_OPTIONS = {
- VERSION: '1.31.0',
+ VERSION: '1.32.0',
LANGUAGE: 'en',
COLLAPSE_INDEX: false,
BUILDER: 'html',
diff --git a/docs/api-reference/cli/genindex.html b/docs/api-reference/cli/genindex.html
index 0fe1b7397c..ee7ce0275d 100644
--- a/docs/api-reference/cli/genindex.html
+++ b/docs/api-reference/cli/genindex.html
@@ -4,11 +4,11 @@
- Index — ADK CLI 1.31.0 documentation
+ Index — ADK CLI 1.32.0 documentation
-
+
diff --git a/docs/api-reference/cli/index.html b/docs/api-reference/cli/index.html
index 2c3a3e4f3c..022a9304a3 100644
--- a/docs/api-reference/cli/index.html
+++ b/docs/api-reference/cli/index.html
@@ -5,11 +5,11 @@
- ADK CLI documentation — ADK CLI 1.31.0 documentation
+ ADK CLI documentation — ADK CLI 1.32.0 documentation
-
+
@@ -43,7 +43,7 @@
ADK CLI documentation
-This page contains the auto-generated command-line reference for ADK 1.31.0.
+This page contains the auto-generated command-line reference for ADK 1.32.0.
adk
diff --git a/docs/api-reference/cli/objects.inv b/docs/api-reference/cli/objects.inv
index 19b631419a..92e4c164b5 100644
Binary files a/docs/api-reference/cli/objects.inv and b/docs/api-reference/cli/objects.inv differ
diff --git a/docs/api-reference/cli/search.html b/docs/api-reference/cli/search.html
index b1be6373ba..ba5af45230 100644
--- a/docs/api-reference/cli/search.html
+++ b/docs/api-reference/cli/search.html
@@ -4,12 +4,12 @@
- Search — ADK CLI 1.31.0 documentation
+ Search — ADK CLI 1.32.0 documentation
-
+
diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md
index e49b58d2a0..9a8def1d2c 100644
--- a/docs/api-reference/index.md
+++ b/docs/api-reference/index.md
@@ -9,7 +9,7 @@ The Agent Development Kit (ADK) provides comprehensive API references for both P
---
Explore the complete API documentation for the Python Agent Development Kit. Discover detailed information on all modules, classes, functions, and examples to build sophisticated AI agents with Python.
- [:octicons-arrow-right-24: View Python API Docs](python/index.html)
+ [:octicons-arrow-right-24: View Python API Docs](https://adk.dev/api-reference/python/)
@@ -30,7 +30,7 @@ The Agent Development Kit (ADK) provides comprehensive API references for both P
---
Access the comprehensive Javadoc for the Java Agent Development Kit. This reference provides detailed specifications for all packages, classes, interfaces, and methods, enabling you to develop robust AI agents using Java.
- [:octicons-arrow-right-24: View Java API Docs](java/index.html)
+ [:octicons-arrow-right-24: View Java API Docs](https://adk.dev/api-reference/java/)
@@ -42,7 +42,7 @@ The Agent Development Kit (ADK) provides comprehensive API references for both P
---
Access the complete API documentation for the TypeScript Agent Development Kit. Find detailed information on all packages, classes, and methods to build powerful and flexible AI agents with TypeScript.
- [:octicons-arrow-right-24: View Typescript API Docs](typescript/index.html)
+ [:octicons-arrow-right-24: View Typescript API Docs](https://adk.dev/api-reference/typescript/)
@@ -54,7 +54,7 @@ The Agent Development Kit (ADK) provides comprehensive API references for both P
Explore the complete API documentation for the CLI including all of the
valid options and subcommands.
- [:octicons-arrow-right-24: View CLI Docs](cli/index.html)
+ [:octicons-arrow-right-24: View CLI Docs](https://adk.dev/api-reference/cli/)
@@ -64,7 +64,7 @@ The Agent Development Kit (ADK) provides comprehensive API references for both P
View the full Agent Config syntax for configuring ADK with
YAML text files.
- [:octicons-arrow-right-24: View Agent Config reference](agentconfig/index.html)
+ [:octicons-arrow-right-24: View Agent Config reference](https://adk.dev/api-reference/agentconfig/)
@@ -73,6 +73,6 @@ The Agent Development Kit (ADK) provides comprehensive API references for both P
---
Explore the REST API for the ADK web server. This reference provides details on the available endpoints, request and response formats, and more.
- [:octicons-arrow-right-24: View REST API Docs](rest/index.html)
+ [:octicons-arrow-right-24: View REST API Docs](https://adk.dev/api-reference/rest/)
diff --git a/docs/api-reference/rest/openapi.json b/docs/api-reference/rest/openapi.json
index 3dc1939bcd..8351b372a1 100644
--- a/docs/api-reference/rest/openapi.json
+++ b/docs/api-reference/rest/openapi.json
@@ -2,7 +2,7 @@
"openapi": "3.1.0",
"info": {
"title": "ADK REST API Reference",
- "version": "1.31.0"
+ "version": "1.32.0"
},
"paths": {
"/health": {
@@ -6549,6 +6549,18 @@
],
"title": "Filesearchstore",
"description": "Optional. Name of the `FileSearchStore` containing the document. Example: `fileSearchStores/123`. This field is not supported in Vertex AI."
+ },
+ "pageNumber": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Pagenumber",
+ "description": "Optional. Page number of the retrieved context. This field is not supported in Vertex AI."
}
},
"additionalProperties": false,
diff --git a/docs/deploy/cloud-run.md b/docs/deploy/cloud-run.md
index 27e48d6947..4d77f239f4 100644
--- a/docs/deploy/cloud-run.md
+++ b/docs/deploy/cloud-run.md
@@ -83,6 +83,17 @@ export GOOGLE_API_KEY=your-api-key
Please make sure you have created a secret which can be read by your service account.
+
+### Cloud Build Permissions
+
+Since the `adk deploy` command uses Google Cloud Build to automate the build process, you must set your default compute service account to have permission to use Cloud Build.
+The following command example shows how to grant this permission:
+
+```bash
+gcloud projects add-iam-policy-binding [PROJECT_ID] \
+ --member="serviceAccount:[PROJECT_NUMBER]-compute@developer.gserviceaccount.com" \
+ --role="roles/cloudbuild.builds.builder"
+
### Entry for GOOGLE_API_KEY secret
You can create your secret manually or use CLI:
diff --git a/docs/observability/index.md b/docs/observability/index.md
index 963e5c953e..aba9bde6dd 100644
--- a/docs/observability/index.md
+++ b/docs/observability/index.md
@@ -8,8 +8,8 @@ in-process behavior. Basic input and output monitoring is typically
insufficient for agents with any significant level of complexity.
Agent Development Kit (ADK) provides built-in observability through
-[logging](/observability/logging/) and [traces](/observability/traces/) to help
-you monitor and debug your agents. However, you may need to consider more
+[logging](/observability/logging/), [metrics](/observability/metrics/), and
+[traces](/observability/traces/) to help you monitor and debug your agents. However, you may need to consider more
advanced [observability ADK Integrations](/integrations/?topic=observability)
for monitoring and analysis.
diff --git a/docs/observability/metrics.md b/docs/observability/metrics.md
new file mode 100644
index 0000000000..c424d83740
--- /dev/null
+++ b/docs/observability/metrics.md
@@ -0,0 +1,93 @@
+# Agent activity metrics
+
+
+ Supported in ADK Python v1.32.0
+
+
+Agent Development Kit (ADK) provides built-in, vendor-neutral metrics collection to help you understand the performance, cost, and usage patterns of your agents. While logs provide a detailed narrative of *what* happened, metrics give you aggregated, quantitative data to answer *how often* and *how fast* things are happening.
+
+## Metrics philosophy
+
+ADK's approach to metrics is designed to be lightweight, standardized, and entirely agnostic to your choice of monitoring backend.
+
+* **OpenTelemetry Semantic Conventions:** ADK implements the OpenTelemetry (OTel) [Semantic Conventions for GenAI](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-metrics.md). This ensures that metrics are recorded under standard, predictable attribute and metric names.
+* **OTLP Wire Format:** ADK emits data using the standard OTLP format, ensuring that your metrics will seamlessly integrate into any OTel-compatible backend (e.g., Prometheus, Datadog, SigNoz, Google Cloud Monitoring).
+* **Cost and Performance Focused:** Metrics are significantly less costly and more performant than logs or traces when performing analytics over large swathes of data. ADK tracks the most critical signals for LLM applications: token consumption, request latency, and tool execution reliability.
+* **Vendor-Neutral Export:** ADK does not lock you into a specific metrics pipeline. You instantiate standard OTel meter providers and export data wherever your infrastructure demands.
+
+---
+
+## Metrics schema
+
+When metrics are enabled, ADK automatically instruments the agent's lifecycle, workflow steps, and tool executions based on the OpenTelemetry GenAI Semantic Conventions. The following core metrics are emitted:
+
+| Metric Name | Type | Description | Key Attributes (Dimensions) |
+| :--- | :--- | :--- | :--- |
+| **`gen_ai.agent.invocation.duration`** | Histogram | The total time taken for an agent to process a prompt and return a response. | `gen_ai.agent.name`, `error.type` |
+| **`gen_ai.tool.execution.duration`** | Histogram | The execution latency of individual tools called by the agent. Useful for spotting slow external APIs. | `gen_ai.tool.name`, `error.type` |
+| **`gen_ai.agent.request.size`** | Histogram | The size or complexity of the incoming request sent to the agent. | `gen_ai.agent.name` |
+| **`gen_ai.agent.response.size`** | Histogram | The size or complexity of the final response generated by the agent. | `gen_ai.agent.name` |
+| **`gen_ai.agent.workflow.steps`** | Histogram | Tracks the number of iterative steps or reasoning loops an agent takes to complete a workflow. | `gen_ai.agent.name` |
+
+---
+
+## Metrics export setup
+
+### Metrics export in ADK Web
+
+If you are running your agent using the `adk web` or `adk api_server` CLI commands, you can configure metrics export.
+
+
+#### OTLP export
+
+To export metrics to an OTLP-compatible backend, set the standard OTel environment variables:
+
+```bash
+export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT="http://your-collector:4318/v1/metrics"
+adk web path/to/your/agents_dir
+```
+
+> **Note:** You can also set the general `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable if you would like to send traces and logs to the same endpoint in addition to metrics.
+
+#### GCP export
+
+To enable metrics export to Google Cloud Monitoring, use the `-otel_to_cloud` flag:
+
+```bash
+adk web -otel_to_cloud path/to/your/agents_dir
+```
+
+### Programmatic metrics export
+
+You can also configure metrics export programmatically in your application code.
+
+#### OTLP export setup
+
+To enable metrics and export them to an OpenTelemetry Collector (or an OTLP-compatible backend) programmatically:
+
+```python
+from google.adk.telemetry.setup import maybe_set_otel_providers
+import os
+
+os.environ["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"] = "http://your-collector:4318/v1/metrics"
+os.environ["OTEL_SERVICE_NAME"] = "your-adk-agent"
+os.environ["OTEL_RESOURCE_ATTRIBUTES"] = "key1=value1,key2=value2"
+maybe_set_otel_providers()
+```
+
+#### GCP export setup
+
+To export metrics to Google Cloud Monitoring programmatically, use the OpenTelemetry Google Cloud exporter. Here is an example in Python:
+
+```python
+from google.adk.telemetry.google_cloud import get_gcp_exporters
+from google.adk.telemetry.setup import maybe_set_otel_providers
+import os
+
+gcp_exporters = get_gcp_exporters(
+ enable_cloud_metrics = True,
+)
+os.environ["OTEL_SERVICE_NAME"] = "your-adk-agent"
+os.environ["OTEL_RESOURCE_ATTRIBUTES"] = "key1=value1,key2=value2"
+maybe_set_otel_providers([gcp_exporters])
+```
diff --git a/docs/runtime/ambient-agents.md b/docs/runtime/ambient-agents.md
index e6203ecadd..24a4b00f65 100644
--- a/docs/runtime/ambient-agents.md
+++ b/docs/runtime/ambient-agents.md
@@ -1,12 +1,15 @@
-# Ambient Agents
+# Trigger actions with ambient agents
- Supported in ADK Python Go
+ Supported in ADK Python v1.29.0 Go v1.1.0
-ADK supports **ambient agents**, autonomous agents that process data, monitor
-events, and respond asynchronously without human intervention. Use ambient
-agents to:
+When running an agent workflow, you may want to activate it in response to an
+event or new data being available, rather than waiting for input from a human.
+You can configure ADK agents with triggers to respond to events and perform
+work, known as *ambient agents*. These agents can run as background processes
+to process data, monitor events, and respond asynchronously without human
+intervention. You can use ambient agents to:
- **React to cloud events.** Process a file when it's uploaded to
[Cloud Storage](https://cloud.google.com/storage), respond to database
diff --git a/docs/runtime/cancel.md b/docs/runtime/cancel.md
new file mode 100644
index 0000000000..174ea0c8c1
--- /dev/null
+++ b/docs/runtime/cancel.md
@@ -0,0 +1,101 @@
+# Cancel agent runs
+
+
+ Supported in ADK TypeScript v1.0.0
+
+
+When an agent run takes too long, encounters changing conditions, or is no
+longer needed, you may want to cancel it without losing the work already
+completed. Cancellation in ADK is non-destructive: events already committed to
+the session remain persisted.
+
+ADK supports graceful cancellation using `AbortController` and `AbortSignal`.
+Pass an `AbortSignal` to `runner.runAsync()` to cancel the entire invocation at
+any point in the execution stack, including agent execution, LLM generation,
+tool execution, and plugin callbacks.
+
+## Get started
+
+Create an `AbortController`, pass its `signal` to `runner.runAsync()`, and call
+`controller.abort()` when you want to cancel execution:
+
+=== "TypeScript"
+
+ ```typescript
+ --8<-- "examples/typescript/snippets/runtime/cancel/basic-usage.ts:full"
+ ```
+
+## How cancellation propagates
+
+When you abort the signal, cancellation propagates down through the entire
+execution stack. Each component checks `abortSignal.aborted` at critical
+lifecycle points and terminates early when it detects cancellation:
+
+| Component | What happens on abort |
+| :--- | :--- |
+| **Runner** | Stops before session fetch, after plugin callbacks, and within the event streaming loop. |
+| **LlmAgent** | Stops between execution steps, before/after model callbacks, and within response streaming. |
+| **LoopAgent** | Stops between loop iterations and between sub-agent executions. |
+| **ParallelAgent** | Stops when merging results from concurrent sub-agent runs. |
+| **Models (Gemini)** | The signal is passed to the underlying Google GenAI SDK via `config.abortSignal`, cancelling the in-flight HTTP request. |
+| **AgentTool** | Passes the signal to the sub-agent runner and checks for abort after session creation. |
+| **MCPTool** | Passes the signal to the MCP client's `callTool` method. |
+
+The `InvocationContext` also registers a listener on the signal that
+automatically sets `endInvocation = true` when triggered, signaling all
+components to wind down.
+
+### Behavior on cancellation
+
+When an `AbortSignal` is triggered, the following applies:
+
+- **Graceful termination:** The async generator returned by `runner.runAsync()`
+ completes (stops yielding events) without throwing an error.
+- **Committed events persist:** Any events that were already yielded and
+ processed by the Runner before the abort remain committed to the session
+ history.
+- **No partial events:** Events that were in progress but not yet yielded are
+ discarded.
+- **Resource cleanup:** In-flight LLM requests to the Gemini API are cancelled
+ through the SDK's native `AbortSignal` support, freeing network resources.
+
+## Advanced examples
+
+The following examples show additional cancellation patterns beyond the basic
+`AbortController` usage.
+
+### Cancellation with a timeout
+
+Use `AbortSignal.timeout()` to automatically cancel an agent run after a
+specified duration. This is useful for enforcing time limits on agent execution.
+
+Using the same agent and runner setup from the get started example, replace
+everything from `const controller` onwards with:
+
+=== "TypeScript"
+
+ ```typescript
+ --8<-- "examples/typescript/snippets/runtime/cancel/timeout.ts:run"
+ ```
+
+You can also combine a timeout with programmatic cancellation using
+`AbortSignal.any()`. Using the same setup, replace everything from `const
+controller` onwards with:
+
+=== "TypeScript"
+
+ ```typescript
+ --8<-- "examples/typescript/snippets/runtime/cancel/combined-signal.ts:run"
+ ```
+
+### AbortSignal in custom tools
+
+When you pass an `AbortSignal` to `runner.runAsync()`, it is available on
+`toolContext.abortSignal` inside your custom tools. The following example shows
+the pattern for checking the abort signal inside a custom tool:
+
+=== "TypeScript"
+
+ ```typescript
+ --8<-- "examples/typescript/snippets/runtime/cancel/custom-tool.ts:tool"
+ ```
diff --git a/docs/runtime/index.md b/docs/runtime/index.md
index 165fe22d33..aa0a7bcabe 100644
--- a/docs/runtime/index.md
+++ b/docs/runtime/index.md
@@ -36,6 +36,15 @@ the method that best fits your development workflow.
[:octicons-arrow-right-24: Use the API Server](api-server.md)
+- :material-access-point:{ .lg .middle } **Ambient Agents**
+
+ ---
+
+ Build autonomous agents that process events, monitor systems, and respond
+ asynchronously without human intervention.
+
+ [:octicons-arrow-right-24: Use Ambient Agents](ambient-agents.md)
+
## Technical reference
@@ -47,5 +56,7 @@ pages:
ADK, including the yield/pause/resume cycle.
- **[Resume Agents](resume.md)**: Learn how to resume agent execution from a
previous state.
+- **[Cancel Agent Runs](cancel.md)**: Gracefully cancel running
+ agent invocations using AbortSignal (TypeScript).
- **[Runtime Config](runconfig.md)**: Configure runtime behavior with
RunConfig.
diff --git a/docs/stylesheets/custom.css b/docs/stylesheets/custom.css
index dac330f0cb..ad6fdaad1a 100644
--- a/docs/stylesheets/custom.css
+++ b/docs/stylesheets/custom.css
@@ -14,6 +14,11 @@
* limitations under the License.
*/
+/* Prevent layout shift from scrollbar appearing/disappearing */
+html {
+ scrollbar-gutter: stable;
+}
+
/* Index page styling */
.md-grid {
@@ -158,28 +163,63 @@ button.md-content__button:hover svg {
}
}
-.md-header__source {
- max-width: 480px;
- display: flex;
- align-items: center;
+.md-header__title {
+ flex-shrink: 1 !important;
+ min-width: 120px !important;
+ overflow: hidden !important;
}
-@media screen and (max-width: 76.1875em) {
- .md-header__source {
- display: none;
- }
+.md-header__source {
+ flex-shrink: 0 !important;
+ display: flex !important;
+ align-items: center;
+ gap: 2px !important;
+ flex-wrap: nowrap !important;
+ max-width: none !important;
+ width: auto !important;
}
-.md-source__facts {
- display: none !important;
+.md-header .md-source {
+ min-width: auto !important;
+ width: auto !important;
+ margin-right: 2px !important;
}
-.md-source__repository {
+.md-header .md-source__repository {
+ font-size: 0.65rem;
white-space: nowrap;
max-width: none !important;
overflow: visible !important;
}
+.md-header .md-source__icon svg {
+ width: 1rem !important;
+ height: 1rem !important;
+}
+
+@media (max-width: 1200px) {
+ .md-header .md-source__repository { display: none !important; }
+ .md-header .md-source { margin-right: 4px !important; position: relative; }
+ .md-header .md-source::after {
+ content: ''; display: inline-block; width: 16px; height: 16px;
+ background-size: contain; background-repeat: no-repeat; background-position: center;
+ position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
+ }
+ .md-header .md-source { padding-right: 22px !important; min-width: 0 !important; width: auto !important; }
+ .md-header .md-source[href*="adk-python"]::after { background-image: url('https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/python/python-original.svg'); }
+ .md-header .md-source[href*="adk-js"]::after { background-image: url('https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/typescript/typescript-original.svg'); }
+ .md-header .md-source[href*="adk-go"]::after { background-image: url('https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/go/go-original.svg'); }
+ .md-header .md-source[href*="adk-java"]::after { background-image: url('https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/java/java-original.svg'); }
+}
+
+@media (max-width: 900px) {
+ .md-header .md-source { display: none !important; }
+}
+
+.md-source__facts {
+ display: none !important;
+}
+
/* Navigation section titles */
nav.md-nav--primary .md-nav__item--section>.md-nav__link {
diff --git a/docs/stylesheets/homepage.css b/docs/stylesheets/homepage.css
index 30d86d7705..c8b6662e0b 100644
--- a/docs/stylesheets/homepage.css
+++ b/docs/stylesheets/homepage.css
@@ -23,39 +23,10 @@
}
/* JS fallback via body class */
-body.adk-landing-page { overflow-x: hidden !important; }
body.adk-landing-page .md-main .md-grid { max-width: 100% !important; width: 100% !important; }
body.adk-landing-page .md-main__inner { max-width: none !important; margin: 0 !important; padding: 0 !important; }
body.adk-landing-page .md-content { max-width: none !important; margin: 0 !important; flex-grow: 1 !important; }
body.adk-landing-page .md-content__inner { max-width: 1280px !important; margin: 0 auto !important; padding: 0 clamp(16px, 4vw, 48px) !important; box-sizing: border-box !important; overflow-x: hidden !important; }
-body.adk-landing-page .md-header__inner { overflow-x: hidden !important; }
-
-/* Responsive header repo links — full text → icons only (≤1200px) → hidden (≤900px) */
-.md-header__title { flex-shrink: 1 !important; min-width: 120px !important; overflow: hidden !important; }
-.md-header__source { flex-shrink: 0 !important; display: flex !important; gap: 2px !important; flex-wrap: nowrap !important; max-width: none !important; width: auto !important; }
-
-.md-header .md-source { min-width: auto !important; width: auto !important; margin-right: 2px !important; }
-.md-header .md-source__repository { font-size: 0.65rem; white-space: nowrap; }
-.md-header .md-source__icon svg { width: 1rem !important; height: 1rem !important; }
-
-@media (max-width: 1200px) {
- .md-header .md-source__repository { display: none !important; }
- .md-header .md-source { margin-right: 4px !important; position: relative; }
- .md-header .md-source::after {
- content: ''; display: inline-block; width: 16px; height: 16px;
- background-size: contain; background-repeat: no-repeat; background-position: center;
- position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
- }
- .md-header .md-source { padding-right: 22px !important; min-width: 0 !important; width: auto !important; }
- .md-header .md-source[href*="adk-python"]::after { background-image: url('https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/python/python-original.svg'); }
- .md-header .md-source[href*="adk-js"]::after { background-image: url('https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/typescript/typescript-original.svg'); }
- .md-header .md-source[href*="adk-go"]::after { background-image: url('https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/go/go-original.svg'); }
- .md-header .md-source[href*="adk-java"]::after { background-image: url('https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/java/java-original.svg'); }
-}
-
-@media (max-width: 900px) {
- .md-header .md-source { display: none !important; }
-}
/* --- Landing wrapper variables (adapts to light/dark via Material) --- */
.md-typeset .adk-landing {
diff --git a/examples/typescript/snippets/agents/models/routing/basic-usage.ts b/examples/typescript/snippets/agents/models/routing/basic-usage.ts
new file mode 100644
index 0000000000..41da68e021
--- /dev/null
+++ b/examples/typescript/snippets/agents/models/routing/basic-usage.ts
@@ -0,0 +1,73 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// --8<-- [start:full]
+import {
+ BaseLlm,
+ Gemini,
+ LlmRequest,
+ LlmAgent,
+ RoutedLlm,
+ InMemoryRunner,
+} from '@google/adk';
+
+const primaryModel = new Gemini({ model: 'gemini-flash-latest' });
+const fallbackModel = new Gemini({ model: 'gemini-pro-latest' });
+
+const router = (
+ models: Readonly>,
+ request: LlmRequest,
+ // errorContext is provided when a previously selected model fails
+ errorContext?: { failedKeys: ReadonlySet; lastError: unknown },
+) => {
+ if (!errorContext) {
+ return 'primary'; // Try primary first
+ }
+ if (errorContext.failedKeys.has('primary')) {
+ return 'fallback'; // Fall back if primary failed
+ }
+ return undefined; // No more options, propagate the error
+};
+
+const routedLlm = new RoutedLlm({
+ models: { primary: primaryModel, fallback: fallbackModel },
+ router,
+});
+
+// Use RoutedLlm as the model for an LlmAgent
+const agent = new LlmAgent({
+ name: 'my_agent',
+ model: routedLlm,
+ instruction: 'You are a helpful assistant.',
+});
+
+const runner = new InMemoryRunner({ agent, appName: 'my_app' });
+
+const session = await runner.sessionService.createSession({
+ appName: 'my_app',
+ userId: 'user_1',
+});
+
+const run = runner.runAsync({
+ userId: 'user_1',
+ sessionId: session.id,
+ newMessage: { role: 'user', parts: [{ text: 'Hello!' }] },
+});
+
+for await (const event of run) {
+ if (event.content?.parts?.[0]?.text) {
+ console.log(event.content.parts[0].text);
+ }
+}
+// --8<-- [end:full]
diff --git a/examples/typescript/snippets/agents/models/routing/package.json b/examples/typescript/snippets/agents/models/routing/package.json
new file mode 100644
index 0000000000..c60d54adf8
--- /dev/null
+++ b/examples/typescript/snippets/agents/models/routing/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "adk-docs-examples",
+ "version": "1.0.0",
+ "description": "TS code examples for the ADK Documentation",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "build": "tsc",
+ "clean": "rm -rf dist"
+ },
+ "keywords": ["adk", "google", "agent", "typescript", "gemini"],
+ "author": "",
+ "license": "Apache-2.0",
+ "type": "commonjs",
+ "devDependencies": {
+ "@types/node": "^20.14.2",
+ "typescript": "^5.9.2"
+ },
+ "dependencies": {
+ "@google/adk": "^1.0.0"
+ }
+}
diff --git a/examples/typescript/snippets/agents/models/routing/tsconfig.json b/examples/typescript/snippets/agents/models/routing/tsconfig.json
new file mode 100644
index 0000000000..820f935898
--- /dev/null
+++ b/examples/typescript/snippets/agents/models/routing/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ /* Build Options */
+ "target": "es2020",
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "outDir": "./dist",
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+
+ /* Strictness */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+
+ /* Module Interop */
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/examples/typescript/snippets/agents/routing/auto-routing.ts b/examples/typescript/snippets/agents/routing/auto-routing.ts
new file mode 100644
index 0000000000..0bf08377ac
--- /dev/null
+++ b/examples/typescript/snippets/agents/routing/auto-routing.ts
@@ -0,0 +1,98 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { InMemoryRunner } from '@google/adk';
+
+// --8<-- [start:config]
+import {
+ BaseAgent,
+ Gemini,
+ InvocationContext,
+ LlmAgent,
+ RoutedAgent,
+} from '@google/adk';
+
+const simpleAgent = new LlmAgent({
+ name: 'simple',
+ model: 'gemini-flash-latest',
+ instruction: 'You are a simple assistant for basic questions.',
+});
+
+const complexAgent = new LlmAgent({
+ name: 'complex',
+ model: 'gemini-pro-latest',
+ instruction: 'You are an expert assistant for complex analysis.',
+});
+
+// Lightweight model to classify input complexity
+const classifierModel = new Gemini({ model: 'gemini-flash-latest' });
+
+const router = async (
+ agents: Readonly>,
+ context: InvocationContext,
+) => {
+ // Extract the user's input text
+ const text = context.userContent?.parts?.[0]?.text || '';
+ if (!text) return 'simple';
+
+ const prompt =
+ `Classify this request as 'simple' or 'complex'. ` +
+ `Reply with ONLY that word.\nRequest: "${text}"`;
+
+ const generator = classifierModel.generateContentAsync({
+ contents: [{ role: 'user', parts: [{ text: prompt }] }],
+ toolsDict: {},
+ liveConnectConfig: {},
+ });
+
+ let classification = '';
+ for await (const resp of generator) {
+ if (resp.content?.parts?.[0]?.text) {
+ classification += resp.content.parts[0].text;
+ }
+ }
+
+ return classification.toLowerCase().includes('complex')
+ ? 'complex'
+ : 'simple';
+};
+
+const routedAgent = new RoutedAgent({
+ name: 'my_routed_agent',
+ agents: { simple: simpleAgent, complex: complexAgent },
+ router,
+});
+// --8<-- [end:config]
+
+const runner = new InMemoryRunner({
+ agent: routedAgent,
+ appName: 'my_app',
+});
+
+const session = await runner.sessionService.createSession({
+ appName: 'my_app',
+ userId: 'user_1',
+});
+
+const run = runner.runAsync({
+ userId: 'user_1',
+ sessionId: session.id,
+ newMessage: { role: 'user', parts: [{ text: 'What is 1+1?' }] },
+});
+
+for await (const event of run) {
+ if (event.content?.parts?.[0]?.text) {
+ console.log(event.content.parts[0].text);
+ }
+}
diff --git a/examples/typescript/snippets/agents/routing/basic-usage.ts b/examples/typescript/snippets/agents/routing/basic-usage.ts
new file mode 100644
index 0000000000..b4f4a4fb25
--- /dev/null
+++ b/examples/typescript/snippets/agents/routing/basic-usage.ts
@@ -0,0 +1,60 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// --8<-- [start:full]
+import { LlmAgent, RoutedAgent, InMemoryRunner } from '@google/adk';
+
+const agentA = new LlmAgent({
+ name: 'agent_a',
+ model: 'gemini-flash-latest',
+ instruction: 'You are Agent A. Always identify yourself as Agent A.',
+});
+
+const agentB = new LlmAgent({
+ name: 'agent_b',
+ model: 'gemini-flash-latest',
+ instruction: 'You are Agent B. Always identify yourself as Agent B.',
+});
+
+// External configuration that can change at runtime
+const config = { selectedAgent: 'agent_a' };
+
+const routedAgent = new RoutedAgent({
+ name: 'my_routed_agent',
+ agents: { agent_a: agentA, agent_b: agentB },
+ router: () => config.selectedAgent,
+});
+
+const runner = new InMemoryRunner({
+ agent: routedAgent,
+ appName: 'my_app',
+});
+
+const session = await runner.sessionService.createSession({
+ appName: 'my_app',
+ userId: 'user_1',
+});
+
+const run = runner.runAsync({
+ userId: 'user_1',
+ sessionId: session.id,
+ newMessage: { role: 'user', parts: [{ text: 'Who are you?' }] },
+});
+
+for await (const event of run) {
+ if (event.content?.parts?.[0]?.text) {
+ console.log(event.content.parts[0].text);
+ }
+}
+// --8<-- [end:full]
diff --git a/examples/typescript/snippets/agents/routing/fallback.ts b/examples/typescript/snippets/agents/routing/fallback.ts
new file mode 100644
index 0000000000..29aea62758
--- /dev/null
+++ b/examples/typescript/snippets/agents/routing/fallback.ts
@@ -0,0 +1,79 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { InMemoryRunner } from '@google/adk';
+
+// --8<-- [start:config]
+import {
+ BaseAgent,
+ InvocationContext,
+ LlmAgent,
+ RoutedAgent,
+} from '@google/adk';
+
+const primaryAgent = new LlmAgent({
+ name: 'primary',
+ model: 'gemini-flash-latest',
+ instruction: 'You are the primary agent.',
+});
+
+const fallbackAgent = new LlmAgent({
+ name: 'fallback',
+ model: 'gemini-pro-latest',
+ instruction: 'You are the fallback agent.',
+});
+
+const router = (
+ agents: Readonly>,
+ context: InvocationContext,
+ // errorContext is provided when a previously selected agent fails
+ errorContext?: { failedKeys: ReadonlySet; lastError: unknown },
+) => {
+ if (!errorContext) {
+ return 'primary'; // Try primary first
+ }
+ if (errorContext.failedKeys.has('primary')) {
+ return 'fallback'; // Fall back if primary failed
+ }
+ return undefined; // No more options, propagate the error
+};
+
+const routedAgent = new RoutedAgent({
+ name: 'my_routed_agent',
+ agents: { primary: primaryAgent, fallback: fallbackAgent },
+ router,
+});
+// --8<-- [end:config]
+
+const runner = new InMemoryRunner({
+ agent: routedAgent,
+ appName: 'my_app',
+});
+
+const session = await runner.sessionService.createSession({
+ appName: 'my_app',
+ userId: 'user_1',
+});
+
+const run = runner.runAsync({
+ userId: 'user_1',
+ sessionId: session.id,
+ newMessage: { role: 'user', parts: [{ text: 'Who are you?' }] },
+});
+
+for await (const event of run) {
+ if (event.content?.parts?.[0]?.text) {
+ console.log(event.content.parts[0].text);
+ }
+}
diff --git a/examples/typescript/snippets/agents/routing/package.json b/examples/typescript/snippets/agents/routing/package.json
new file mode 100644
index 0000000000..64cf657306
--- /dev/null
+++ b/examples/typescript/snippets/agents/routing/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "adk-docs-examples",
+ "version": "1.0.0",
+ "description": "TS code examples for the ADK Documentation",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "build": "tsc",
+ "clean": "rm -rf dist"
+ },
+ "keywords": ["adk", "google", "agent", "typescript", "gemini"],
+ "author": "",
+ "license": "Apache-2.0",
+ "type": "commonjs",
+ "devDependencies": {
+ "@types/node": "^20.14.2",
+ "typescript": "^5.9.2"
+ },
+ "dependencies": {
+ "@google/adk": "^1.0.0",
+ "zod": "^3.24.0"
+ }
+}
diff --git a/examples/typescript/snippets/agents/routing/planning-mode.ts b/examples/typescript/snippets/agents/routing/planning-mode.ts
new file mode 100644
index 0000000000..4c4e8ad14d
--- /dev/null
+++ b/examples/typescript/snippets/agents/routing/planning-mode.ts
@@ -0,0 +1,83 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { InMemoryRunner } from '@google/adk';
+
+// --8<-- [start:config]
+import {
+ FunctionTool,
+ LlmAgent,
+ RoutedAgent,
+} from '@google/adk';
+import { z } from 'zod';
+
+const readFileTool = new FunctionTool({
+ name: 'read_file',
+ description: 'Reads content from a file.',
+ parameters: z.object({ filePath: z.string() }),
+ execute: (args) => ({ content: `Contents of ${args.filePath}` }),
+});
+
+const writeFileTool = new FunctionTool({
+ name: 'write_file',
+ description: 'Writes content to a file.',
+ parameters: z.object({ filePath: z.string(), content: z.string() }),
+ execute: (args) => ({ result: `Wrote to ${args.filePath}` }),
+});
+
+const basicAgent = new LlmAgent({
+ name: 'basic',
+ model: 'gemini-flash-latest',
+ instruction: 'You are a basic assistant. Use tools to help the user.',
+ tools: [readFileTool, writeFileTool],
+});
+
+const planningAgent = new LlmAgent({
+ name: 'planning',
+ model: 'gemini-flash-latest',
+ instruction: 'You are a planning expert. Analyze carefully. You can only read files.',
+ tools: [readFileTool],
+});
+
+// Toggle this to switch between basic and planning agents
+let planningMode = false;
+
+const routedAgent = new RoutedAgent({
+ name: 'my_routed_agent',
+ agents: { basic: basicAgent, planning: planningAgent },
+ router: () => (planningMode ? 'planning' : 'basic'),
+});
+// --8<-- [end:config]
+
+const runner = new InMemoryRunner({
+ agent: routedAgent,
+ appName: 'my_app',
+});
+
+const session = await runner.sessionService.createSession({
+ appName: 'my_app',
+ userId: 'user_1',
+});
+
+const run = runner.runAsync({
+ userId: 'user_1',
+ sessionId: session.id,
+ newMessage: { role: 'user', parts: [{ text: 'Read the file report.txt' }] },
+});
+
+for await (const event of run) {
+ if (event.content?.parts?.[0]?.text) {
+ console.log(event.content.parts[0].text);
+ }
+}
diff --git a/examples/typescript/snippets/agents/routing/tsconfig.json b/examples/typescript/snippets/agents/routing/tsconfig.json
new file mode 100644
index 0000000000..820f935898
--- /dev/null
+++ b/examples/typescript/snippets/agents/routing/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ /* Build Options */
+ "target": "es2020",
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "outDir": "./dist",
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+
+ /* Strictness */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+
+ /* Module Interop */
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/examples/typescript/snippets/runtime/cancel/basic-usage.ts b/examples/typescript/snippets/runtime/cancel/basic-usage.ts
new file mode 100644
index 0000000000..4de265eb17
--- /dev/null
+++ b/examples/typescript/snippets/runtime/cancel/basic-usage.ts
@@ -0,0 +1,52 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// --8<-- [start:full]
+import { Runner, InMemorySessionService, LlmAgent, FunctionTool } from '@google/adk';
+import { z } from 'zod';
+
+const getInfo = new FunctionTool({
+ name: 'get_info',
+ description: 'Gets information about a topic.',
+ parameters: z.object({ topic: z.string() }),
+ execute: (args) => ({ result: `Info about ${args.topic}` }),
+});
+
+const agent = new LlmAgent({
+ name: 'my_agent',
+ model: 'gemini-flash-latest',
+ instruction: 'Always use the get_info tool before answering.',
+ tools: [getInfo],
+});
+
+const sessionService = new InMemorySessionService();
+const runner = new Runner({ agent, appName: 'my_app', sessionService });
+const session = await sessionService.createSession({ appName: 'my_app', userId: 'user_1' });
+
+const controller = new AbortController();
+const run = runner.runAsync({
+ userId: session.userId,
+ sessionId: session.id,
+ newMessage: { role: 'user', parts: [{ text: 'Tell me about quantum computing.' }] },
+ abortSignal: controller.signal,
+});
+
+let count = 0;
+for await (const event of run) {
+ count++;
+ console.log('Event:', event.author);
+ controller.abort(); // Without this, 3+ events; with it, only 1.
+}
+console.log(`Done. Received ${count} event(s).`);
+// --8<-- [end:full]
diff --git a/examples/typescript/snippets/runtime/cancel/combined-signal.ts b/examples/typescript/snippets/runtime/cancel/combined-signal.ts
new file mode 100644
index 0000000000..9072a1bc16
--- /dev/null
+++ b/examples/typescript/snippets/runtime/cancel/combined-signal.ts
@@ -0,0 +1,59 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Runner, InMemorySessionService, LlmAgent, FunctionTool } from '@google/adk';
+import { z } from 'zod';
+
+const getInfo = new FunctionTool({
+ name: 'get_info',
+ description: 'Gets information about a topic.',
+ parameters: z.object({ topic: z.string() }),
+ execute: (args) => ({ result: `Info about ${args.topic}` }),
+});
+
+const agent = new LlmAgent({
+ name: 'my_agent',
+ model: 'gemini-flash-latest',
+ instruction: 'Always use the get_info tool before answering.',
+ tools: [getInfo],
+});
+
+const sessionService = new InMemorySessionService();
+const runner = new Runner({ agent, appName: 'my_app', sessionService });
+const session = await sessionService.createSession({ appName: 'my_app', userId: 'user_1' });
+
+// --8<-- [start:run]
+const controller = new AbortController();
+
+// Cancel on timeout OR programmatically via controller.abort()
+// e.g.: cancelButton.addEventListener('click', () => controller.abort());
+const combinedSignal = AbortSignal.any([
+ controller.signal,
+ AbortSignal.timeout(60_000),
+]);
+
+const run = runner.runAsync({
+ userId: session.userId,
+ sessionId: session.id,
+ newMessage: { role: 'user', parts: [{ text: 'Tell me about quantum computing.' }] },
+ abortSignal: combinedSignal,
+});
+// --8<-- [end:run]
+
+let count = 0;
+for await (const event of run) {
+ count++;
+ console.log('Event:', event.author);
+}
+console.log(`Done. Received ${count} event(s).`);
diff --git a/examples/typescript/snippets/runtime/cancel/custom-tool.ts b/examples/typescript/snippets/runtime/cancel/custom-tool.ts
new file mode 100644
index 0000000000..c2f62aea16
--- /dev/null
+++ b/examples/typescript/snippets/runtime/cancel/custom-tool.ts
@@ -0,0 +1,44 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// --8<-- [start:tool]
+import { FunctionTool } from '@google/adk';
+import { z } from 'zod';
+
+const fetchItems = async (id: string) => ['item1', 'item2', 'item3'];
+const processItem = async (item: string) => ({ processed: item });
+
+const longRunningTool = new FunctionTool({
+ name: 'process_data',
+ description: 'Processes data in multiple steps.',
+ parameters: z.object({
+ dataId: z.string(),
+ }),
+ execute: async (args, toolContext) => {
+ const items = await fetchItems(args.dataId);
+
+ const results = [];
+ for (const item of items) {
+ // Check the abort signal before each step
+ if (toolContext?.abortSignal?.aborted) {
+ return { status: 'cancelled', processed: results.length };
+ }
+
+ results.push(await processItem(item));
+ }
+
+ return { status: 'complete', processed: results.length };
+ },
+});
+// --8<-- [end:tool]
diff --git a/examples/typescript/snippets/runtime/cancel/package.json b/examples/typescript/snippets/runtime/cancel/package.json
new file mode 100644
index 0000000000..64cf657306
--- /dev/null
+++ b/examples/typescript/snippets/runtime/cancel/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "adk-docs-examples",
+ "version": "1.0.0",
+ "description": "TS code examples for the ADK Documentation",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "build": "tsc",
+ "clean": "rm -rf dist"
+ },
+ "keywords": ["adk", "google", "agent", "typescript", "gemini"],
+ "author": "",
+ "license": "Apache-2.0",
+ "type": "commonjs",
+ "devDependencies": {
+ "@types/node": "^20.14.2",
+ "typescript": "^5.9.2"
+ },
+ "dependencies": {
+ "@google/adk": "^1.0.0",
+ "zod": "^3.24.0"
+ }
+}
diff --git a/examples/typescript/snippets/runtime/cancel/timeout.ts b/examples/typescript/snippets/runtime/cancel/timeout.ts
new file mode 100644
index 0000000000..34360ae3ee
--- /dev/null
+++ b/examples/typescript/snippets/runtime/cancel/timeout.ts
@@ -0,0 +1,50 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Runner, InMemorySessionService, LlmAgent, FunctionTool } from '@google/adk';
+import { z } from 'zod';
+
+const getInfo = new FunctionTool({
+ name: 'get_info',
+ description: 'Gets information about a topic.',
+ parameters: z.object({ topic: z.string() }),
+ execute: (args) => ({ result: `Info about ${args.topic}` }),
+});
+
+const agent = new LlmAgent({
+ name: 'my_agent',
+ model: 'gemini-flash-latest',
+ instruction: 'Always use the get_info tool before answering.',
+ tools: [getInfo],
+});
+
+const sessionService = new InMemorySessionService();
+const runner = new Runner({ agent, appName: 'my_app', sessionService });
+const session = await sessionService.createSession({ appName: 'my_app', userId: 'user_1' });
+
+// --8<-- [start:run]
+const run = runner.runAsync({
+ userId: session.userId,
+ sessionId: session.id,
+ newMessage: { role: 'user', parts: [{ text: 'Tell me about quantum computing.' }] },
+ abortSignal: AbortSignal.timeout(2_000), // Cancel after 2 seconds
+});
+
+let count = 0;
+for await (const event of run) {
+ count++;
+ console.log('Event:', event.author);
+}
+console.log(`Done. Received ${count} event(s).`);
+// --8<-- [end:run]
diff --git a/examples/typescript/snippets/runtime/cancel/tsconfig.json b/examples/typescript/snippets/runtime/cancel/tsconfig.json
new file mode 100644
index 0000000000..820f935898
--- /dev/null
+++ b/examples/typescript/snippets/runtime/cancel/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ /* Build Options */
+ "target": "es2020",
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "outDir": "./dist",
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+
+ /* Strictness */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+
+ /* Module Interop */
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/lychee.toml b/lychee.toml
index 86510cd5b0..d7877294ec 100644
--- a/lychee.toml
+++ b/lychee.toml
@@ -2,8 +2,10 @@
# https://github.com/lycheeverse/lychee
# Accepted HTTP status codes
-# 403 is included to avoid false positives from bot-blocking sites
-accept = ["200", "206", "302", "403"]
+accept = ["200", "206", "302", "403", "429"]
+
+# Website timeout in seconds
+timeout = 30
# Exclude localhost/loopback links (used in code examples throughout the docs)
exclude_loopback = true
diff --git a/mkdocs.yml b/mkdocs.yml
index bbe5d6fe57..85d69fc8f1 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -298,6 +298,7 @@ nav:
- Parallel agents: agents/workflow-agents/parallel-agents.md
- Custom agents: agents/custom-agents.md
- Multi-agent systems: agents/multi-agents.md
+ - Agent routing: agents/routing.md
- Agent Config: agents/config.md
- Models for Agents:
- agents/models/index.md
@@ -306,6 +307,7 @@ nav:
- Claude: agents/models/anthropic.md
- Agent Platform hosted: agents/models/agent-platform.md
- Apigee AI Gateway: agents/models/apigee.md
+ - Model routing: agents/models/routing.md
- Ollama: agents/models/ollama.md
- vLLM: agents/models/vllm.md
- LiteLLM: agents/models/litellm.md
@@ -332,6 +334,7 @@ nav:
- API Server: runtime/api-server.md
- Ambient Agents: runtime/ambient-agents.md
- Resume Agents: runtime/resume.md
+ - Cancel Agent Runs: runtime/cancel.md
- Runtime Config: runtime/runconfig.md
- Event Loop: runtime/event-loop.md
- Deployment:
@@ -346,6 +349,7 @@ nav:
- Observability:
- observability/index.md
- Logging: observability/logging.md
+ - Metrics: observability/metrics.md
- Traces: observability/traces.md
- Evaluation:
- evaluate/index.md