Skip to content

Conversation

@edis-uipath
Copy link

@edis-uipath edis-uipath commented Feb 11, 2026

Summary

In some cases we need to save the session id or to restore an existing session id and to continue from it, in some cases we need to move a session across process boundary (for example debug or human in the loop scenarios).

The transport previously owned session state directly via self.session_id, making it impossible for consumers to observe or persist session IDs externally (e.g. persisting to a debug state endpoint for playground/agent resume scenarios).

This PR introduces a SessionInfo base class with async get_session_id / set_session_id methods that the transport delegates all session ID storage to. Consumers can subclass SessionInfo to add side-effects like HTTP persistence, and pass it via the new session_info parameter on StreamableHTTPTransport and streamable_http_client.

Key changes

  • Add SessionInfo class — base class with async get/set methods; default implementation stores in a plain attribute (backwards compatible)
  • Transport delegates to _session_info — replaces direct self.session_id usage
  • _prepare_headers and _maybe_extract_session_id_from_response are now async — to support subclasses that need I/O (e.g. loading from a remote store)
  • Remove unused session_id field from RequestContext — headers are built from _prepare_headers, not from the context
  • streamable_http_client accepts optional session_info parameter — passed through to the transport constructor
  • get_session_id is now async — delegates to SessionInfo.get_session_id()

Backwards compatibility

  • Default behavior is unchanged: when no session_info is provided, a plain SessionInfo() is created internally
  • All 49 existing streamable HTTP tests pass without modification
  • The removed RequestContext.session_id field was not used by any handler (headers come from _prepare_headers)

Example usage

Track the session ID externally:

session_info = SessionInfo()

async with streamable_http_client(url, http_client=client, session_info=session_info) as (read, write):
    session = ClientSession(read, write)
    await session.initialize()
    # session_info.session_id now holds the server-assigned session ID
    print(f"Session ID: {await session_info.get_session_id()}")

Resume an existing session (skip initialize):

# Reuse a session ID from a previous connection
session_info = SessionInfo(session_id="previously-saved-session-id")

async with streamable_http_client(url, http_client=client, session_info=session_info) as (read, write):
    session = ClientSession(read, write)
    # No initialize() call — the transport includes the existing session ID in headers
    tools = await session.list_tools()

The transport previously owned session state directly via self.session_id,
making it impossible for consumers to observe or persist session IDs
(e.g. to debug state for playground mode).

Introduce a SessionInfo base class with async get/set methods that the
transport delegates to. Consumers can subclass SessionInfo to add
side-effects like session id persistence, and pass it via the new session_info
parameter on StreamableHTTPTransport and streamable_http_client.

Key changes:
- Add SessionInfo class with async get_session_id/set_session_id
- Transport stores _session_info instead of session_id
- _prepare_headers and _maybe_extract_session_id_from_response are now async
- Remove unused session_id field from RequestContext
- streamable_http_client accepts optional session_info parameter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant