nemo_gym.session_routing
nemo_gym.session_routing
Route each session’s requests to the uvicorn worker that holds its state.
uvicorn’s workers share one listening socket, so a request reaches whichever worker accepts the connection, while a session’s state lives in the memory of the worker that created it. With more than one worker, each worker also serves the same app on a private Unix socket, in a directory the main process creates for the server, and requests are routed by one rule:
- Find the request’s session ID.
- Look up the session’s claim, which names the worker that created the session.
- Forward the request to that worker’s private socket, unchanged, and stream its reply back unchanged. If this worker holds the claim, or the session has no claim, the request is handled here.
A request that arrives over a private socket is always handled where it arrives, so it is forwarded at most once. If the claimant’s socket is gone, because its worker exited, the request gets a 410 instead of silently running against empty state.
Where the session ID comes from:
The body is read only on the seed and close routes, where it names the session before any cookie exists.
If the cookie and the body name different sessions, the reply is 409.
A typed resources session uses its resources_session_id as its cookie’s session_id too,
so the seed, its close and every request in between name the session by the same ID.
A resources request with no session ID gets a new ID, which this router mints.
A session start, such as a seed, claims it before its handler runs, since its reply’s cookie names the session.
Any other request claims it only if the handler used the session, before the reply leaves the worker.
A request that keeps nothing, such as a health check or /aggregate_metrics, leaves no claim and gets no cookie,
so no cookie names a session nobody claims.
A request whose cookie names a session whose claim ended is claimed the same way by the worker that serves it.
A claim is a file in the socket directory, named by a hash of the session ID and holding the claimant’s worker ID. The worker that creates the session creates the claim, atomically, so two workers can never claim one ID. Seeds and closes create claims, so a retried seed, a cleanup close, or a delayed seed after a close all reach the worker that saw the ID first. A claim ends once its session’s last record on the claimant is gone:
- an agent session’s claim ends when the agent drops the session’s last record,
which is its close receipt or failed seed,
session_close_retry_window_secondsafter it was written; - a resources session’s claim lasts while the session is open, from a successful seed until its close or
/verify, and endssession_close_retry_window_secondsafter the last request for it otherwise. A/verifythat says a close follows, as an environment server’s does, leaves the session open for that close.
The claims of a worker that exited stay until the server exits, so its sessions keep getting a 410. The whole directory goes with the server’s runtime directory when the server exits. With one worker there is no private socket and no claim.
Module Contents
Classes
Functions
Data
_PRIVATE_SERVER_KEEPALIVE_SECONDS
API
An agent server, whose sessions are claimed by agent_session_id across workers.
Drop the close receipts and failed seeds whose window has passed, releasing their claims.
Whether this worker still keeps any record of the session, open, failed or closed.
Keep the claims, to release one when the worker drops the session’s last record.
How long a resources server’s worker keeps the claim on a session, judged from the requests it served.
A resources server keeps its per-session state in its own subclass, so the router judges by the requests:
- A successful seed, or another declared session start such as a Gymnasium
/reset, opens the session: its claim lasts until the session ends. - A successful
/close_sessionor/verify, orSimpleResourcesServer.checkpoint_session_ended, ends it, unless the request says a close follows, as an environment server’s/verifydoes. - A session that is not open keeps its claim
window_secondsafter the last request for it. The caller’s retried or cleanup close reaches the claimant within that window. A delayed seed after a close reaches it too, where the server refuses it while it remembers the close. - A failed seed or close of an open session leaves it open.
So a claim outlives the session’s last request by at most the window, except for a session never closed, which keeps its claim as long as the server keeps the session.
End a session this worker claims, from a route that ends a session under another name.
Which worker created each session, shared by the workers of one server.
A claim is a file in the claims directory, named by a hash of the session ID and holding the claimant’s worker ID. It is created with a hard link from a complete temporary file, which fails if the claim exists, so two workers cannot both claim one ID and a reader never sees a partly written claim.
A claim lives as long as holds says its claimant keeps anything of the session.
It is not released while a request for the session is being served here,
since that request may be about to create a record.
Only the claimant removes its claim, so a removal cannot delete another worker’s claim made since.
Claim the ID for this worker unless another worker has; return the claimant.
Returns None for a claim file that names no valid worker, which nothing here writes.
The worker that claims the ID, or None if nobody does.
Remove this worker’s claim on the ID, unless a request for it is in progress or the session remains.
Hold the claim while a request for the ID is served here, and release it after if nothing is left.
Where one kind of server’s requests name their session.
How long a worker keeps the claims on the sessions it created.
Whether this worker still keeps any record of the session.
Record a request for a session this worker claims, once it is served; status is None if it failed.
Whether a request on path starts a session, so it claims its session whatever its handler does.
Drop the records whose time has passed, and return the sessions whose claims can be released.
Find a request’s session ID, and forward the request to the worker that claims the session.
It sits outside SessionMiddleware and reads the cookie by decoding it with the same secret. A forwarded request therefore never reaches this worker’s SessionMiddleware, so the reply carries only the claimant’s Set-Cookie, built from the session as the claimant left it. Sitting inside SessionMiddleware instead would add this worker’s stale copy of the session as a second Set-Cookie.
With one worker there are no claims, and it only reads the session ID of seeds and closes: it refuses a request whose cookie and body name different sessions, and gives a typed resources session its ID in the cookie.
Serve a request for a session this worker claims, or may claim, and record it for the claim’s lifetime.
An agent’s own records decide its claims: it releases a claim itself when it drops a session’s last record.
Bases: HttpToolsProtocol
uvicorn’s httptools protocol without access logs, which it would otherwise write whenever the logger has handlers.
Match any of paths, bare or under a rollout prefix.
Read the request body, or None if the client disconnected.
A receive that gives the app the body already read, then passes later messages, such as a disconnect, on.
The session ID in field of a seed or close body, or None for a body that has no non-empty string one.
Release the claims whose session has ended, so a claim ends even when no more requests arrive.
Return the session stored in a session cookie, or None if Starlette’s SessionMiddleware would reject it.
Decoded exactly as SessionMiddleware decodes it. A cookie it rejects (badly signed or expired) starts a fresh session.
The directory for one server’s private worker sockets and claims: this process’s runtime directory.
Route this worker’s requests by session ID, and serve forwarded requests on a private socket.
Call once the app’s middleware is otherwise in place: the router must sit outside SessionMiddleware.
Without a socket_dir (one worker), it only reads the session ID of seeds and closes, and claims nothing.
With one, it claims sessions in it and returns this worker’s ID, also stored in app.state.nemo_gym_routing_id.
Pass the agent for an agent server, whose own records decide its claims’ lifetimes,
or the close window and the session start and end routes for a resources server (see ResourcesSessionLifetimes).
Return the session ID in an MCP session token, or None for a token with a bad signature.
A rejected token is handled locally, where the MCP endpoint’s own token check refuses it.
Serve app on a Unix socket for requests other workers forward here, on the running event loop.