MESHFLOW_MOCK=1 python3 hands_on/14_cross_framework.pyBy the end of this lesson, you should be able to:
Estimated time: 45 to 60 minutes.
You have an existing agent. It might be a LangGraph compiled graph, a CrewAI crew, an AutoGen ConversableAgent, or a plain Python function. You want to run it inside a MeshFlow workflow so it gets:
The adapter pattern lets you add all of this without rewriting the agent. You wrap the existing object in a CallableAgent that MeshFlow can call.
Your existing agent → from_langgraph(agent) → CallableAgent → MeshFlow node
Any Python async function with the signature (task: str, context: dict) -> str can be wrapped:
from meshflow.agents.adapters import from_callable
from meshflow.core.schemas import AgentRole
async def my_research_fn(task: str, context: dict) -> str:
return f"Research result for: {task}"
agent = from_callable(my_research_fn, role=AgentRole.RESEARCHER)
from_callable is the fallback adapter. Use it for:
LangGraph compiles graphs to a Runnable interface with an .invoke() method:
from meshflow.agents.adapters import from_langgraph
from meshflow.core.schemas import AgentRole
# compiled_graph = StateGraph(...).compile()
agent = from_langgraph(compiled_graph, role=AgentRole.RESEARCHER)
Under the hood, from_langgraph calls compiled_graph.invoke({"input": task}) and extracts the output. The LangGraph graph runs exactly as it was designed; MeshFlow wraps its execution with governance.
What MeshFlow adds:
What it cannot change:
from meshflow.agents.adapters import from_crewai
from meshflow.core.schemas import AgentRole
# crewai_agent = Agent(role="researcher", goal="...", backstory="...")
agent = from_crewai(crewai_agent, role=AgentRole.RESEARCHER)
The adapter calls crewai_agent.execute_task(task) and returns the result. The CrewAI agent's role, goal, and backstory remain unchanged.
from meshflow.agents.adapters import from_autogen
from meshflow.core.schemas import AgentRole
# autogen_agent = AssistantAgent(name="researcher", ...)
agent = from_autogen(autogen_agent, role=AgentRole.RESEARCHER)
The adapter initiates a single-turn conversation with the AutoGen agent and returns the last message content. For multi-turn AutoGen setups, wrap the entire team as a single callable.
Any service that accepts a POST request with a JSON body and returns JSON can be wrapped as a MeshFlow node:
from meshflow.core.node import MeshNode
node = MeshNode.from_http(
url="https://api.example.com/analyze",
role=AgentRole.EXECUTOR,
)
The adapter sends {"task": task, "context": context} and reads response["output"] or response["result"] from the JSON response.
Use from_http for:
You can combine all adapter types in a single workflow:
agents = [
from_langgraph(lg_graph, role=AgentRole.RESEARCHER),
from_callable(my_analyzer, role=AgentRole.EXECUTOR),
from_crewai(crew_writer, role=AgentRole.EXECUTOR),
from_autogen(ag_reviewer, role=AgentRole.CRITIC),
]
result = await Mesh(agents=agents, policy=policy).run(task)
Each agent runs in sequence. The output of one becomes the input task for the next. All agents are governed by the same policy, regardless of which framework produced them.
The adapter pattern enables progressive migration. You do not have to rewrite your existing agents to get governance. You can:
from_callable or the appropriate adapter.This means you can start getting governance benefits on day one without a full rewrite.
An adapter adds governance around an agent. It cannot change what happens inside:
unless you also add guardian screening to the policy.
explicit output schema validation in downstream nodes if you need that.
MESHFLOW_MOCK=1 python3 hands_on/14_cross_framework.py
Observe:
from_http is demonstrated with a mock serviceThe adapter pattern — from_callable, from_langgraph, from_crewai, from_autogen, from_http — lets you wrap any existing agent in MeshFlow governance without rewriting it. All adapters produce CallableAgent objects that plug into Mesh(agents=[...]) or WorkflowDefinition nodes. Mixing adapter types in one workflow is fully supported. Governance is added around the agent; behavior inside the agent is unchanged.
Goal: Observe multiple adapters working together in a single MeshFlow pipeline.
Instructions:
python hands_on/14_cross_framework.py
- Which nodes use from_callable? - Which nodes use from_langgraph, from_crewai, or from_autogen? - Which nodes (if any) use from_http?
- The adapter type. - The node's role in the pipeline (what does it do?). - Whether the node appears in the MeshFlow trace output with a node_type label.
Expected output: A console trace showing each node's execution, with labels or metadata indicating the adapter type. All nodes should appear in a single unified execution trace.
Goal: Practice the simplest adapter pattern.
Instructions:
def extract_keywords(text: str) -> dict:
words = text.lower().split()
stopwords = {"the", "a", "an", "is", "in", "of", "and", "to"}
keywords = [w for w in words if w not in stopwords and len(w) > 3]
return {"keywords": list(set(keywords)), "count": len(set(keywords))}
from meshflow import MeshNode
node = MeshNode.from_callable(extract_keywords, name="keyword_extractor")
- Is keyword_extractor visible as a named step in the trace? - What does MeshFlow record as the step's input and output? - Is there a node_type: callable or similar label in the step metadata?
from_callable node that takes the output of keyword_extractor and counts how many keywords start with a vowel. Chain the two nodes and run the workflow.Expected output: A two-step trace showing both callable nodes with their respective inputs, outputs, and metadata. The workflow should complete without errors.
Goal: Integrate an existing LangGraph graph as a single MeshFlow node.
Instructions:
from langgraph.graph import StateGraph, END
def classify(state):
text = state["text"]
label = "positive" if "good" in text.lower() else "negative"
return {"label": label}
builder = StateGraph(dict)
builder.add_node("classifier", classify)
builder.set_entry_point("classifier")
builder.add_edge("classifier", END)
lg_graph = builder.compile()
from meshflow import MeshNode
node = MeshNode.from_langgraph(lg_graph, name="sentiment_graph")
from_callable node that prepares the input. - Does the LangGraph graph's internal nodes appear as sub-steps in the MeshFlow trace, or does the entire graph appear as a single step? - What does node_type show for this step?
Expected output: The LangGraph graph appears as a single MeshFlow step with node_type: langgraph. The internal LangGraph state transitions are not individually visible in the MeshFlow trace (they are encapsulated by the adapter).
Goal: Combine at least three different adapter types in one workflow.
Instructions:
- Node 1 (from_callable): A Python function that fetches data from a local file or generates mock data. - Node 2 (from_langgraph or from_crewai): A framework-specific component that processes the data. - Node 3 (from_http): An HTTP endpoint that a local test server or public API handles.
https://httpbin.org/post or spin up a minimal local Flask server: # In a separate terminal:
# pip install flask && python -c "
# from flask import Flask, request, jsonify
# app = Flask(__name__)
# @app.route('/summarize', methods=['POST'])
# def summarize():
# data = request.json
# return jsonify({'summary': data.get('text', '')[:50] + '...'})
# app.run(port=8765)"
node3 = MeshNode.from_http("http://localhost:8765/summarize", name="http_summarizer")
node_type labels and that data flows correctly from node 1 through to node 3.Expected output: A three-step trace with different node_type values for each step, and the output of each step correctly passed as the input to the next.
Goal: Experience the progressive adoption pattern by instrumenting an existing codebase without rewriting it.
Instructions:
from_callable MeshNode: from meshflow import MeshFlow, MeshNode
# Existing functions — unchanged
def step_one(data): ...
def step_two(data): ...
def step_three(data): ...
# Wrap without modifying
app = MeshFlow()
app.add_node(MeshNode.from_callable(step_one, name="step_one"))
app.add_node(MeshNode.from_callable(step_two, name="step_two"))
app.add_node(MeshNode.from_callable(step_three, name="step_three"))
result = app.run({"input": "your data here"})
app = MeshFlow(enable_ledger=True, compliance=PolicyMode.STANDARD)
Expected output: Identical business output to the original script, plus a full MeshFlow trace and ledger record with no changes to the wrapped functions.