Skip to content

[STG-1427] feat: per-method serverCache threshold configuration#2155

Open
BABTUNA wants to merge 2 commits into
browserbase:mainfrom
BABTUNA:revive/caching-threshold-config
Open

[STG-1427] feat: per-method serverCache threshold configuration#2155
BABTUNA wants to merge 2 commits into
browserbase:mainfrom
BABTUNA:revive/caching-threshold-config

Conversation

@BABTUNA
Copy link
Copy Markdown
Contributor

@BABTUNA BABTUNA commented May 21, 2026

What

Extends serverCache to accept boolean | { threshold: number } on act() / extract() / observe() options and on the Stagehand constructor. The constructor additionally accepts a per-method object for fine-grained control:

new Stagehand({
  serverCache: {
    act: { threshold: 5 },
    extract: false,
    observe: true,
  },
});

await stagehand.act("click login", { serverCache: { threshold: 10 } });

Method-level options take precedence over constructor-level defaults.

How

  • New resolveServerCacheBoolean() + resolveServerCacheThreshold() helpers in V3 collapse the constructor + per-call config to a boolean (for the cache-bypass header) and an optional number (for the new cacheThreshold wire field) on each request.
  • ActRequestSchema / ExtractRequestSchema / ObserveRequestSchema gain an optional cacheThreshold field; matching entries added to packages/server-v3/openapi.v3.yaml.
  • extractHandler marks incomplete extractions with a non-enumerable __extractionIncomplete property so the server-side cache layer can skip persisting them.
  • APIClient constructor only receives serverCache when it's a plain boolean — the per-method object form is handled per-request via cacheThreshold.

Note

Picks up #1732 by @sameelarif. PR went stale (~3 months) and had conflicts with main after the STG-1182 was added through #1581. Rebased onto current main and resolved conflicts so that this PR is purely the threshold layer on top of the already-merged on/off cache toggle.

