Handle comments
diff --git a/AGENTS.md b/AGENTS.md
index 0fa51a9..0f70128 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -3,6 +3,7 @@
 Language-specific details live in respective subdirectories.
 -->
 See @.local/AGENTS.md for additional guidance
+See [skills.md](skills.md) for a consolidated cross-language quickstart and skill matrix.
 
 Selenium is a Bazel-built monorepo implementing the W3C WebDriver (and related) protocols,
 shipping multiple language bindings plus Grid and Selenium Manager.
diff --git a/py/selenium/webdriver/common/bidi/permissions.py b/py/selenium/webdriver/common/bidi/permissions.py
index 17faa1f..6dd138d 100644
--- a/py/selenium/webdriver/common/bidi/permissions.py
+++ b/py/selenium/webdriver/common/bidi/permissions.py
@@ -15,12 +15,20 @@
 # specific language governing permissions and limitations
 # under the License.
 
+"""WebDriver BiDi Permissions module."""
 
-from selenium.webdriver.common.bidi.common import command_builder
+from __future__ import annotations
+
+from enum import Enum
+from typing import Any
+
+from .common import command_builder
+
+_VALID_PERMISSION_STATES = {"granted", "denied", "prompt"}
 
 
-class PermissionState:
-    """Represents the possible permission states."""
+class PermissionState(str, Enum):
+    """Permission state enumeration."""
 
     GRANTED = "granted"
     DENIED = "denied"
@@ -28,56 +36,69 @@
 
 
 class PermissionDescriptor:
-    """Represents a permission descriptor."""
+    """Descriptor for a permission."""
 
-    def __init__(self, name: str):
+    def __init__(self, name: str) -> None:
+        """Initialize a PermissionDescriptor.
+
+        Args:
+            name: The name of the permission (e.g., 'geolocation', 'microphone', 'camera')
+        """
         self.name = name
 
-    def to_dict(self) -> dict:
-        return {"name": self.name}
+    def __repr__(self) -> str:
+        return f"PermissionDescriptor('{self.name}')"
 
 
 class Permissions:
-    """BiDi implementation of the permissions module."""
+    """WebDriver BiDi Permissions module."""
 
-    def __init__(self, conn):
-        self.conn = conn
+    def __init__(self, websocket_connection: Any) -> None:
+        """Initialize the Permissions module.
+
+        Args:
+            websocket_connection: The WebSocket connection for sending BiDi commands
+        """
+        self._conn = websocket_connection
 
     def set_permission(
         self,
-        descriptor: str | PermissionDescriptor,
-        state: str,
-        origin: str,
+        descriptor: PermissionDescriptor | str,
+        state: PermissionState | str,
+        origin: str | None = None,
         user_context: str | None = None,
     ) -> None:
-        """Sets a permission state for a given permission descriptor.
+        """Set a permission for a given origin.
 
         Args:
-            descriptor: The permission name (str) or PermissionDescriptor object.
-              Examples: "geolocation", "camera", "microphone".
-            state: The permission state (granted, denied, prompt).
-            origin: The origin for which the permission is set.
-            user_context: The user context id (optional).
+            descriptor: The permission descriptor or permission name as a string
+            state: The desired permission state
+            origin: The origin for which to set the permission
+            user_context: Optional user context ID to scope the permission
 
         Raises:
