A minimal end-to-end example showing how to host a compiled LangGraph as an
A2A agent on Solace Agent Mesh, using the solace_agent_mesh_langgraph wrapper from
this repo.
The graph itself (agent.py) is a single-node LangGraph that takes raw
technical notes and returns a structured Markdown document with three
sections: ## Summary, ## Technical Details, ## Action Items.
| File | Purpose |
|---|---|
agent.py |
The LangGraph definition. Exposes a module-level graph (compiled, no checkpointer) for langgraph dev and a DocumentationFormatterAgent class that accepts an optional checkpointer= for deployment-time wrapping. |
main.py |
Entry point for the SAM/A2A deployment path. Wraps the graph with MemorySaver, builds broker properties, runs the server. |
agent_card.json |
A2A agent card published on the discovery topic. |
Dockerfile |
Builds a single image bundling wrapper + this agent. Built with -f from the repo root. |
langgraph.json |
Optional. LangGraph CLI manifest — only used if you want to run / debug the graph in LangGraph Studio or LangSmith. Not used by the A2A Solace path. |
requirements.txt |
Re-exports the top-level requirements.txt. |
python -m venv .venv && source .venv/bin/activate
pip install -e ".[examples]"
cp .env.example examples/doc_formatter/.env
# edit examples/doc_formatter/.env: fill in broker creds + OPENAI_API_KEY
cd examples/doc_formatter
python main.pymain.py composes the deployment-time graph — wrapping with MemorySaver
so the wrapper's contextId → thread_id mapping actually persists state
across A2A turns — and runs the server with graceful shutdown handling.
See Conversation continuity below for what
goes in main.py and why.
TLS configuration only matters for
tcps://URLs.
- Plain
tcp://broker (e.g. a local Solace broker container): no TLS settings needed at all. Just the fourSOLACE_BROKER_*creds.tcps://broker, real deployment: configureSOLACE_BROKER_TRUST_STORE_DIRto point at a directory of CA certs.tcps://broker, quick sandbox demo: skip the trust store by settingSOLACE_BROKER_VALIDATE_CERTS=falsein.env. Never use this in production — it disables TLS's man-in-the-middle protection.
What you should see (in order):
Initializing Documentation Formatter Agent...— the graph compiles.Connected to Solace broker— wrapper is connected.Queue '<random>' created and receiver started— non-durable exclusive queue.A2A LangChain Server ready - agent=doc_formatter ...- Every 3 seconds, an agent-card publish happens silently in the background.
Send a JSON-RPC message/send to
<SAM_NAMESPACE>/a2a/v1/agent/request/doc_formatter with a replyTo user
property set to a topic you're subscribed to, and you'll receive the formatted
Markdown back on that replyTo.
Dockerfile in this folder bundles the wrapper + this example
agent into a single image. Useful for handing a runnable agent to someone
without a Python environment, or as a starting point for deploying to a
container platform. (Stage 3 of the deployability roadmap will split this
into a published base image + a thin extension Dockerfile; for now it's
one self-contained file.)
The build context must be the repo root so the Dockerfile can reach
src/ and pyproject.toml. Build from there with the -f flag pointing
at this Dockerfile:
docker build -t doc-formatter:dev -f examples/doc_formatter/Dockerfile .Minimal run — works for any plain tcp:// broker (e.g. a local Solace
broker container):
docker run --rm \
--env-file examples/doc_formatter/.env \
doc-formatter:devFor a tcps:// broker, mount your trust-store directory and tell the
container where it landed:
docker run --rm \
--env-file examples/doc_formatter/.env \
-v ~/.solace-truststore:/certs:ro \
-e SOLACE_BROKER_TRUST_STORE_DIR=/certs \
doc-formatter:devWhat the extra tcps:// flags do:
-v ~/.solace-truststore:/certs:romounts your TLS trust-store directory from the host into the container at/certs. Your host path doesn't exist inside the container, so the bind-mount is necessary.-e SOLACE_BROKER_TRUST_STORE_DIR=/certsoverrides whatever the.envsays for the in-container path. Container env vars beat--env-file.
TLS configuration only matters for
tcps://URLs.
- Plain
tcp://broker: use the minimaldocker runabove. No TLS settings to configure.tcps://broker, real deployment: use the second command above withSOLACE_BROKER_TRUST_STORE_DIRpointing at real CA certs.tcps://broker, quick sandbox demo: setSOLACE_BROKER_VALIDATE_CERTS=falsein.envand the minimaldocker runworks fortcps://too — no trust-store mount needed. Never use this in production — it disables TLS's man-in-the-middle protection.
- Local broker on macOS / Windows. If
SOLACE_BROKER_URL=tcp://localhost:55555in your.env, "localhost" inside the container means the container itself, not your host. Change the URL totcp://host.docker.internal:55555(Docker Desktop / Podman Desktop). On Linux, add--network=hostinstead and keeplocalhost. - Podman is a drop-in replacement. Substitute
podmanfordockerin both commands — the flags are identical. - Quoted env values are fine. The wrapper strips surrounding
"..."or'...'from broker properties at load time, so it doesn't matter whether your.envquotes the values or not. (Docker/Podman's--env-fileparser preserves quotes; python-dotenv strips them — the wrapper normalises both paths.)
Useful when iterating on the LangGraph itself:
python examples/doc_formatter/agent.pyThis runs the built-in test in agent.py:__main__ (sends a sample notes
blob through the graph and prints the formatted Markdown). It only needs
OPENAI_API_KEY — no Solace broker required.
langgraph.json is included so you can load this graph in
LangGraph Studio
or trace it via LangSmith — useful for
visualising state transitions, replaying turns, or inspecting tool calls
during development. None of this is required to run the agent on Solace
Agent Mesh — the A2A path (python main.py) ignores langgraph.json entirely.
The manifest declares:
{
"dependencies": ["."],
"graphs": { "documentation_formatter": "./agent.py:graph" },
"env": "../../.env",
"python_version": "3.11"
}graphsexposes the compiledgraphfromagent.pyunder the namedocumentation_formatter.envpoints back at the repo-root.env(relative to this file), so theOPENAI_API_KEYyou already configured is picked up automatically.
pip install -U langgraph-cli
langgraph dev # from examples/doc_formatter/This starts the LangGraph dev server (Studio UI on http://localhost:8123 by
default) using your local Python environment. Trace data is sent to
LangSmith if LANGCHAIN_API_KEY / LANGCHAIN_TRACING_V2=true are set in
.env.
- The Studio path runs the graph standalone, not through Solace. It's for graph-iteration and tracing only.
dependencies: ["."]tells the LangGraph CLI to install this directory. The localrequirements.txthere just re-exports the top-level one (-r ../../requirements.txt); if the CLI in your environment doesn't follow-rreferences, replace the contents ofexamples/doc_formatter/requirements.txtwith the explicit pins from the top-level file.
The wrapper maps the A2A contextId of an incoming request to the
LangGraph thread_id, so two requests sharing the same contextId should
see each other's message history. But that mapping is only meaningful if
the compiled graph has a checkpointer — without one, every invocation
is stateless regardless of thread_id.
The example splits that decision across two files on purpose:
agent.pycompiles a stateless module-levelgraphsolanggraph dev(which rejects custom checkpointers) can load it. TheDocumentationFormatterAgentclass accepts an optionalcheckpointer=kwarg so deployment-time code can re-build the graph with persistence.main.pyis where the deployment choice lives. It does:Swapfrom langgraph.checkpoint.memory import MemorySaver graph = DocumentationFormatterAgent(checkpointer=MemorySaver()).graph
MemorySaver()for whatever fits your deployment:
| Need | Pick |
|---|---|
| Quick A2A demo on one process | MemorySaver — in-memory, process-local |
| Single host, durable | SqliteSaver — file on disk |
| Multi-replica / shared state | PostgresSaver |
| Running on LangGraph Platform | Don't add one — platform provides its own |
This split is why main.py exists alongside agent.py. agent.py is the
agent's definition — loadable as-is by langgraph dev. main.py is the
agent's deployment — re-wraps the graph with checkpointer (and any
other runtime concerns) before handing it to the server.
- System prompt / formatting rules: edit
SYSTEM_PROMPTinagent.py. - Model: set
LLM_MODEL_NAMEin.env(defaultgpt-4o). - Agent name / discovery URL: edit
agent_card.json. Thenamefield controls the request topic (.../agent/request/<name>), so renaming it changes what callers must address. - Checkpointer: see Conversation continuity above. Bundled example is stateless by default; pick a checkpointer for your own copy based on where you're deploying.