Skip to content

feat(integrations): add Deep Agents examples - #2653

Merged
shrey150 merged 9 commits into
mainfrom
feat/deepagents-integration
Aug 10, 2026
Merged

feat(integrations): add Deep Agents examples#2653
shrey150 merged 9 commits into
mainfrom
feat/deepagents-integration

Conversation

@miguelg719

@miguelg719 miguelg719 commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Deep Agents (LangChain) examples for the Stagehand facade tool surface, in Python.

  • Tools: run / snapshot / screenshot — the shared facade contract, served by a Python stdio MCP server (stagehand_deepagents) backed by the published stagehand PyPI package.
  • Two examples: examples/local (deepagents OSS + langchain-mcp-adapters over stdio, persistent MCP session) and examples/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).
  • Publish-aware: stagehand>=4.0.0 from PyPI; the wheel bundles the browser extension, so the previously vendored extension build and upload machinery are gone.
  • Run: 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).

@changeset-bot

changeset-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: df620a0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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:

  • packages/integrations/deepagents/examples/managed/tools/_assets/stagehand_extension/service-worker.js (~58,999 changed lines)
  • packages/integrations/deepagents/examples/managed/tools/_assets/stagehand_extension/content-script.js (~1,155 changed lines)
  • packages/integrations/deepagents/examples/managed/tools/stagehand.py (~394 changed lines)
  • packages/integrations/deepagents/src/stagehand_deepagents/runtime.py (~309 changed lines)
  • packages/integrations/deepagents/src/stagehand_deepagents/server.py (~281 changed lines)

Comment @cubic-dev-ai review this to review it anyway. If the largest files are generated or fixture data, add them to your ignored files in review settings or ignorePatterns in cubic.yaml - cubic will then review the rest automatically. You can also raise this limit in review settings.

@socket-security

socket-security Bot commented Aug 9, 2026

Copy link
Copy Markdown

@socket-security

socket-security Bot commented Aug 9, 2026

Copy link
Copy Markdown

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.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: pypi orjson is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: packages/integrations/deepagents/examples/local/uv.lockpypi/langchain@1.3.14pypi/langchain-mcp-adapters@0.3.2pypi/deepagents@0.7.5pypi/langchain-openai@1.4.2pypi/orjson@3.11.9

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore pypi/orjson@3.11.9. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: pypi pycparser is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: packages/integrations/deepagents/examples/local/uv.lockpypi/deepagents@0.7.5pypi/pycparser@3.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore pypi/pycparser@3.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: pypi pycparser is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: packages/integrations/deepagents/examples/local/uv.lockpypi/deepagents@0.7.5pypi/pycparser@3.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore pypi/pycparser@3.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

miguelg719 added a commit that referenced this pull request Aug 10, 2026
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. -->
miguelg719 added a commit that referenced this pull request Aug 10, 2026
…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.
@miguelg719
miguelg719 force-pushed the feat/deepagents-integration branch from c7a4d7d to 428644e Compare August 10, 2026 07:21
@miguelg719
miguelg719 requested a review from a team as a code owner August 10, 2026 07:21
@miguelg719
miguelg719 changed the base branch from v4-spike to main August 10, 2026 07:21

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Loading

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>

Comment thread packages/integrations/deepagents/examples/managed/tools/stagehand.py Outdated
Comment thread packages/integrations/deepagents/README.md
Comment thread packages/integrations/deepagents/examples/local/check_browser.py Outdated
Comment thread packages/integrations/deepagents/examples/local/pyproject.toml
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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 example 75.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 editable sdk-python checkout over the published stagehand wheel, 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/false STAGEHAND_HEADLESS handling — add targeted RuntimeConfig.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

Comment thread packages/integrations/deepagents/src/stagehand_deepagents/server.py
Comment thread packages/integrations/deepagents/examples/local/_client.py
Comment thread packages/integrations/deepagents/src/stagehand_deepagents/runtime.py Outdated
Comment thread packages/integrations/deepagents/examples/local/_client.py
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
@mintlify

mintlify Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
stagehand 🟢 Ready View Preview Aug 10, 2026, 3:06 PM

💡 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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 because asyncio.CancelledError is not caught, which can leave stale Browserbase keep-alive sessions stranded and accumulate leaked resources — handle cancellation in the same release flow (or finally) so sessions are always released.
  • packages/integrations/deepagents/examples/managed/tools/stagehand.py now awaits _release_session while _BrowserRegistry.get still 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 screenshot quality breaks caller expectations and weakens the MCP contract, creating hard-to-diagnose behavior differences across clients — reject non-integer input at the boundary and document/expose quality consistently.
  • 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 in quality handling 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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.)

View Feedback

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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 mock define_deep_agent and 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"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.)

View Feedback

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>

@shrey150
shrey150 merged commit 8869862 into main Aug 10, 2026
19 checks passed
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.

2 participants