> ## 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.

# Migrating from Google ADK

> Step-by-step guide for moving a raw Google ADK agent onto SwarmD

# Migrating from Google ADK

This guide walks through migrating an existing raw Google ADK agent
onto SwarmD using the `swarmd-google-adk` package. By the end your
agent's `main.py` contains only domain logic — the tool functions, the
description, and the instruction — and `swarmd-google-adk` owns every
platform concern.

If you have not read [Google ADK on SwarmD](./google-adk) yet, do that
first. It explains what each of `create_runtime`, `create_llm_agent`,
and `serve` actually does. This guide focuses on the *migration
journey* — what to delete, what to replace, and how to roll it out —
rather than re-explaining the helpers.

Each step shows the **before** (raw Google ADK) and the **after**
(SwarmD) side by side. Read top to bottom the first time, then use it
as a checklist.

## What you are migrating away from

A typical standalone Google ADK agent — one file owning everything: the
tool function, the LLM config, hardcoded `RemoteA2aAgent` URLs for any
sub-agents, the FastAPI app, and uvicorn startup.

```python main.py theme={null}
import os

import uvicorn
from dotenv import load_dotenv
from google.adk.a2a.utils.agent_to_a2a import to_a2a
from google.adk.agents import LlmAgent
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
from google.adk.models.lite_llm import LiteLlm

load_dotenv()


def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"The weather in {city} is sunny"


# Every remote agent URL is hardcoded into the source
time_agent = RemoteA2aAgent(
    name="time_agent",
    description="Provides time information",
    agent_card="https://time-agent.internal:8080/.well-known/agent-card.json",
)

calendar_agent = RemoteA2aAgent(
    name="calendar_agent",
    description="Manages calendar events",
    agent_card="https://calendar-agent.internal:8080/.well-known/agent-card.json",
)

agent = LlmAgent(
    model=LiteLlm(model="openai/gpt-4o"),
    name="weather_agent",
    description="A weather agent that collaborates with other agents",
    instruction="You are a helpful weather agent.",
    tools=[get_weather],
    sub_agents=[time_agent, calendar_agent],
)

app = to_a2a(agent, port=8080)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", "8080")))
```

Pain points that compound the moment this leaves your laptop:

* Sub-agent URLs are baked into source — adding, removing, or moving a
  sub-agent requires a code change and redeploy.
* No identity between agents. Every outbound A2A call is
  unauthenticated unless you write your own header-injection layer.
* No central audit, policy, or HITL — the relay isn't in the path.
* Long-running sub-agents block the LLM. `RemoteA2aAgent` returns
  whatever it got; if the downstream returns `working`, the parent has
  no way to wait for the terminal state.
* MCP tools aren't there at all.

The goal of the migration is to keep the tool function (`get_weather`)
and the agent's identity (name, description, instruction) exactly as
they are while replacing every other line with one of three SDK
helpers.

## What you are migrating to

```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_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"The weather in {city} is sunny"


runtime = create_runtime()
agent = create_llm_agent(
    runtime,
    name="weather_agent",
    description="A weather agent that collaborates with other agents",
    instruction="You are a helpful weather agent.",
    tools=[get_weather],
)

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

That's the whole file. For what each helper does under the hood, see
[Google ADK on SwarmD](./google-adk).

## Migration steps

### Step 1 — Register the agent

In the [dashboard](https://app.swarmd.ai) (or via
`POST /registry/v1/agents`), register `weather_agent`. Capture the
three secrets shown once in the response: `agentId`, `clientSecret`,
and `webhookSecret`. Store them with your deployment configuration.

**After this step:** the platform knows your agent exists. You have
the OAuth2 credentials it will use for outbound calls.

### Step 2 — Install the SDK

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

You can keep your existing `google-adk` pin — `swarmd-google-adk`
declares a compatible range, not an exact version.

**After this step:** `from swarmd_google_adk import create_runtime,
create_llm_agent, serve` resolves.

### Step 3 — Add platform credentials to `.env`

Append to your existing `.env`:

```bash .env theme={null}
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

# Optional: route LLM calls through a SwarmD gateway instead of direct OpenAI
SWARMD_LLM_GATEWAY_ID=     # UUID of a configured LLM gateway
```

**After this step:** with these vars set, `create_runtime()` will
attach the agent to SwarmD. With them unset, the agent still runs
locally in "standalone mode" — useful for local dev without a SwarmD
account.

### Step 4 — Replace bootstrap with `create_runtime()`

```python theme={null}
# Before — load .env, then everything below is hand-wired
load_dotenv()

# After
load_dotenv()
from swarmd_google_adk import create_runtime
runtime = create_runtime()
```

**After this step:** the runtime owns auth, token caching, registry
calls, MCP discovery, and correlation-ID propagation. You hold one
reference to `runtime` and pass it into the next two helpers.

### Step 5 — Delete the hardcoded `RemoteA2aAgent` blocks

```python theme={null}
# Before — every remote agent has a hardcoded card URL
time_agent = RemoteA2aAgent(name=..., agent_card="https://...")
calendar_agent = RemoteA2aAgent(name=..., agent_card="https://...")

