Recipe: FastAPI gateway + agent channel¶
Bridge a browser (or any WebSocket client) through FastAPI into a remote
@channel agent on the Zenoh fabric. FastAPI owns HTTP; Istos owns the mesh.
Agent node¶
# agent.py
from istos import Istos, ChannelSession
from istos.communication.config import IstosZenohConfig
mesh = Istos(
config=IstosZenohConfig(
mode="client",
connect_endpoints=["tcp/router:7447"],
),
# storage=RedisStoragePlugin(...) # for durable=True across pods
)
@mesh.channel("agent/chat", durable=True)
async def chat(s: ChannelSession):
context = [t["data"] for t in await s.history()]
await s.send({"role": "system", "text": "ready", "conversation_id": s.conversation_id})
async for msg in s:
# replace with your SLM / tool loop
reply = {"role": "assistant", "text": f"echo: {msg}", "context_len": len(context)}
context.append(msg)
context.append(reply)
await s.send(reply)
if __name__ == "__main__":
mesh.run()
FastAPI edge¶
# gateway.py
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from istos import Istos
from istos.http.asgi import lifespan
from istos.communication.config import IstosZenohConfig
mesh = Istos(
config=IstosZenohConfig(
mode="client",
connect_endpoints=["tcp/router:7447"],
),
)
api = FastAPI(lifespan=lifespan(mesh))
@api.websocket("/chat")
async def chat_ws(ws: WebSocket):
await ws.accept()
# Browsers cannot set Authorization on WebSocket — authenticate earlier
# (cookie / short-lived ticket) and pass token= when you have one.
conversation_id = ws.query_params.get("conversation_id")
chan = await mesh.open_channel(
"agent/chat",
conversation_id=conversation_id,
# token=jwt,
)
try:
await ws.send_json({"conversation_id": chan.conversation_id})
while True:
msg = await ws.receive_text()
await chan.send(msg)
reply = await chan.receive()
await ws.send_json(reply)
except WebSocketDisconnect:
pass
finally:
await chan.close()
Run the agent with python agent.py, the gateway with
uvicorn gateway:api --host 0.0.0.0 --port 8000. Do not also set
Istos(http_port=...) on the FastAPI process — leave HTTP to uvicorn
(serving() defaults to serve_http=False).
Embedded Istos WebSocket (no FastAPI)¶
If the agent node itself exposes HTTP:
app = Istos(http_port=8080, authorizer=jwt)
@app.channel("agent/chat", ws="/chat", durable=True)
async def chat(s: ChannelSession):
...
Resume from a browser with the query param (auth still needs a non-browser
client that can send Authorization, or an open/Public channel for demos):
const id = localStorage.getItem("cid");
const q = id ? `?conversation_id=${id}` : "";
const ws = new WebSocket(`ws://host:8080/chat${q}`);
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.conversation_id) localStorage.setItem("cid", msg.conversation_id);
};
Resume after reconnect¶
Persist conversation_id from the first open_channel (or from the JSON the
gateway sent). On reconnect:
The durable handler reloads history via session.history().