Migrating from LangChain
This guide walks through migrating an existing raw LangChain agent onto SwarmD using theswarmd-langchain package. By the end your
agent’s main.py contains only domain logic — the tools, the prompt,
and the model choice — and swarmd-langchain owns every platform
concern.
If you have not read LangChain on SwarmD 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 LangChain) and the after
(SwarmD) side by side. The shape mirrors
Migrating from Google ADK on purpose — the
helper trio is the same.
What you are migrating away from
A typical standalone LangChain agent — one file owning everything: the LangChain tool functions, hand-rolled HTTP tools that POST to other agents directly, the LLM, and (if it serves over A2A at all) a custom Starlette adapter.main.py
- Sub-agent URLs and request shapes are baked into source. Each
call_*_agenttool reinvents request/response, error handling, and retries. - No A2A protocol. Your “tools” call arbitrary endpoints — the downstream might be A2A, REST, or something else, and your code has to know.
- No polling for long-running tasks. If the downstream returns a
workingtask, your tool either blocks forever or returns a useless intermediate payload. - No identity between agents.
- No central audit, policy, or HITL — the relay isn’t in the path.
- MCP tools aren’t there at all.
get_weather) and the agent’s identity (system prompt, model)
exactly as they are while deleting every call_*_agent tool and the
bespoke Starlette / FastAPI wrapper.
What you are migrating to
main.py
Migration steps
Step 1 — Register the agent
In the dashboard (or viaPOST /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.
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, and it has
to land before Step 8 below will work.Step 2 — Install the SDK
swarmd-langchain
declares compatible ranges, not exact versions.
After this step: from swarmd_langchain import create_runtime, create_llm_agent, serve resolves.
Step 3 — Add platform credentials to .env
Append to your existing .env:
.env
create_runtime() will
attach the agent to SwarmD. With them unset, the agent still runs
locally as a plain LangChain agent against direct OpenAI — useful for
local dev without a SwarmD account.
Step 4 — Replace bootstrap with create_runtime()
runtime and pass it into the next two helpers.
Step 5 — Delete the hand-rolled HTTP tools
httpx, you don’t write retry logic, and you don’t
type the downstream URL anywhere in source.
After this step: half the surface area of main.py disappears.
This is the highest-leverage change in the migration.
Step 6 — Replace create_agent(...) with create_llm_agent(runtime, ...)
instruction, and the agent gets a stable
name and description — these show up on its A2A card, in audit
logs, and in the dashboard.
For exactly what create_llm_agent does internally, see
the helper reference.
After this step: the returned CompiledStateGraph is a regular
LangGraph with local tools + remote sub-agent tools + MCP tools
already wired in. You can still call agent.ainvoke({"messages": [...]}) against it exactly as before.
Step 7 — Replace your A2A wrapper with serve(agent, runtime)
If you previously hand-rolled a Starlette / FastAPI server to expose
the LangChain agent over HTTP, delete it. If you weren’t exposing the
agent over HTTP at all (you were calling agent.ainvoke(...) from
another process), serve is what makes the migration’s value land —
it gives your agent the same A2A surface every SwarmD agent has, so
the relay can route to it like any other.
serve mounts (A2A executor, agent card, /admin,
correlation middleware, task store, refresh-in-place), see
the helper reference.
After this step: the agent process exposes the A2A protocol on
HOST:PORT, 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 subscribeweather_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. Provided the runtime has the webhook secret (see the warning in Step 3
above), your running agent verifies
the HMAC, rebuilds the LangGraph from the recipe stashed by create_llm_agent,
and the new PollingA2aTool (or MCP tool) appears in the next LLM call’s tool
list. No restart. Without the secret, the delivery is refused with 503 and
you have to POST /admin/refresh by hand after each grant change.
Step 9 — Verify and roll out
In order, before flipping production traffic:- Boot the agent locally with
SWARMD_*vars set. Look for the banner with the agent name, thenFound N subscribed agentsandLoaded N MCP tool(s) from M server(s)in the logs. Check credentials and base URL if either is missing. curl /.well-known/agent-card.json— the response should match the agent’s name, description, and at least the synthetic skill.- Send a message that exercises a sub-agent via
POST /(JSON-RPCmessage/send). Confirm the LLM picks the rightPollingA2aTool, that the call shows up in the audit log under a single correlation ID, and that the response makes its way back even if the downstream went through aworkingstate first. - Send a message that exercises an MCP tool. Confirm the same.
- Rotate a subscription in the dashboard. Watch
/admin/webhookfire andLoaded N MCP tool(s) from M server(s)reappear in the logs with the new count. No restart. If the webhook secret never reached the runtime you’ll see a503instead —POST /admin/refreshto confirm the rebuild itself works. - 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.pyshrinks to domain logic. No more bespoke HTTP tools, no more hand-rolled FastAPI / Starlette server.- 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. Each
one is a standard LangChain
BaseTool. - Long-running sub-agents stop blocking your LLM —
PollingA2aTooldrivestasks/getuntil terminal. /admin/webhookkeeps the agent’s tool catalogue in sync without restarts.
- Your
@tool-decorated functions. - Your
agent.ainvoke({"messages": [...]})call shape from anything that already calls the agent locally. - Your existing LangChain integrations on the graph (chat history,
memory, structured output) — they live on the
CompiledStateGraphreturned bycreate_llm_agent. - Local-only behaviour: with
SWARMD_*unset the agent runs as a standalone LangChain agent against direct OpenAI.
