> ## Documentation Index
> Fetch the complete documentation index at: https://docs.swarmd.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Google ADK

> Build a Google ADK agent on SwarmD with swarmd-google-adk

# Google ADK on SwarmD

`swarmd-google-adk` is a thin wrapper on top of [Google ADK](https://google.github.io/adk-docs/)
that gives a Google ADK `LlmAgent` access to the SwarmD platform: OAuth2
authentication, sub-agent discovery, MCP tool discovery, polling for
long-running sub-agents, an A2A server, signed-webhook refresh, and an
admin surface for operators.

If you are already running a Google ADK agent and want to move it onto
SwarmD, read this first to learn the helpers, then follow
[Migrating from Google ADK](./migration-google-adk). If you are starting
from scratch this page is the whole story.

## What it gives you

The SDK exposes three helpers that, in order, produce a complete agent:

| Helper                           | Returns               | What it does                                                                                                                                                    |
| -------------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create_runtime()`               | `SwarmDRuntime`       | Reads `SWARMD_*` env vars, configures a runtime, and instantiates token managers (platform-API + MCP-relay).                                                    |
| `create_llm_agent(runtime, ...)` | Google ADK `LlmAgent` | Picks the LLM provider, discovers subscribed sub-agents, discovers subscribed MCP servers, wires both into the `LlmAgent`'s `sub_agents` and `tools`.           |
| `serve(agent, runtime)`          | runs forever          | Mounts the A2A protocol, the agent card, the `/admin` surface, correlation middleware, and starts uvicorn. Refreshes the catalogue in place on signed webhooks. |

The full agent file is usually 20–30 lines: a tool function, three calls,
and an `if __name__ == "__main__"` guard.

## Installation

```bash theme={null}
pip install swarmd-google-adk
```

That pulls in `swarmd-sdk` as a transitive dependency plus `google-adk`,
`a2a-sdk`, and the relay HTTP client.

## Credentials

Every agent registered on SwarmD receives three secrets:

* **`agentId`** — the agent's UUID and OAuth2 client ID.
* **`clientSecret`** — used for outbound calls (registry, A2A relay, MCP,
  LLM gateway).
* **`webhookSecret`** — used by the platform to sign inbound webhooks to
  your agent's `/admin/webhook` endpoint.

All three are shown **once** in the [dashboard](https://app.swarmd.ai) or
in the `POST /registry/v1/agents` response. Store them with your
deployment configuration. The webhook secret is technically optional —
outbound calls still work without it — but without it your agent has to
be manually refreshed (`POST /admin/refresh`) every time a subscription
or grant changes upstream.

## Environment

```bash .env theme={null}
# LLM provider — pick one (gateway preferred)
SWARMD_LLM_GATEWAY_ID=     # UUID of a configured LLM gateway, or
OPENAI_API_KEY=sk-...      # direct OpenAI fallback
OPENAI_MODEL=gpt-4o        # optional, default gpt-4o
OPENAI_TEMPERATURE=0       # optional; when set, enables deterministic sampling

# SwarmD platform credentials
SWARMD_AGENT_ID=00000000-0000-0000-0000-000000000000
SWARMD_CLIENT_SECRET=...
SWARMD_WEBHOOK_SECRET=...
SWARMD_BASE_URL=https://api.swarmd.ai
SWARMD_TOKEN_URL=https://auth.swarmd.ai/realms/swarmd/protocol/openid-connect/token

# Server (optional)
HOST=0.0.0.0
PORT=8080
LOG_LEVEL=INFO
```

The four `SWARMD_AGENT_ID` / `SWARMD_CLIENT_SECRET` / `SWARMD_BASE_URL` /
`SWARMD_TOKEN_URL` values are the platform-attached set. Leave any of
them unset and the runtime stays in "standalone mode" — the agent still
runs locally, it just won't see any subscribed sub-agents or MCP tools.
This is useful for local development without a SwarmD account.

## The whole agent, end to end

```python main.py theme={null}
from dotenv import load_dotenv
load_dotenv()

from swarmd_google_adk import create_llm_agent, create_runtime, serve


def get_time(city: str) -> str:
    """Get the current time for any city."""
    return f"The time in {city} is 18:00"


runtime = create_runtime()
agent = create_llm_agent(
    runtime,
    name="time_agent",
    description="A simple agent that provides time information",
    instruction=(
        "You are a helpful time agent. Use the get_time tool to tell users "
        "the time in any city they ask about. You can also collaborate with "
        "other agents and call MCP tools for additional capabilities."
    ),
    tools=[get_time],
)


if __name__ == "__main__":
    serve(agent, runtime)
```

Run it:

```bash theme={null}
python3 main.py
```

You should see a banner with the agent name and bind address, then —
once the runtime is configured — `Found N subscribed agents` and
`Loaded N MCP toolset(s)` in the logs.

## The three helpers, in detail

### `create_runtime()`

Builds a `SwarmDRuntime` object and, if the four `SWARMD_*` vars are
set, calls `runtime.configure(...)`. That instantiates:

* A platform-API `SwarmDClient` for registry and audit calls.
* An `McpClient` for MCP discovery.
* Two `TokenManagerProxy` objects — one for the platform-API audience
  (`swarmd:api`), one for the MCP relay audience (`mcp:call`). Both do
  double-check-locked OAuth2 client-credentials grants with caching,
  jittered backoff retries on 5xx, and a single 401 retry.

You never instantiate or thread these directly — every other helper
grabs them off the runtime when it needs to mint a bearer.

### `create_llm_agent(runtime, name, description, instruction, tools, *, generate_content_config=None)`

Returns a regular Google ADK `LlmAgent` you can pass around like any
other ADK agent. It does five things on top of `LlmAgent(...)`:

1. **Picks the LLM provider.**
   * If `SWARMD_LLM_GATEWAY_ID` is set, LiteLlm is built with `api_base`
     pointed at `${SWARMD_BASE_URL}/llm/v1/${id}` and `api_key` set to
     the agent's bearer — every LLM call flows through the relay and
     shows up in audit. No provider API key needed.
   * If only `OPENAI_API_KEY` is set, LiteLlm goes direct to OpenAI.
   * If neither is set the call raises `ValueError` rather than booting
     in a broken state.
2. **Discovers sub-agents.** Calls `fetch_remote_agents(runtime)`, which
   pulls subscriptions from `GET /registry/v1/agents/{id}/subscriptions`
   and wraps each one in a `PollingRemoteA2aAgent`. The polling wrapper
   is the important bit: when a sub-agent returns a non-terminal task
   state (`working`, `submitted`, `input_required`, `auth_required`) the
   wrapper polls `tasks/get` against the relay until it reaches a
   terminal state, then surfaces the final result to the parent LLM as
   a normal Event. ADK's stock `RemoteA2aAgent` just returns the
   non-terminal payload, which the LLM can't do anything useful with.
3. **Discovers MCP servers.** Calls `fetch_mcp_tools(runtime)`, which
   lists MCP grants via the SDK's `McpClient`, then builds one ADK
   `McpToolset` per granted server pointed at the relay's MCP proxy
   (`/relay/v1/mcp-servers/{id}/mcp`). Tools are namespaced with a
   sanitised, UUID-seeded prefix so a free-form server name like
   `"GitLab - swarmd.ai"` can't produce a tool name the LLM provider
   rejects. A header provider mints a fresh `mcp:call`-scoped token on
   every request and propagates the current correlation ID.
4. **Honours `OPENAI_TEMPERATURE`.** When set, it becomes a
   `GenerateContentConfig(temperature=...)` automatically. Pass
   `generate_content_config=...` to override.
5. **Composes the catalogue.** Local tools you passed in + remote
   sub-agents (as `PollingRemoteA2aAgent` in `sub_agents`) + MCP
   toolsets (as `McpToolset` entries in `tools`).

If you want fine-grained control — your own header injector on the MCP
client, a custom polling interval, hand-built `RemoteA2aAgent`s — drop
down to `create_runtime`, `fetch_remote_agents`, and `fetch_mcp_tools`
directly and build the `LlmAgent` yourself. They are all exported.

### `serve(agent, runtime)`

The thickest of the three helpers because the A2A server is the agent's
public face. In order, it:

1. **Mounts the A2A protocol.** Builds an ADK `AgentCardBuilder`,
   serialises the card at `/.well-known/agent-card.json`, attaches
   `A2AStarletteApplication` (which owns `message/send`,
   `message/stream`, `tasks/get`, `tasks/cancel`, etc.), and wires
   `A2aAgentExecutor` so the protocol calls reach your `LlmAgent`.
2. **Persists tasks and sessions.** A SQLite-backed `DatabaseTaskStore`
   and `DatabaseSessionService` are created at `/tmp/{agent}_tasks.db`
   and `/tmp/{agent}_sessions.db`. Enough for local dev; in production
   point them at a real database by overriding the create calls inside
   your own copy of `serve` if you need durability across pod restarts.
3. **Adds correlation propagation.** A `CorrelationIdMiddleware`
   extracts `X-Correlation-Id` off inbound A2A calls into a
   `ContextVar`, and the SDK's outbound interceptors copy that var onto
   every downstream bearer-tokened request. One request through the
   relay shows up as one trace ID end-to-end in the audit log.
4. **Mounts `/admin`.** Four endpoints —
   `POST /admin/configure`, `POST /admin/refresh`, `POST /admin/webhook`,
   `GET /admin/status`. `/admin/webhook` is the platform-driven kick:
   when a subscription, MCP grant, agent lifecycle, or tenant
   lifecycle event fires, the platform POSTs a signed payload, the SDK
   verifies the HMAC against `SWARMD_WEBHOOK_SECRET`, and calls the
   same refresh path as `/admin/refresh`.
5. **Refreshes in place.** The refresh callback re-runs
   `fetch_remote_agents` and `fetch_mcp_tools`, then swaps the
   `LlmAgent`'s `sub_agents` and `tools` lists atomically. Local Python
   tools you passed into `create_llm_agent` are preserved across the
   swap. No restart needed when subscriptions change.
6. **Starts uvicorn** on `HOST:PORT` (defaults `0.0.0.0:8080`) with
   `LOG_LEVEL` from env.

## Verifying it works

```bash theme={null}
# Agent card with discovered sub-agents and tools
curl http://localhost:8080/.well-known/agent-card.json

# A2A message via JSON-RPC
curl -X POST http://localhost:8080/ \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "message/send",
    "params": {
      "message": {
        "role": "user",
        "parts": [{"kind": "text", "text": "What time is it in Tokyo?"}],
        "messageId": "11111111-1111-1111-1111-111111111111"
      }
    }
  }'