-            ValueError: If the permission state is invalid.
+            ValueError: If the state is not a valid permission state
         """
-        if state not in [PermissionState.GRANTED, PermissionState.DENIED, PermissionState.PROMPT]:
-            valid_states = f"{PermissionState.GRANTED}, {PermissionState.DENIED}, {PermissionState.PROMPT}"
-            raise ValueError(f"Invalid permission state. Must be one of: {valid_states}")
+        state_value = state.value if isinstance(state, PermissionState) else state
+        if state_value not in _VALID_PERMISSION_STATES:
+            raise ValueError(
+                f"Invalid permission state: {state_value!r}. "
+                f"Must be one of {sorted(_VALID_PERMISSION_STATES)}"
+            )
 
         if isinstance(descriptor, str):
-            permission_descriptor = PermissionDescriptor(descriptor)
+            descriptor_dict = {"name": descriptor}
         else:
-            permission_descriptor = descriptor
+            descriptor_dict = {"name": descriptor.name}
 
-        params = {
-            "descriptor": permission_descriptor.to_dict(),
-            "state": state,
-            "origin": origin,
+        params: dict[str, Any] = {
+            "descriptor": descriptor_dict,
+            "state": state_value,
         }
-
+        if origin is not None:
+            params["origin"] = origin
         if user_context is not None:
             params["userContext"] = user_context
 
-        self.conn.execute(command_builder("permissions.setPermission", params))
+        cmd = command_builder("permissions.setPermission", params)
+        self._conn.execute(cmd)
diff --git a/py/selenium/webdriver/common/bidi/session.py b/py/selenium/webdriver/common/bidi/session.py
index 3481c2d..fcb42a4 100644
--- a/py/selenium/webdriver/common/bidi/session.py
+++ b/py/selenium/webdriver/common/bidi/session.py
@@ -1,134 +1,246 @@
-# Licensed to the Software Freedom Conservancy (SFC) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The SFC licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
+# DO NOT EDIT THIS FILE!
 #
-#   http://www.apache.org/licenses/LICENSE-2.0
+# This file is generated from the WebDriver BiDi specification. If you need to make
+# changes, edit the generator and regenerate all of the modules.
 #
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
+# WebDriver BiDi module: session
+from __future__ import annotations
 
+from dataclasses import dataclass, field
+from typing import Any
 
-from selenium.webdriver.common.bidi.common import command_builder
+from .common import command_builder
 
 
 class UserPromptHandlerType:
-    """Represents the behavior of the user prompt handler."""
+    """UserPromptHandlerType."""
 
     ACCEPT = "accept"
     DISMISS = "dismiss"
     IGNORE = "ignore"
 
-    VALID_TYPES = {ACCEPT, DISMISS, IGNORE}
+
+@dataclass
+class CapabilitiesRequest:
+    """CapabilitiesRequest."""
+
+    always_match: Any | None = None
+    first_match: list[Any] = field(default_factory=list)
 
 
+@dataclass
+class CapabilityRequest:
+    """CapabilityRequest."""
+
+    accept_insecure_certs: bool | None = None
+    browser_name: str | None = None
+    browser_version: str | None = None
+    platform_name: str | None = None
+    proxy: Any | None = None
+    unhandled_prompt_behavior: Any | None = None
+
+
+@dataclass
+class AutodetectProxyConfiguration:
+    """AutodetectProxyConfiguration."""
+
+    proxy_type: str = field(default="autodetect", init=False)
+
+
+@dataclass
+class DirectProxyConfiguration:
+    """DirectProxyConfiguration."""
+
+    proxy_type: str = field(default="direct", init=False)
+
+
+@dataclass
+class ManualProxyConfiguration:
+    """ManualProxyConfiguration."""
+
+    proxy_type: str = field(default="manual", init=False)
+    http_proxy: str | None = None
+    ssl_proxy: str | None = None
+    no_proxy: list[Any] = field(default_factory=list)
+
+
+@dataclass
+class SocksProxyConfiguration:
+    """SocksProxyConfiguration."""
+
+    socks_proxy: str | None = None
+    socks_version: Any | None = None
+
+
+@dataclass
+class PacProxyConfiguration:
+    """PacProxyConfiguration."""
+
+    proxy_type: str = field(default="pac", init=False)
+    proxy_autoconfig_url: str | None = None
+
+
+@dataclass
+class SystemProxyConfiguration:
+    """SystemProxyConfiguration."""
+
+    proxy_type: str = field(default="system", init=False)
+
+
+@dataclass
+class SubscribeParameters:
+    """SubscribeParameters."""
+
+    events: list[str] = field(default_factory=list)
+    contexts: list[Any] = field(default_factory=list)
+    user_contexts: list[Any] = field(default_factory=list)
+
+
+@dataclass
+class UnsubscribeByIDRequest:
+    """UnsubscribeByIDRequest."""
+
+    subscriptions: list[Any] = field(default_factory=list)
+
+
+@dataclass
+class UnsubscribeByAttributesRequest:
+    """UnsubscribeByAttributesRequest."""
+
+    events: list[str] = field(default_factory=list)
+
+
+@dataclass
+class StatusResult:
+    """StatusResult."""
+
+    ready: bool | None = None
+    message: str | None = None
+
+
+@dataclass
+class NewParameters:
+    """NewParameters."""
+
+    capabilities: Any | None = None
+
+
+@dataclass
+class NewResult:
+    """NewResult."""
+
+    session_id: str | None = None
+    accept_insecure_certs: bool | None = None
+    browser_name: str | None = None
+    browser_version: str | None = None
+    platform_name: str | None = None
+    set_window_rect: bool | None = None
+    user_agent: str | None = None
+    proxy: Any | None = None
+    unhandled_prompt_behavior: Any | None = None
+    web_socket_url: str | None = None
+
+
+@dataclass
+class SubscribeResult:
+    """SubscribeResult."""
+
+    subscription: Any | None = None
+
+
+@dataclass
 class UserPromptHandler:
-    """Represents the configuration of the user prompt handler."""
+    """UserPromptHandler."""
 
-    def __init__(
-        self,
-        alert: str | None = None,
-        before_unload: str | None = None,
-        confirm: str | None = None,
-        default: str | None = None,
-        file: str | None = None,
-        prompt: str | None = None,
-    ):
-        """Initialize UserPromptHandler.
+    alert: Any | None = None
+    before_unload: Any | None = None
+    confirm: Any | None = None
+    default: Any | None = None
+    file: Any | None = None
+    prompt: Any | None = None
 
