> ## 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](/sdks/python/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](/sdks/python/google-adk).

## Migration steps

### Step 1 — Register the agent

In the [dashboard](https://app.dev.swarmd.ai) (or via
`POST /registry/v1/agents`, body `{"name": "...", "description": "..."}`),
register `weather_agent`. Capture the three values 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.

<Note>
  Registration is two calls, and this is only the first. What you hold now is an
  *unbootstrapped shell* — an identity with no agent card behind it. It can mint
  tokens, which is all you need for the rest of this migration, but it cannot be
  subscribed to anything and cannot be sent a webhook until you attach a card
  with `PUT /registry/v1/agents/{agentId}`.

  Do that once the migrated agent is running on a URL Swarmd can reach — it is
  [Step 7 of Setup](/sdks/python/setup#step-7-attach-your-agent-card), and it has
  to land before Step 8 below will work.
</Note>

### 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.dev.swarmd.ai
SWARMD_TOKEN_URL=https://auth.dev.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](/sdks/python/google-adk#dissected-create_llm_agent).

**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](/sdks/python/google-adk#dissected-serve).

**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
   `[remote-agents] discovered N subscribed agent(s)` and
   `[mcp-tools] 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 `[mcp-tools] loaded N MCP toolset(s)` reappear in the logs
   with the new count. 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 the `time_agent` example
under `agents/time_agent` in the platform repository — a complete agent in
\~30 lines of `main.py`.

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.