# Admin status (configured? agent_id?)
curl http://localhost:8080/admin/status
```

If the agent card response is missing sub-agents or MCP tools you
expect to see, check that the subscription is live in the dashboard and
that `Found N subscribed agents` / `Loaded N MCP toolset(s)` appear in
the agent's startup logs.

## Advanced — using the SDK pieces directly

If you need to deviate from what `create_llm_agent` and `serve` do, drop
down to the underlying pieces. The whole surface area is exported:

```python theme={null}
from swarmd_google_adk import (
    create_runtime,
    fetch_remote_agents,   # returns List[PollingRemoteA2aAgent]
    fetch_mcp_tools,        # returns List[BaseToolset]
    PollingRemoteA2aAgent,  # ADK RemoteA2aAgent + polling
    create_a2a_client_factory,
)
from swarmd_sdk import create_admin_app, SwarmDRuntime
```

Common reasons to drop down:

* **Custom polling interval / max wait.** Build `PollingRemoteA2aAgent`
  instances yourself with `poll_interval=` and `max_wait=`.
* **Hand-built `LlmAgent` config.** Use the discovery functions to get
  the lists, then assemble `LlmAgent(...)` with whatever extra
  callbacks, structured-output config, or custom tools you need.
* **Custom A2A server.** Use `create_admin_app(runtime, on_refresh=...)`
  for the `/admin` surface and write your own Starlette / FastAPI
  bootstrap around it. The refresh callback is yours to define.

## Reference implementation

See the [`time_agent`](https://github.com/swarmd-ai/swarmd/tree/main/agents/time_agent)
in the repo — a complete agent in \~30 lines of `main.py`. It is the
canonical "smallest working agent" example and the file you should
diff against when you want to know what a clean migration looks like.

## Next steps

* [Migrating from Google ADK](./migration-google-adk) — step-by-step
  guide for moving an existing raw-ADK agent onto this SDK.
* [LangChain on SwarmD](./langchain) — the same shape for LangChain.
* [Configuration](./configuration) — env var reference and advanced
  options on the base `swarmd-sdk`.