# After — gone. Sub-agents arrive via subscription discovery.
```

If a downstream URL was used anywhere else (a helper script, a test,
a deployment manifest), audit and remove those too. The next step
shows what replaces them.

**After this step:** there is nothing in your source tree that knows
the URL of another agent. The registry is the source of truth.

### Step 6 — Replace `LlmAgent(...)` with `create_llm_agent(runtime, ...)`

```python theme={null}
# Before
agent = LlmAgent(
    model=LiteLlm(model="openai/gpt-4o"),
    name="weather_agent",
    description="A weather agent that collaborates with other agents",
    instruction="You are a helpful weather agent.",
    tools=[get_weather],
    sub_agents=[time_agent, calendar_agent],
)

# After
agent = create_llm_agent(
    runtime,
    name="weather_agent",
    description="A weather agent that collaborates with other agents",
    instruction="You are a helpful weather agent.",
    tools=[get_weather],
)
```

Notice what disappears from the call: no `model=` (the SDK picks
LiteLlm with the right config — gateway-routed if
`SWARMD_LLM_GATEWAY_ID` is set, direct OpenAI otherwise), no
`sub_agents=` (discovered from subscriptions), and no MCP toolset
construction (also discovered).

For exactly what `create_llm_agent` does internally, see
[the helper reference](./google-adk#create_llm_agent_runtime_name_description_instruction_tools).

**After this step:** the returned `LlmAgent` is a regular ADK agent
with `sub_agents` and `tools` already populated from your
subscriptions. You can still set `LlmAgent` callbacks or structured
output on it before passing to `serve()`.

### Step 7 — Replace `to_a2a(...) + uvicorn.run(...)` with `serve(agent, runtime)`

```python theme={null}
# Before
app = to_a2a(agent, port=8080)
uvicorn.run(app, host="0.0.0.0", port=8080)

# After
serve(agent, runtime)
```

For exactly what `serve` mounts (A2A protocol, agent card, `/admin`,
correlation middleware, task store, refresh-in-place), see
[the helper reference](./google-adk#serve_agent_runtime).

**After this step:** the agent process exposes the same A2A protocol
ADK gave you, plus the `/admin` surface SwarmD uses to push refresh
events.

### Step 8 — Subscribe to downstream agents and MCP servers

Configuration only — no code change. In the dashboard subscribe
`weather_agent` to the sub-agents and MCP servers it needs (the same
ones you used to hardcode in Step 5). The dashboard's
`POST /registry/v1/agents/{id}/subscriptions` endpoint works too.

**After this step:** the platform fires a `SUBSCRIPTION_CHANGED`
webhook. Your running agent verifies the HMAC, calls
`fetch_remote_agents` and `fetch_mcp_tools` again, and the new
sub-agent or MCP tool appears in the LLM's catalogue without a
restart.

### Step 9 — Verify and roll out

In order, before flipping production traffic:

1. **Boot the agent locally with `SWARMD_*` vars set.** Look for the
   banner with the agent name, then `Found N subscribed agents` and
   `Loaded N MCP toolset(s)` in the logs. Check credentials and base
   URL if either is missing.
2. **`curl /.well-known/agent-card.json`** — the response should
   include the sub-agent and MCP-tool names you subscribed to.
3. **Send a message that exercises a sub-agent** via
   `POST /` (JSON-RPC `message/send`). Confirm the LLM picks the right
   sub-agent, that the call shows up in the audit log under a single
   correlation ID, and that the response makes its way back.
4. **Send a message that exercises an MCP tool.** Confirm the same.
5. **Rotate a subscription in the dashboard.** Watch `/admin/webhook`
   fire and `Loaded N+/-1 MCP toolset(s)` reappear in logs. No
   restart.
6. **Roll out.** The migration is purely additive on the agent
   process — the same binary runs unconfigured (standalone) or
   configured (SwarmD-attached) depending on env, so you can canary
   it.

## Summary

**What changed.**

* `main.py` shrinks to domain logic. No more bootstrap, no more
  hardcoded URLs, no more manual `to_a2a`.
* Outbound calls go through the relay. You get audit, policy, HITL,
  and rate limits for free.
* Sub-agents and MCP tools are discovered from subscriptions.
* LLM calls can optionally go through a SwarmD LLM gateway.
* `/admin/webhook` keeps the agent's catalogue in sync without
  restarts.

**What didn't.**

* Your tool function signatures.
* Your `LlmAgent`'s name, description, and instruction.
* The A2A protocol your agent speaks — both sides are still A2A
  0.3.0.
* Local-only behaviour: with `SWARMD_*` unset the agent runs as a
  standalone ADK agent against direct OpenAI.

For a reference implementation of the end state, see
[`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`.

For the LangChain equivalent of this guide, see
[Migrating from LangChain](./migration-langchain).
