[{"data":1,"prerenderedAt":502},["ShallowReactive",2],{"blog:/blog/coordinate-langchain-agents-with-nolag":3},{"id":4,"title":5,"author":6,"body":7,"category":491,"date":492,"description":493,"excerpt":494,"extension":495,"meta":496,"navigation":133,"path":497,"readTime":498,"seo":499,"stem":500,"__hash__":501},"blog/blog/coordinate-langchain-agents-with-nolag.md","Coordinate LangChain Agents with NoLag","Henco Burger",{"type":8,"value":9,"toc":482},"minimark",[10,14,19,56,59,63,94,98,333,344,348,351,418,424,428,445,449,452,456,478],[11,12,13],"p",{},"LangChain is excellent at building a single agent: prompts, tools, memory, and chains. What it does not give you is the layer above a single agent, where several agents and humans coordinate: dispatching work, sharing state, and gating actions. That is what NoLag provides. This guide connects a LangChain worker to a NoLag room so you can fan work out and collect results in realtime.",[15,16,18],"h2",{"id":17},"the-shape","The shape",[20,21,22,36,50],"ul",{},[23,24,25,26,30,31,35],"li",{},"A ",[27,28,29],"strong",{},"dispatcher"," publishes tasks to a ",[32,33,34],"code",{},"tasks"," topic.",[23,37,38,39,42,43,45,46,49],{},"One or more ",[27,40,41],{},"workers",", each wrapping a LangChain chain, subscribe to ",[32,44,34],{},", run the chain, and publish to ",[32,47,48],{},"results",".",[23,51,52,53,55],{},"Anything can watch ",[32,54,48],{},": a UI, a supervisor agent, or a human.",[11,57,58],{},"Two platform rules shape the code below. A publishing actor never receives its own messages, so the dispatcher and each worker connect with their own actor token. And every subscriber to a topic receives every message unless it opts into load balancing, so workers that should share a queue join a load-balance group.",[15,60,62],{"id":61},"_1-install","1. Install",[64,65,70],"pre",{"className":66,"code":67,"language":68,"meta":69,"style":69},"language-bash shiki shiki-themes github-light github-dark","pip install nolag langchain langchain-openai\n","bash","",[32,71,72],{"__ignoreMap":69},[73,74,77,81,85,88,91],"span",{"class":75,"line":76},"line",1,[73,78,80],{"class":79},"sScJk","pip",[73,82,84],{"class":83},"sZZnC"," install",[73,86,87],{"class":83}," nolag",[73,89,90],{"class":83}," langchain",[73,92,93],{"class":83}," langchain-openai\n",[15,95,97],{"id":96},"_2-a-langchain-worker-on-a-nolag-room","2. A LangChain worker on a NoLag room",[64,99,103],{"className":100,"code":101,"language":102,"meta":69,"style":69},"language-python shiki shiki-themes github-light github-dark","import asyncio\nfrom nolag import NoLag, NoLagOptions\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.prompts import ChatPromptTemplate\n\n# Build the LangChain agent (a simple summariser here)\nllm = ChatOpenAI(model=\"gpt-4o-mini\")\nprompt = ChatPromptTemplate.from_template(\"Summarise this in one sentence:\\n\\n{input}\")\nchain = prompt | llm\n\nasync def main():\n    # Every worker in the \"summarisers\" group shares the queue: each task\n    # goes to one of them instead of all of them.\n    client = NoLag(WORKER_TOKEN, NoLagOptions(\n        load_balance=True,\n        load_balance_group=\"summarisers\",\n    ))\n    await client.connect()\n\n    # Create the app and the \"workflow\" room first; the slug you get back\n    # has a random suffix and that suffixed slug is what set_app() takes.\n    room = client.set_app(APP_SLUG).set_room(\"workflow\")\n    await room.subscribe(\"tasks\")\n\n    def on_task(data, meta):\n        async def run():\n            result = await chain.ainvoke({\"input\": data[\"input\"]})\n            await room.emit(\"results\", {\n                \"task_id\": data[\"task_id\"],\n                \"output\": result.content,\n            })\n        asyncio.create_task(run())\n\n    room.on(\"tasks\", on_task)\n\n    # Keep the worker alive\n    await asyncio.Event().wait()\n\nasyncio.run(main())\n","python",[32,104,105,110,116,122,128,135,141,147,153,159,164,170,176,182,188,194,200,206,212,217,223,229,235,241,246,252,258,264,270,276,282,288,294,299,305,310,316,322,327],{"__ignoreMap":69},[73,106,107],{"class":75,"line":76},[73,108,109],{},"import asyncio\n",[73,111,113],{"class":75,"line":112},2,[73,114,115],{},"from nolag import NoLag, NoLagOptions\n",[73,117,119],{"class":75,"line":118},3,[73,120,121],{},"from langchain_openai import ChatOpenAI\n",[73,123,125],{"class":75,"line":124},4,[73,126,127],{},"from langchain_core.prompts import ChatPromptTemplate\n",[73,129,131],{"class":75,"line":130},5,[73,132,134],{"emptyLinePlaceholder":133},true,"\n",[73,136,138],{"class":75,"line":137},6,[73,139,140],{},"# Build the LangChain agent (a simple summariser here)\n",[73,142,144],{"class":75,"line":143},7,[73,145,146],{},"llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",[73,148,150],{"class":75,"line":149},8,[73,151,152],{},"prompt = ChatPromptTemplate.from_template(\"Summarise this in one sentence:\\n\\n{input}\")\n",[73,154,156],{"class":75,"line":155},9,[73,157,158],{},"chain = prompt | llm\n",[73,160,162],{"class":75,"line":161},10,[73,163,134],{"emptyLinePlaceholder":133},[73,165,167],{"class":75,"line":166},11,[73,168,169],{},"async def main():\n",[73,171,173],{"class":75,"line":172},12,[73,174,175],{},"    # Every worker in the \"summarisers\" group shares the queue: each task\n",[73,177,179],{"class":75,"line":178},13,[73,180,181],{},"    # goes to one of them instead of all of them.\n",[73,183,185],{"class":75,"line":184},14,[73,186,187],{},"    client = NoLag(WORKER_TOKEN, NoLagOptions(\n",[73,189,191],{"class":75,"line":190},15,[73,192,193],{},"        load_balance=True,\n",[73,195,197],{"class":75,"line":196},16,[73,198,199],{},"        load_balance_group=\"summarisers\",\n",[73,201,203],{"class":75,"line":202},17,[73,204,205],{},"    ))\n",[73,207,209],{"class":75,"line":208},18,[73,210,211],{},"    await client.connect()\n",[73,213,215],{"class":75,"line":214},19,[73,216,134],{"emptyLinePlaceholder":133},[73,218,220],{"class":75,"line":219},20,[73,221,222],{},"    # Create the app and the \"workflow\" room first; the slug you get back\n",[73,224,226],{"class":75,"line":225},21,[73,227,228],{},"    # has a random suffix and that suffixed slug is what set_app() takes.\n",[73,230,232],{"class":75,"line":231},22,[73,233,234],{},"    room = client.set_app(APP_SLUG).set_room(\"workflow\")\n",[73,236,238],{"class":75,"line":237},23,[73,239,240],{},"    await room.subscribe(\"tasks\")\n",[73,242,244],{"class":75,"line":243},24,[73,245,134],{"emptyLinePlaceholder":133},[73,247,249],{"class":75,"line":248},25,[73,250,251],{},"    def on_task(data, meta):\n",[73,253,255],{"class":75,"line":254},26,[73,256,257],{},"        async def run():\n",[73,259,261],{"class":75,"line":260},27,[73,262,263],{},"            result = await chain.ainvoke({\"input\": data[\"input\"]})\n",[73,265,267],{"class":75,"line":266},28,[73,268,269],{},"            await room.emit(\"results\", {\n",[73,271,273],{"class":75,"line":272},29,[73,274,275],{},"                \"task_id\": data[\"task_id\"],\n",[73,277,279],{"class":75,"line":278},30,[73,280,281],{},"                \"output\": result.content,\n",[73,283,285],{"class":75,"line":284},31,[73,286,287],{},"            })\n",[73,289,291],{"class":75,"line":290},32,[73,292,293],{},"        asyncio.create_task(run())\n",[73,295,297],{"class":75,"line":296},33,[73,298,134],{"emptyLinePlaceholder":133},[73,300,302],{"class":75,"line":301},34,[73,303,304],{},"    room.on(\"tasks\", on_task)\n",[73,306,308],{"class":75,"line":307},35,[73,309,134],{"emptyLinePlaceholder":133},[73,311,313],{"class":75,"line":312},36,[73,314,315],{},"    # Keep the worker alive\n",[73,317,319],{"class":75,"line":318},37,[73,320,321],{},"    await asyncio.Event().wait()\n",[73,323,325],{"class":75,"line":324},38,[73,326,134],{"emptyLinePlaceholder":133},[73,328,330],{"class":75,"line":329},39,[73,331,332],{},"asyncio.run(main())\n",[11,334,335,336,339,340,343],{},"Start this process on as many machines as you like. Each instance is another member of the ",[32,337,338],{},"summarisers"," group, and the broker hands each task to one of them. Leave ",[32,341,342],{},"load_balance"," off and every instance would run every task.",[15,345,347],{"id":346},"_3-dispatch-work","3. Dispatch work",[11,349,350],{},"Any authorised client can publish a task. Use a different actor token from the workers, or the dispatcher will never see the results it is waiting for:",[64,352,354],{"className":100,"code":353,"language":102,"meta":69,"style":69},"from nolag import NoLag\n\nclient = NoLag(DISPATCHER_TOKEN)\nawait client.connect()\nroom = client.set_app(APP_SLUG).set_room(\"workflow\")\n\nawait room.subscribe(\"results\")\nroom.on(\"results\", lambda data, meta: print(\"done:\", data[\"task_id\"], data[\"output\"]))\n\nawait room.emit(\"tasks\", {\n    \"task_id\": \"t-001\",\n    \"input\": \"NoLag is realtime messaging infrastructure with a coordination layer for agents.\",\n})\n",[32,355,356,361,365,370,375,380,384,389,394,398,403,408,413],{"__ignoreMap":69},[73,357,358],{"class":75,"line":76},[73,359,360],{},"from nolag import NoLag\n",[73,362,363],{"class":75,"line":112},[73,364,134],{"emptyLinePlaceholder":133},[73,366,367],{"class":75,"line":118},[73,368,369],{},"client = NoLag(DISPATCHER_TOKEN)\n",[73,371,372],{"class":75,"line":124},[73,373,374],{},"await client.connect()\n",[73,376,377],{"class":75,"line":130},[73,378,379],{},"room = client.set_app(APP_SLUG).set_room(\"workflow\")\n",[73,381,382],{"class":75,"line":137},[73,383,134],{"emptyLinePlaceholder":133},[73,385,386],{"class":75,"line":143},[73,387,388],{},"await room.subscribe(\"results\")\n",[73,390,391],{"class":75,"line":149},[73,392,393],{},"room.on(\"results\", lambda data, meta: print(\"done:\", data[\"task_id\"], data[\"output\"]))\n",[73,395,396],{"class":75,"line":155},[73,397,134],{"emptyLinePlaceholder":133},[73,399,400],{"class":75,"line":161},[73,401,402],{},"await room.emit(\"tasks\", {\n",[73,404,405],{"class":75,"line":166},[73,406,407],{},"    \"task_id\": \"t-001\",\n",[73,409,410],{"class":75,"line":172},[73,411,412],{},"    \"input\": \"NoLag is realtime messaging infrastructure with a coordination layer for agents.\",\n",[73,414,415],{"class":75,"line":178},[73,416,417],{},"})\n",[11,419,420,421,423],{},"Results arrive on the ",[32,422,48],{}," topic for anyone subscribed, so a dashboard or a supervising agent can react as they land.",[15,425,427],{"id":426},"keeping-a-human-in-the-loop","Keeping a human in the loop",[11,429,430,431,434,435,440,441,444],{},"For steps that need sign-off, NoLag's coordination patterns include ",[27,432,433],{},"approval gates",": an agent publishes a proposed action, a human approves or rejects it from a UI, and the agent proceeds only on approval. The ",[436,437,439],"a",{"href":438},"/docs/agents","AI agents guide"," covers approval gates, shared blackboard state, and observability in depth. For higher-level Python ergonomics, the ",[32,442,443],{},"nolag-agents"," package wraps these patterns so you do not hand-roll the topics.",[15,446,448],{"id":447},"why-coordinate-over-pubsub","Why coordinate over pub/sub",[11,450,451],{},"Direct orchestration, where one process calls each agent in sequence, is easy to start and hard to scale: it couples your agents together and hides what is happening. A realtime coordination layer decouples dispatch from execution, lets you add workers freely, and gives you a single stream to observe every decision. LangChain builds the agent; NoLag coordinates the system.",[15,453,455],{"id":454},"next-steps","Next steps",[20,457,458,465,472],{},[23,459,460,461,49],{},"Read ",[436,462,464],{"href":463},"/blog/multi-agent-coordination-layer","why multi-agent systems need a coordination layer",[23,466,467,468,49],{},"Start with the ",[436,469,471],{"href":470},"/docs/getting-started","5-minute quick start",[23,473,474,475,49],{},"Go deeper on the six ",[436,476,477],{"href":438},"coordination patterns",[479,480,481],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":69,"searchDepth":112,"depth":112,"links":483},[484,485,486,487,488,489,490],{"id":17,"depth":112,"text":18},{"id":61,"depth":112,"text":62},{"id":96,"depth":112,"text":97},{"id":346,"depth":112,"text":347},{"id":426,"depth":112,"text":427},{"id":447,"depth":112,"text":448},{"id":454,"depth":112,"text":455},"Integration","2026-08-02","Use NoLag as the coordination layer for LangChain agents. Dispatch tasks to workers, collect results, and keep a human in the loop, over realtime pub/sub.",null,"md",{},"/blog/coordinate-langchain-agents-with-nolag","8 min read",{"title":5,"description":493},"blog/coordinate-langchain-agents-with-nolag","qiR81OP9FrEABwnnEFtjRIAy8xNO4ZTIQo8gRA5_Xlk",1789869416248]