-        Args:
-            alert: Handler type for alert prompts.
-            before_unload: Handler type for beforeUnload prompts.
-            confirm: Handler type for confirm prompts.
-            default: Default handler type for all prompts.
-            file: Handler type for file picker prompts.
-            prompt: Handler type for prompt dialogs.
-
-        Raises:
-            ValueError: If any handler type is not valid.
-        """
-        for field_name, value in [
-            ("alert", alert),
-            ("before_unload", before_unload),
-            ("confirm", confirm),
-            ("default", default),
-            ("file", file),
-            ("prompt", prompt),
-        ]:
-            if value is not None and value not in UserPromptHandlerType.VALID_TYPES:
-                raise ValueError(
-                    f"Invalid {field_name} handler type: {value}. Must be one of {UserPromptHandlerType.VALID_TYPES}"
-                )
-
-        self.alert = alert
-        self.before_unload = before_unload
-        self.confirm = confirm
-        self.default = default
-        self.file = file
-        self.prompt = prompt
-
-    def to_dict(self) -> dict[str, str]:
-        """Convert the UserPromptHandler to a dictionary for BiDi protocol.
-
-        Returns:
-            Dictionary representation suitable for BiDi protocol.
-        """
-        field_mapping = {
-            "alert": "alert",
-            "before_unload": "beforeUnload",
-            "confirm": "confirm",
-            "default": "default",
-            "file": "file",
-            "prompt": "prompt",
-        }
-
+    def to_bidi_dict(self) -> dict:
+        """Convert to BiDi protocol dict with camelCase keys."""
         result = {}
-        for attr_name, dict_key in field_mapping.items():
-            value = getattr(self, attr_name)
-            if value is not None:
-                result[dict_key] = value
+        if self.alert is not None:
+            result["alert"] = self.alert
+        if self.before_unload is not None:
+            result["beforeUnload"] = self.before_unload
+        if self.confirm is not None:
+            result["confirm"] = self.confirm
+        if self.default is not None:
+            result["default"] = self.default
+        if self.file is not None:
+            result["file"] = self.file
+        if self.prompt is not None:
+            result["prompt"] = self.prompt
         return result
 
-
 class Session:
-    def __init__(self, conn):
-        self.conn = conn
+    """WebDriver BiDi session module."""
 
-    def subscribe(self, *events, browsing_contexts=None):
-        params = {
-            "events": events,
-        }
-        if browsing_contexts is None:
-            browsing_contexts = []
-        if browsing_contexts:
-            params["browsingContexts"] = browsing_contexts
-        return command_builder("session.subscribe", params)
-
-    def unsubscribe(self, *events, browsing_contexts=None):
-        params = {
-            "events": events,
-        }
-        if browsing_contexts is None:
-            browsing_contexts = []
-        if browsing_contexts:
-            params["browsingContexts"] = browsing_contexts
-        return command_builder("session.unsubscribe", params)
+    def __init__(self, conn) -> None:
+        self._conn = conn
 
     def status(self):
-        """The session.status command returns information about the remote end's readiness.
+        """Execute session.status."""
+        params = {
+        }
+        params = {k: v for k, v in params.items() if v is not None}
+        cmd = command_builder("session.status", params)
+        result = self._conn.execute(cmd)
+        return result
 