Notable resolutions:

  • Took main's improved cache-status suppression logic in api.ts (suppresses MISS log/surface when caching was bypassed).
  • Kept main's cache-variables.test.ts and caching.mdx (both already shipped via [STG-1182] cache config #1581, the PR's older versions would have regressed them).
  • Used expectTypeOf().toExtend() in the ObserveResult type test (main's pattern). The strict toEqualTypeOf against an intersection type is brittle.

Summary by cubic

Add per-method server cache thresholds so you can tune when cached results are returned for act, extract, and observe. Implements STG-1427; method-level options override constructor defaults without breaking the on/off toggle.

  • New Features
    • serverCache now accepts boolean | { threshold: number } in Stagehand and per-method options; the constructor also supports { act, extract, observe }.
    • Resolved settings send a cache-bypass boolean and a cacheThreshold per request; method options take precedence over constructor values.
    • Added cacheThreshold to ActRequest, ExtractRequest, and ObserveRequest schemas and updated openapi.v3.yaml; API client logs browserbase-cache-status when present.
    • Incomplete extractions are marked with a non-enumerable __extractionIncomplete so the server cache skips persisting them.

Written for commit 9b6d859. Summary will update on new commits. Review in cubic

Extends serverCache to accept boolean | { threshold: number } on
act/extract/observe options and on the Stagehand constructor, with
the constructor additionally accepting a per-method object
({ act, extract, observe }) for fine-grained control.

Method-level options take precedence over constructor-level defaults.
A new cacheThreshold field on the wire schemas carries the resolved
threshold; the cache-bypass header is set when caching is disabled.

Also flags incomplete extractions via a non-enumerable
__extractionIncomplete property so the server-side cache layer can
skip persisting them.

Revives browserbase#1732 by sameelarif.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 21, 2026

🦋 Changeset detected

Latest commit: 9b6d859

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@browserbasehq/stagehand Minor
@browserbasehq/browse-cli Patch
@browserbasehq/stagehand-evals Patch
@browserbasehq/stagehand-server-v3 Patch
@browserbasehq/stagehand-server-v4 Patch

Not sure what this means? Click here to learn what changesets are.

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

@github-actions
Copy link
Copy Markdown
Contributor

This PR is from an external contributor and must be approved by a stagehand team member with write access before CI can run.
Approving the latest commit mirrors it into an internal PR owned by the approver.
If new commits are pushed later, the internal PR stays open but is marked stale until someone approves the latest external commit and refreshes it.

@github-actions github-actions Bot added external-contributor Tracks PRs mirrored from external contributor forks. external-contributor:awaiting-approval Waiting for a stagehand team member to approve the latest external commit. labels May 21, 2026
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

1 issue found across 9 files

Confidence score: 4/5

  • This PR looks safe to merge overall; the only noted issue is a release metadata mismatch rather than a runtime/code-path defect.
  • The most significant risk is in .changeset/slow-insects-lie.md: labeling an additive public API change as patch instead of minor could under-communicate impact to consumers and affect versioning expectations.
  • Given the issue’s moderate-low severity (4/10) and non-functional scope, merge risk appears minimal if the changeset type is corrected promptly.
  • Pay close attention to .changeset/slow-insects-lie.md - ensure the semver bump reflects the additive API change (minor vs patch).
Architecture diagram
sequenceDiagram
    participant Client as SDK Consumer
    participant V3 as V3 Engine
    participant Resolver as resolveServerCache*
    participant API as APIClient
    
    Note over Client,API: NEW: Per-Method Cache Threshold Flow
    
    Client->>V3: stagehand.act("click", { serverCache: { threshold: 10 } })
    
    V3->>Resolver: resolveServerCacheBoolean("act", { threshold: 10 })
    Resolver-->>V3: true
    V3->>Resolver: resolveServerCacheThreshold("act", { threshold: 10 })
    Resolver-->>V3: 10
    
    V3->>API: act({ input, options: { serverCache: true }, cacheThreshold: 10 })
    
    API->>API: Strip serverCache from options, derive cache-bypass header
    alt serverCache is object { threshold }
        API->>API: Set serverCache = true (caching enabled)
    end
    API->>API: Include cacheThreshold in request body
    API-->>V3: ActResult (with cache metadata)
    
    Note over Client,API: Constructor-Level Per-Method Defaults
    
    Client->>V3: new Stagehand({ serverCache: { act: { threshold: 5 }, extract: false, observe: true } })
    V3->>API: Constructor stores config
    API-->>V3: APIClient with serverCache=undefined (delegates to per-request)
    
    Client->>V3: stagehand.extract("get user info")  // no method-level override
    V3->>Resolver: resolveServerCacheBoolean("extract")
    alt Constructor config is object
        V3->>Resolver: Lookup this.opts.serverCache["extract"] = false
        Resolver-->>V3: false (caching disabled)
    end
    V3->>Resolver: resolveServerCacheThreshold("extract")
    Resolver-->>V3: undefined (boolean, no threshold)
    
    Note over V3,API: Extraction Incomplete Marker
    
    API->>API: extract() sends request with cacheThreshold(undefined)
    API-->>V3: ExtractResult
    
    V3->>V3: extractHandler runs, process complete
    alt Extraction not fully complete
        V3->>V3: Mark output with non-enumerable __extractionIncomplete = true
        Note right of V3: Prevents caching of partial results
    end
    
    Note over Client,API: Priority Resolution (method > constructor > default)
    
    Client->>V3: stagehand.observe("find buttons", { serverCache: false })
    V3->>Resolver: resolveServerCacheBoolean("observe", false)
    alt Method-level false overrides constructor-level true
        Resolver-->>V3: false (bypass cache)
    end
    V3->>API: observe({ options: { serverCache: false } })
    API->>API: Send cache-bypass header, no cacheThreshold
    
    API-->>V3: ObserveResult
    
    Client->>V3: stagehand.act("type text")  // no method-level, constructor has threshold object
    V3->>Resolver: resolveServerCacheBoolean("act")
    alt Constructor serverCache is object, method setting exists
        V3->>Resolver: this.opts.serverCache["act"] = { threshold: 5 }
        Resolver-->>V3: true (caching enabled)
    end
    V3->>Resolver: resolveServerCacheThreshold("act")
    Resolver-->>V3: 5 (from constructor default)
    V3->>API: act({ cacheThreshold: 5 })
Loading

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread .changeset/slow-insects-lie.md Outdated
Per cubic review on browserbase#2155: extending the public serverCache option to
accept boolean | { threshold: number } and per-method config objects is
an additive surface change, so the semver bump should be minor rather
than patch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external-contributor:awaiting-approval Waiting for a stagehand team member to approve the latest external commit. external-contributor Tracks PRs mirrored from external contributor forks.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants