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), 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
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. 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.
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+/-1 MCP tool(s)reappear in logs. No restart. - 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.