-        Returns information about the remote end's readiness to create new sessions
-        and may include implementation-specific metadata.
+    def new(self, capabilities: Any | None = None):
+        """Execute session.new."""
+        if capabilities is None:
+            raise TypeError("new() missing required argument: {{snake_param!r}}")
 
-        Returns:
-            Dictionary containing the ready state (bool), message (str) and metadata.
-        """
-        cmd = command_builder("session.status", {})
-        return self.conn.execute(cmd)
+        params = {
+            "capabilities": capabilities,
+        }
+        params = {k: v for k, v in params.items() if v is not None}
+        cmd = command_builder("session.new", params)
+        result = self._conn.execute(cmd)
+        return result
+
+    def end(self):
+        """Execute session.end."""
+        params = {
+        }
+        params = {k: v for k, v in params.items() if v is not None}
+        cmd = command_builder("session.end", params)
+        result = self._conn.execute(cmd)
+        return result
+
+    def subscribe(
+        self,
+        events: list[Any] | None = None,
+        contexts: list[Any] | None = None,
+        user_contexts: list[Any] | None = None,
+    ):
+        """Execute session.subscribe."""
+        if events is None:
+            raise TypeError("subscribe() missing required argument: {{snake_param!r}}")
+
+        params = {
+            "events": events,
+            "contexts": contexts,
+            "userContexts": user_contexts,
+        }
+        params = {k: v for k, v in params.items() if v is not None}
+        cmd = command_builder("session.subscribe", params)
+        result = self._conn.execute(cmd)
+        return result
+
+    def unsubscribe(self, events: list[Any] | None = None, subscriptions: list[Any] | None = None):
+        """Execute session.unsubscribe."""
+        params = {
+            "events": events,
+            "subscriptions": subscriptions,
+        }
+        params = {k: v for k, v in params.items() if v is not None}
+        cmd = command_builder("session.unsubscribe", params)
+        result = self._conn.execute(cmd)
+        return result
+
diff --git a/skills.md b/skills.md
index 93ee0d2..9be642c 100644
--- a/skills.md
+++ b/skills.md
@@ -2,6 +2,8 @@
 
 This document captures best practices for contributors and coding agents working in the Selenium monorepo.
 
+See also: [AGENTS.md](AGENTS.md) and language-specific guides such as `<language>/AGENTS.md` for per-language agent usage details. Those files are the authoritative source of truth; this guide is a consolidated companion.
+
 ## Purpose
 
 Use this guide to make safe, focused changes across Selenium's multi-language bindings while staying aligned with Bazel-based workflows and project invariants.
@@ -44,7 +46,7 @@
 3. Run the smallest meaningful tests first:
 
 ```bash
-bazel test //path/to/area:target --test_output=all --cache_test_results=no
+bazel test //path/to/area:target --test_output=all
 ```
 
 4. Expand test scope only when the focused tests pass.
@@ -212,4 +214,4 @@
 - Logging, deprecation, or public documentation conventions
 - High-risk area definitions
 
-Treat the language-specific AGENTS files as source of truth and keep this file synchronized with them.
+The language-specific AGENTS files and the root `AGENTS.md` are the authoritative source of truth. This guide is a consolidated summary and must not contradict them. When a language-specific AGENTS file changes, update the corresponding section here to stay consistent.