feat(integrations): add Deep Agents examples - #2653
Conversation
|
|
This PR has 61,816 reviewable changed lines after ignored/generated files are excluded, above cubic's default 50,000-changed-line automatic review limit. The raw diff is 65,117 lines before ignored/generated files are excluded. Most of the diff comes from:
Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
Adds a readonly `sessionId` to `StagehandBrowser` for Browserbase-backed handles (`browserbase.launch` / `browserbase.connect`); undefined for local browsers. The id was already threaded internally through the worker init metadata — this only surfaces it. **Why:** integration examples that persist sessions for reconnect-after-restart (Eve native tools in #2666, managed deep agents in #2653) currently have to recover the id out-of-band — stamping a `userMetadata` marker at launch and querying `sessions.list` — because the handle doesn't expose it. With this, that workaround collapses to `browser.sessionId`. **Scope:** two files (`browser/index.ts`, `browser/factories.ts`) + changeset. No behavior change; purely additive surface. Gate: build, typecheck, 186/186 unit tests. **Port parity:** TS is the contract — Python/Go should mirror (`session_id` on the Python browser handle) in follow-ups; the Python managed-deepagents example has the same workaround to delete. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Added a readonly `sessionId` to `StagehandBrowser` handles for Browserbase-backed browsers (`browserbase.launch` / `browserbase.connect`). It’s undefined for local browsers and makes reconnect-after-restart flows simpler. - **New Features** - Access the Browserbase session id via `browser.sessionId`; avoids metadata markers + `sessions.list`. - Purely additive API; no behavior changes. <sup>Written for commit 33bb054. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2672?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
…2673) Python port of #2672 (TS is the contract; this mirrors it exactly): readonly `session_id` property on `StagehandBrowser`, populated from the worker init metadata for Browserbase launch/connect, `None` for local browsers. Kills the out-of-band session-id recovery in the managed deep-agents example (#2653), same as #2672 does for the Eve example (#2666). Gate: ruff format/check, ty check, 457 passed / 1 skipped. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Add a read-only session_id property to StagehandBrowser in `sdk-python`, populated from worker init metadata for Browserbase and None for local. This lets clients persist the Browserbase session ID for reconnects without out-of-band recovery. <sup>Written for commit dad895e. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2673?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
Rebased onto main post-v4-release (squashed; full review/smoke history on PR #2653) and updated for the published packages: stagehand resolves from PyPI 4.0.0 (path source retained for in-repo dev), and the managed example drops its vendored extension build plus the manual zip/upload/delete machinery — the published wheel bundles the extension and browserbase.launch provisions it automatically.
c7a4d7d to
428644e
Compare
There was a problem hiding this comment.
1 issue found across 28 files
Architecture diagram
sequenceDiagram
participant Dev as Developer
participant Agent as Deep Agent
participant Client as MCP Client (langchain-mcp-adapters)
participant Server as Stdio MCP Server (stagehand-deepagents)
participant RT as BrowserTools Runtime
participant SDK as Stagehand Python SDK
participant Br as Browser (Chrome / Browserbase)
Note over Dev,Br: Local example: agent.py drives Deep Agents over a persistent stdio MCP session
Dev->>Agent: uv run agent.py (instruction, model, response_format)
Agent->>Client: load_mcp_tools(session)
Client->>Server: spawn stdio process (uv run stagehand-deepagents-mcp)
Server->>Server: RuntimeConfig.from_env() (STAGEHAND_BROWSER, keys, model, timeout)
Client->>Server: initialize / tools/list
Server-->>Client: NEW: exposes exactly run, snapshot, screenshot (browser not launched)
Client-->>Agent: tool handles
Agent->>Client: run(code: "page.goto(...) ...")
Client->>Server: tools/call run
Server->>RT: BrowserTools.start(config) - lazy on first tool call
alt STAGEHAND_BROWSER=local
RT->>Br: local_browser.launch(headless)
else browserbase
RT->>Br: browserbase.launch(viewport 1280x720, keep_alive)
end
RT->>SDK: Stagehand.create(browser, model, api_url)
Server->>RT: run(code=...)
RT->>SDK: experimental_batch(playwright_facade.js + user code)
SDK->>Br: execute inside browser extension service worker (browser-side, not host)
Br-->>RT: result
RT-->>Server: serialized result
Server-->>Client: JSON-RPC response (New: errors redact apiKey/sk-*)
Client-->>Agent: run output
Agent->>Client: snapshot()
Client->>Server: tools/call snapshot
Server->>RT: snapshot(include_iframes)
RT->>SDK: page.snapshot()
RT->>RT: NEW: store xpath_by_id per page_id (replaces prior map)
RT-->>Agent: formatted accessibility tree with bracketed IDs
Agent->>Client: run(actions: [{op: click, id: ...}])
Client->>Server: tools/call run
Server->>RT: run(actions=...)
alt snapshot in cache and page URL unchanged
RT->>RT: hydrate IDs to CSS selectors (strip /text() suffix)
RT->>SDK: experimental_batch(_ACTION_SOURCE)
SDK->>Br: batch click/fill/type/press/select
RT-->>Agent: {completed, url}
else stale or missing snapshot
RT-->>Agent: NEW: error - call snapshot again after navigation
end
Note over Dev,Br: Managed example: deployed agent uses authored langchain tools, not MCP stdio
Dev->>Agent: mda deploy (agent.py with run/snapshot/screenshot)
Agent->>Agent: ToolRuntime injects thread ID (not exposed to model)
Agent->>Br: Browserbase module-level session - launch or reconnect by session_id
Br-->>Agent: session_id
Agent->>SDK: Stagehand.create(browser, model/api_url BYOK or Model Gateway)
Agent->>Br: best-effort REQUEST_RELEASE on expiry (requires BROWSERBASE_PROJECT_ID)
Note over Agent,Br: CHANGED: stagehand>=4.0.0 wheel bundles extension - no manual extension upload
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/integrations/deepagents/src/stagehand_deepagents/__init__.py">
<violation number="1" location="packages/integrations/deepagents/src/stagehand_deepagents/__init__.py:5">
P3: This makes mutable runtime implementation types part of the package's supported import surface, creating a compatibility commitment for `BrowserTools` and `RuntimeConfig` although the integration is documented as a stdio MCP server. Keep them internal and let the console entry point expose the server behavior instead.
(Based on your team's feedback about exposing internal types as public APIs.)</violation>
</file>
Local examples: - shared _client.py builds the MCP client with an explicit STAGEHAND_*/BROWSERBASE_* env allowlist — the stdio client strips the parent env, so Browserbase/model config silently never reached the server before - drop leftover debug sleep in check_browser.py Server: - from_env headless default now True, matching the dataclass and README - run description restores the reference anti-hallucination guidance (never "kind"/"ref", JSON examples) - screenshot quality accepts numbers per the advertised schema and rejects booleans - stale comment path corrected to core/src/facade/contract.ts Managed example: - session release no longer gated on BROWSERBASE_PROJECT_ID (the API doesn't require it), so TTL expiry stops stranding paid sessions - reconnect trigger replaced with a real liveness probe (browser.closed never flips on socket drop) - expiry cleanup contains per-entry close failures instead of failing unrelated tool calls - tool errors sanitized before reaching the model (mirrors the server) - stale-snapshot message gains the recovery hint; facade asset cached
There was a problem hiding this comment.
4 issues found across 8 files (changes from recent commits).
Confidence score: 3/5
- In
packages/integrations/deepagents/examples/managed/tools/stagehand.py, reconnect failure can abandon an existing keep-alive Browserbase session before a replacement is confirmed, which risks leaking paid sessions until provider-side expiry — explicitly close/release the stale session on reconnect failure paths before retrying. - In
packages/integrations/deepagents/src/stagehand_deepagents/server.py, relaxed screenshot quality validation now accepts floats (for example75.5) even though downstream expects integers, so requests can pass server checks but fail or behave inconsistently later — restore strict integer validation (or coerce deterministically) at the API boundary. - In
packages/integrations/deepagents/examples/local/_client.py, examples currently prioritize an editablesdk-pythoncheckout over the publishedstagehandwheel, which can make documented integration behavior diverge from what users run in production — remove the source override so examples exercise the shipped package path. - In
packages/integrations/deepagents/src/stagehand_deepagents/runtime.py, the default browser mode changed without executable coverage, leaving regression risk around unset/falseSTAGEHAND_HEADLESShandling — add targetedRuntimeConfig.from_env()tests for default and explicit false cases.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/integrations/deepagents/src/stagehand_deepagents/runtime.py">
<violation number="1" location="packages/integrations/deepagents/src/stagehand_deepagents/runtime.py:79">
P3: The default browser mode changed without executable coverage. A focused `RuntimeConfig.from_env()` test for unset `STAGEHAND_HEADLESS` (and ideally an explicit false override) would prevent this configuration default from silently regressing.</violation>
</file>
<file name="packages/integrations/deepagents/examples/managed/tools/stagehand.py">
<violation number="1" location="packages/integrations/deepagents/examples/managed/tools/stagehand.py:334">
P1: A failed reconnect abandons the previous keep-alive Browserbase session before launching a replacement, so transient reconnect failures leave paid sessions running until provider expiry. Release the stale session (and dispose its local resources) when revival fails before creating a fresh runtime.</violation>
</file>
<file name="packages/integrations/deepagents/src/stagehand_deepagents/server.py">
<violation number="1" location="packages/integrations/deepagents/src/stagehand_deepagents/server.py:224">
P1: Custom agent flagged.
Screenshot quality validation was relaxed to accept floats, which conflicts with the requirement that quality values be integers. A float like `75.5` now passes server validation but is forwarded to Playwright, which expects an `int`, causing a downstream contract mismatch. Please tighten the check back to `int`-only while keeping the explicit `bool` rejection: use `not isinstance(quality, int)` plus the `bool` guard, and keep the error message as `"quality must be an integer"`.</violation>
</file>
<file name="packages/integrations/deepagents/examples/local/_client.py">
<violation number="1" location="packages/integrations/deepagents/examples/local/_client.py:40">
P2: Local examples launch an editable `sdk-python` checkout instead of the published `stagehand` wheel, so extension/runtime behavior can diverge from the documented integration. Remove the root `stagehand` source override and regenerate its lockfile for this published-package example.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
The multi-language surface extraction exceeds the 5s default on cold PR runners (observed 5090ms+, failing twice on this branch).
- coerce numeric screenshot quality to int before CDP - release the old keep-alive session when managed reconnect fails - declare python-dotenv in the local example
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
An overly broad git add in 2c2d712 swept uncommitted local experiments (google_genai model swap + debug flags) into the commit; google_genai isn't a declared dependency of the example.
There was a problem hiding this comment.
4 issues found across 4 files (changes from recent commits).
Confidence score: 3/5
- In
packages/integrations/deepagents/examples/managed/tools/stagehand.py, cancellation can bypass the new cleanup path becauseasyncio.CancelledErroris not caught, which can leave stale Browserbase keep-alive sessions stranded and accumulate leaked resources — handle cancellation in the same release flow (orfinally) so sessions are always released. packages/integrations/deepagents/examples/managed/tools/stagehand.pynow awaits_release_sessionwhile_BrowserRegistry.getstill holds the registry lock, so a slow/hung Browserbase call can block unrelated lookups and stall concurrent tool calls — move network I/O outside the lock and add a timeout around release.- In
packages/integrations/deepagents/src/stagehand_deepagents/runtime.py, silently rounding fractional screenshotqualitybreaks caller expectations and weakens the MCP contract, creating hard-to-diagnose behavior differences across clients — reject non-integer input at the boundary and document/exposequalityconsistently. - Also in
packages/integrations/deepagents/src/stagehand_deepagents/runtime.py, the new validation path appears to block the introduced normalization/range logic without unit coverage, so regressions inqualityhandling may slip through — add targeted tests for fractional input, range checks, and png+quality rejection paths.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/integrations/deepagents/examples/managed/tools/stagehand.py">
<violation number="1" location="packages/integrations/deepagents/examples/managed/tools/stagehand.py:350">
P2: Cancelled managed-tool calls can still strand the stale Browserbase keep-alive session: `asyncio.CancelledError` bypasses this `except Exception`, so the new release path never runs. Include cancellation in the cleanup handler while allowing it to propagate afterward.</violation>
<violation number="2" location="packages/integrations/deepagents/examples/managed/tools/stagehand.py:350">
P3: This new best-effort session release issues an awaited Browserbase API request (via `_release_session`) while `_BrowserRegistry.get` still holds the registry-wide `self.lock`. If the API call is slow or hangs (no timeout is set on it), every concurrent tool invocation that calls `_REGISTRY.get` for any thread will block on that lock for the whole request. Consider releasing the stale session outside the lock — e.g. via `asyncio.create_task(...)` for the fire-and-forget cleanup, or at least setting an explicit timeout on the release — so a degraded Browserbase API doesn't stall unrelated browser calls during this failure-recovery path.</violation>
</file>
<file name="packages/integrations/deepagents/src/stagehand_deepagents/runtime.py">
<violation number="1" location="packages/integrations/deepagents/src/stagehand_deepagents/runtime.py:176">
P2: Fractional `quality` requests are silently rounded rather than rejected, so callers cannot rely on the screenshot contract preserving their supplied value. Require an integer at the MCP boundary and expose `quality` as JSON Schema `integer` instead of rounding it.
(Based on your team's feedback about enforcing JPEG screenshot quality.)</violation>
<violation number="2" location="packages/integrations/deepagents/src/stagehand_deepagents/runtime.py:176">
P3: The new screenshot quality validation blocks the newly introduced `quality = round(quality)` normalization plus its validation path (range check, png+quality rejection) with no accompanying unit tests. Since server.py accepts quality as a float over the wire and this rounding now determines the value passed to page.screenshot, consider adding a couple of focused tests on BrowserTools.screenshot covering the typical path (e.g. a fractional quality like 87.4 being rounded) and the key edge cases (below 0 / above 100 raising, and png with quality raising). Encoding this behavior in tests would prevent regressions as the tool surface evolves.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| # Reconnect failed — release the old keep-alive session | ||
| # best-effort so the replacement doesn't strand it. | ||
| if stale.browser.browserbase_api_key: | ||
| await _release_session( |
There was a problem hiding this comment.
P2: Cancelled managed-tool calls can still strand the stale Browserbase keep-alive session: asyncio.CancelledError bypasses this except Exception, so the new release path never runs. Include cancellation in the cleanup handler while allowing it to propagate afterward.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/deepagents/examples/managed/tools/stagehand.py, line 350:
<comment>Cancelled managed-tool calls can still strand the stale Browserbase keep-alive session: `asyncio.CancelledError` bypasses this `except Exception`, so the new release path never runs. Include cancellation in the cleanup handler while allowing it to propagate afterward.</comment>
<file context>
@@ -344,6 +344,14 @@ async def get(self, thread_id: str) -> _BrowserRuntime:
+ # Reconnect failed — release the old keep-alive session
+ # best-effort so the replacement doesn't strand it.
+ if stale.browser.browserbase_api_key:
+ await _release_session(
+ stale.browser.browserbase_api_key,
+ stale.browser.browserbase_api_url,
</file context>
| if quality is not None: | ||
| if not 0 <= quality <= 100: | ||
| raise ValueError("quality must be between 0 and 100") | ||
| quality = round(quality) |
There was a problem hiding this comment.
P2: Fractional quality requests are silently rounded rather than rejected, so callers cannot rely on the screenshot contract preserving their supplied value. Require an integer at the MCP boundary and expose quality as JSON Schema integer instead of rounding it.
(Based on your team's feedback about enforcing JPEG screenshot quality.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/deepagents/src/stagehand_deepagents/runtime.py, line 176:
<comment>Fractional `quality` requests are silently rounded rather than rejected, so callers cannot rely on the screenshot contract preserving their supplied value. Require an integer at the MCP boundary and expose `quality` as JSON Schema `integer` instead of rounding it.
(Based on your team's feedback about enforcing JPEG screenshot quality.) </comment>
<file context>
@@ -170,8 +170,10 @@ async def screenshot(
+ if quality is not None:
+ if not 0 <= quality <= 100:
+ raise ValueError("quality must be between 0 and 100")
+ quality = round(quality)
if type == "png" and quality is not None:
raise ValueError("quality is only valid for jpeg screenshots")
</file context>
| # Reconnect failed — release the old keep-alive session | ||
| # best-effort so the replacement doesn't strand it. | ||
| if stale.browser.browserbase_api_key: | ||
| await _release_session( |
There was a problem hiding this comment.
P3: This new best-effort session release issues an awaited Browserbase API request (via _release_session) while _BrowserRegistry.get still holds the registry-wide self.lock. If the API call is slow or hangs (no timeout is set on it), every concurrent tool invocation that calls _REGISTRY.get for any thread will block on that lock for the whole request. Consider releasing the stale session outside the lock — e.g. via asyncio.create_task(...) for the fire-and-forget cleanup, or at least setting an explicit timeout on the release — so a degraded Browserbase API doesn't stall unrelated browser calls during this failure-recovery path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/deepagents/examples/managed/tools/stagehand.py, line 350:
<comment>This new best-effort session release issues an awaited Browserbase API request (via `_release_session`) while `_BrowserRegistry.get` still holds the registry-wide `self.lock`. If the API call is slow or hangs (no timeout is set on it), every concurrent tool invocation that calls `_REGISTRY.get` for any thread will block on that lock for the whole request. Consider releasing the stale session outside the lock — e.g. via `asyncio.create_task(...)` for the fire-and-forget cleanup, or at least setting an explicit timeout on the release — so a degraded Browserbase API doesn't stall unrelated browser calls during this failure-recovery path.</comment>
<file context>
@@ -344,6 +344,14 @@ async def get(self, thread_id: str) -> _BrowserRuntime:
+ # Reconnect failed — release the old keep-alive session
+ # best-effort so the replacement doesn't strand it.
+ if stale.browser.browserbase_api_key:
+ await _release_session(
+ stale.browser.browserbase_api_key,
+ stale.browser.browserbase_api_url,
</file context>
| if quality is not None: | ||
| if not 0 <= quality <= 100: | ||
| raise ValueError("quality must be between 0 and 100") | ||
| quality = round(quality) |
There was a problem hiding this comment.
P3: The new screenshot quality validation blocks the newly introduced quality = round(quality) normalization plus its validation path (range check, png+quality rejection) with no accompanying unit tests. Since server.py accepts quality as a float over the wire and this rounding now determines the value passed to page.screenshot, consider adding a couple of focused tests on BrowserTools.screenshot covering the typical path (e.g. a fractional quality like 87.4 being rounded) and the key edge cases (below 0 / above 100 raising, and png with quality raising). Encoding this behavior in tests would prevent regressions as the tool surface evolves.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/deepagents/src/stagehand_deepagents/runtime.py, line 176:
<comment>The new screenshot quality validation blocks the newly introduced `quality = round(quality)` normalization plus its validation path (range check, png+quality rejection) with no accompanying unit tests. Since server.py accepts quality as a float over the wire and this rounding now determines the value passed to page.screenshot, consider adding a couple of focused tests on BrowserTools.screenshot covering the typical path (e.g. a fractional quality like 87.4 being rounded) and the key edge cases (below 0 / above 100 raising, and png with quality raising). Encoding this behavior in tests would prevent regressions as the tool surface evolves.</comment>
<file context>
@@ -170,8 +170,10 @@ async def screenshot(
+ if quality is not None:
+ if not 0 <= quality <= 100:
+ raise ValueError("quality must be between 0 and 100")
+ quality = round(quality)
if type == "png" and quality is not None:
raise ValueError("quality is only valid for jpeg screenshots")
</file context>
….6-luna Was a hard KeyError when DEEPAGENTS_MODEL was unset.
There was a problem hiding this comment.
1 issue found across 6 files (changes from recent commits).
Confidence score: 5/5
- In
packages/integrations/deepagents/examples/managed/agent.py, the managed-agent model fallback path is currently untested, so default vs. override environment handling could silently drift and cause incorrect model selection at runtime—add focused tests that mockdefine_deep_agentand assert both environment cases.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/integrations/deepagents/examples/managed/agent.py">
<violation number="1" location="packages/integrations/deepagents/examples/managed/agent.py:8">
P3: The new managed-agent model fallback has no executable coverage for either its default or override behavior. Add focused tests that mock `define_deep_agent` and assert both environment cases.
(Based on your team's feedback about adding unit tests for new behavior.)</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
|
||
| agent = define_deep_agent( | ||
| name="stagehand-browser-agent", | ||
| model=os.environ.get("DEEPAGENTS_MODEL", "openai:gpt-5.6-luna"), |
There was a problem hiding this comment.
P3: The new managed-agent model fallback has no executable coverage for either its default or override behavior. Add focused tests that mock define_deep_agent and assert both environment cases.
(Based on your team's feedback about adding unit tests for new behavior.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/integrations/deepagents/examples/managed/agent.py, line 8:
<comment>The new managed-agent model fallback has no executable coverage for either its default or override behavior. Add focused tests that mock `define_deep_agent` and assert both environment cases.
(Based on your team's feedback about adding unit tests for new behavior.) </comment>
<file context>
@@ -5,6 +5,6 @@
agent = define_deep_agent(
name="stagehand-browser-agent",
- model=os.environ["DEEPAGENTS_MODEL"],
+ model=os.environ.get("DEEPAGENTS_MODEL", "openai:gpt-5.6-luna"),
tools=[run, snapshot, screenshot],
)
</file context>
Deep Agents (LangChain) examples for the Stagehand facade tool surface, in Python.
run/snapshot/screenshot— the shared facade contract, served by a Python stdio MCP server (stagehand_deepagents) backed by the publishedstagehandPyPI package.examples/local(deepagents OSS +langchain-mcp-adaptersover stdio, persistent MCP session) andexamples/managed(LangSmith Managed Deep Agents with authored tools — stdio is not supported there; module-level Browserbase session with keep-alive + reconnect-by-session-id, best-effort release on expiry).stagehand>=4.0.0from PyPI; the wheel bundles the browser extension, so the previously vendored extension build and upload machinery are gone.packages/integrations/deepagents/README.md.Verified: server contract tests in CI (6); ruff clean; real-browser flows exercised during review rounds.
Base: main (independent of the facade-core TS stack; same tool contract).