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

# LangChain

> Build a LangChain agent on SwarmD with swarmd-langchain

# LangChain on SwarmD

`swarmd-langchain` is a thin wrapper on top of
[LangChain](https://python.langchain.com/) and
[LangGraph](https://langchain-ai.github.io/langgraph/) that gives a
LangChain agent access to the SwarmD platform: OAuth2 authentication,
sub-agent discovery (as `BaseTool`s), 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 LangChain agent and want to move it onto
SwarmD, read this first to learn the helpers, then follow
[Migrating from LangChain](./migration-langchain). If you are starting
from scratch this page is the whole story.

The shape mirrors [Google ADK on SwarmD](./google-adk) on purpose — the
helper trio (`create_runtime`, `create_llm_agent`, `serve`) is the
same.

## 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, ...)` | LangGraph `CompiledStateGraph` | Picks the model, discovers subscribed sub-agents (as `PollingA2aTool` instances), discovers subscribed MCP servers, compiles a LangGraph with the combined toolset.                   |
| `serve(agent, runtime)`          | runs forever                   | Wraps the graph in an A2A executor, builds the agent card, mounts the `/admin` surface, correlation middleware, and starts uvicorn. Recompiles the graph in place on signed webhooks. |

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

## Installation

```bash theme={null}
pip install swarmd-langchain
```

That pulls in `swarmd-sdk` as a transitive dependency, plus `langchain`,
`langgraph`, `langchain-openai`, `langchain-mcp-adapters`, and
`a2a-sdk`.

## 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
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o        # optional, default gpt-4o

# 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 as a plain LangChain agent against direct OpenAI, it just
won't see any subscribed sub-agents or MCP tools.

## The whole agent, end to end

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

from langchain_core.tools import tool

from swarmd_langchain import create_llm_agent, create_runtime, serve


@tool
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 tool(s) from M server(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)`

Returns a LangGraph `CompiledStateGraph` you can `ainvoke` like any
other LangGraph. It does five things on top of
`langchain.agents.create_agent(...)`:

1. **Picks the model.** Reads `OPENAI_MODEL` (default `gpt-4o`) and
   `OPENAI_API_KEY` and instantiates `ChatOpenAI` with `temperature=0`.
   If you need a different temperature or a different provider, drop
   down to `fetch_remote_agents` + `fetch_mcp_tools` and assemble the
   graph yourself — they are all exported.
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 `PollingA2aTool` — a LangChain `BaseTool`. The tool itself owns
   A2A `message/send` and polls `tasks/get` against the relay until
   the downstream task reaches a terminal state. So a long-running
   sub-agent doesn't block your LLM and your tool never returns a
   stale intermediate payload.
3. **Discovers MCP servers.** Calls `fetch_mcp_tools(runtime)`, which
   lists MCP grants via the SDK's `McpClient`, then connects to each
   granted server via `langchain-mcp-adapters`
   (`MultiServerMCPClient`), 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 OpenAI rejects.
   An `httpx.Auth` impl mints a fresh `mcp:call`-scoped token on every
   request and propagates the current correlation ID.
4. **Compiles the LangGraph.** Local tools + remote sub-agent tools +
   MCP tools are concatenated and handed to
   `langchain.agents.create_agent` with your `instruction` as the
   system prompt and your `name` as the graph name.
5. **Stashes the recipe on the graph.** Attaches `swarmd_name`,
   `swarmd_description`, `swarmd_instruction`, and
   `swarmd_local_tools` to the returned `CompiledStateGraph` so
   `serve()` can rebuild the graph in place when subscriptions change,
   without you having to pass everything through again.

If you want fine-grained control — a custom model, structured outputs,
a memory layer — drop down to `runtime`, `fetch_remote_agents`, and
`fetch_mcp_tools` directly and call
`langchain.agents.create_agent` yourself.

### `serve(agent, runtime, *, skills=None, on_refresh=None)`

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

1. **Wraps the LangGraph in a `LangChainA2aExecutor`.** This is the
   adapter that translates incoming A2A protocol calls
   (`message/send`, `message/stream`, `tasks/get`, `tasks/cancel`)
   into LangGraph `ainvoke`s and streams events back as A2A `task`
   updates.
2. **Builds the agent card.** Either from an explicit
   `skills=[AgentSkill(...), ...]` list, or — if you didn't pass one —
   a single catch-all skill is synthesised from the agent's
   `description`. The card is exposed at
   `/.well-known/agent-card.json`.
3. **Persists tasks.** A SQLite-backed `DatabaseTaskStore` is created
   at `/tmp/{agent}_tasks.db`. Enough for local dev; for production
   point it at a real database by overriding the create call inside
   your own copy of `serve` if you need durability across pod
   restarts.
4. **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.
5. **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 refresh path.
6. **Refreshes in place.** The default refresh callback rebuilds the
   LangGraph from the recipe stashed by `create_llm_agent` —
   re-discovering sub-agents and MCP tools — and swaps it into the
   executor atomically. Local tools survive the swap. No restart
   needed when subscriptions change. Pass `on_refresh=...` to
   override.
7. **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 tool catalogue
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 tool(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_langchain import (
    create_runtime,
    fetch_remote_agents,        # returns List[PollingA2aTool]
    fetch_mcp_tools,             # returns List[BaseTool]
    PollingA2aTool,              # LangChain BaseTool + A2A + polling
    LangChainA2aExecutor,        # AgentExecutor adapter for A2A protocol
    create_a2a_client_factory,
)
from swarmd_sdk import create_admin_app, SwarmDRuntime
```

Common reasons to drop down:

* **Custom polling interval / max wait.** Build `PollingA2aTool`
  instances yourself.
* **Hand-built LangGraph.** Use the discovery functions to get the
  tool lists, then call `langchain.agents.create_agent` (or build a
  raw LangGraph) with whatever model, memory, or structured-output
  config you need.
* **Custom A2A server.** Use `LangChainA2aExecutor(graph)` directly
  and bolt it onto your own Starlette / FastAPI app — call
  `create_admin_app(runtime, on_refresh=...)` for the `/admin`
  surface.

## Next steps

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