Skip to content

[AP-2925] Enable server-side caching by default in v4 - #2964

Merged
sameelarif merged 7 commits into
mainfrom
sameelarif/ap-2925-ensure-caching-is-enabled-by-default-in-v4
Sep 22, 2026
Merged

sameelarif merged 7 commits into
mainfrom
sameelarif/ap-2925-ensure-caching-is-enabled-by-default-in-v4

Conversation

@sameelarif

@sameelarif sameelarif commented Sep 17, 2026

Copy link
Copy Markdown
Member

v4 shipped with server-side caching opt-in, where v3 had it on by default. Anyone who upgraded without passing cache lost caching without an error.

Change

Both defaults flip to true, so v4 matches v3: caching runs unless the instance or the call opts out.

Docs

v4/best-practices/caching.mdx described opt-in, which this makes wrong.

The v3 migration guide also mapped enableCachingcache. enableCaching was a v2-era local option that v3 had already replaced with cacheDir; the server-side option v3 users actually had was serverCache, which appeared nowhere in the v4 docs. Corrected the mapping and the diff example.

🤖 Generated with Claude Code


Summary by cubic

Fixes AP-2925 by restoring v3's caching default in v4: caching was opt-in and is now enabled unless the instance or call opts out. This prevents upgrades from silently losing caching; the server's project feature flag still controls availability.

  • Adds tests for the omitted default, explicit opt-out, and default cache lookup, and regenerates the embedded Go extension archive to match the merged extension source after resolving a conflict with main.
  • Corrects the migration guide to map serverCache to cache and remove outdated enableCaching/cacheDir mappings; updates caching docs for locator-scoped bypasses, DISABLED status, the stagehand.act opt-out example, and create() examples that show the default doing the work.
  • Updates credential-safety guidance in act, prompting, and the Browser Use migration to warn that variable values reach the cache service by default, recommending cache: false on calls carrying credentials.

Written for commit a4ee44f. Summary will update on new commits.

Review in cubic

v3 cached every eligible act/observe/extract unless the call opted out via
`serverCache: false` — the server decided, gated on the per-project
LaunchDarkly flag. v4 added a client-side gate that defaults to off, so
`withCache` returns before it ever calls /v1/cache/get and metadata reports
DISABLED. Anyone who upgraded without passing `cache` silently lost caching,
including projects with the LaunchDarkly flag already enabled.

Flip the default so v4 matches v3: caching is on unless the instance or the
call opts out. The server still gates every lookup on
`stagehand-api-server-caching`, so this only decides whether we ask.

No existing test covered the default — baseArgs() always passed
`caching: true` — which is how the inversion shipped. Added three: the
buildCacheContext default, an explicit opt-out, and a lookup with neither
the request nor the instance setting `cache`.

Docs said caching was opt-in, which this makes wrong. Also corrects the v3
migration guide, which mapped `enableCaching` (a v2-era local option that v3
had already replaced with `cacheDir`) to `cache`, and never mentioned
`serverCache` — the option v3 users actually had.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sameelarif
sameelarif requested a review from a team as a code owner September 17, 2026 17:50
@mintlify

mintlify Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

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

Project Status Preview Updated
stagehand 🟢 Ready View Preview Sep 22, 2026, 5:19 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

@changeset-bot

changeset-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: a4ee44f

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

All reported issues were addressed across 4 files

Architecture diagram
sequenceDiagram
    participant App as Client App
    participant SH as Stagehand Client v4
    participant CacheSvc as Cache Service
    participant API as Stagehand API
    participant LD as LaunchDarkly
    participant Cache as Cache Store

    Note over App, Cache: Caching Flow

    App->>SH: create({ browser, apiKey })
    SH->>CacheSvc: buildCacheContext(initParams)
    CacheSvc->>CacheSvc: defaultCaching = cache ?? true

    App->>SH: act("...") or observe() or extract()
    SH->>CacheSvc: withCache({ caching, context, execute })

    alt Request or instance sets cache: false
        CacheSvc->>CacheSvc: resolvedCaching = false
        CacheSvc->>SH: execute() directly
        SH-->>App: Result with cache.status = "DISABLED"
    else Default path (no explicit cache option)
        CacheSvc->>CacheSvc: resolvedCaching = defaultCaching (true)
        CacheSvc->>CacheSvc: cachePage = asCachePage(page)
        CacheSvc->>API: GET /v1/cache/get
        API->>LD: Check stagehand-api-server-caching flag
        alt Flag disabled per project
            LD-->>API: false
            API-->>CacheSvc: No cache lookup
            CacheSvc->>CacheSvc: execute()
            SH-->>App: Result with cache.status = "DISABLED"
        else Flag enabled per project
            LD-->>API: true
            API->>Cache: Lookup by cache key
            alt Cache hit
                Cache-->>API: Cached result
                API-->>CacheSvc: Cache hit
                CacheSvc->>CacheSvc: onHit()
                SH-->>App: Result with cache.status = "HIT"
            else Cache miss
                Cache-->>API: Not found
                API-->>CacheSvc: Miss
                CacheSvc->>CacheSvc: execute()
                API->>Cache: Store result
                SH-->>App: Result with cache.status = "MISS"
            end
        end
    end
Loading

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

Re-trigger cubic

Comment thread packages/docs/v4/best-practices/caching.mdx Outdated
Comment thread packages/docs/v4/migrations/v3.mdx Outdated
Comment thread packages/docs/v4/migrations/v3.mdx Outdated
Comment thread packages/docs/v4/best-practices/caching.mdx Outdated
- Locator-scoped calls bypass the cache entirely
  (shouldBypassCacheForLocatorScope gates act/observe/extract on
  `locator` or a non-empty `ignoreLocators`, and withCache returns before
  any read or write). The intro claimed every call is cached, contradicting
  the page's own note further down. Qualify it and add the exclusion to
  Limitations, which is where the other caching caveats live.
- Drop the two em dashes, prohibited by .cubic/docs-style-guide.md:38.
- `page.act` is not a v4 API and `page` was never declared in that snippet,
  so the opt-out example failed when copied. Use `stagehand.act`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

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

All reported issues were addressed across 2 files (changes from recent commits).

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

Re-trigger cubic

Comment thread packages/docs/v4/best-practices/caching.mdx Outdated
docs-style-guide.md:35 mandates active voice, and the limitation bullet used
two passives ("are not cached", "is keyed") with no actor, which also made it
inconsistent with its neighbours ("Stagehand calls the LLM", "Stagehand falls
back"). Name Stagehand as the actor.

Two more of the same class in this PR's prose that review did not flag:
"Caching is enabled by default" is the guide's own example pattern, and
"request made by that instance" is a passive participle. Also corrects
"behaviour", the only British spelling against 13 uses of "behavior" in the
v4 docs.

Leaves the matching passive on line 346 alone: it is untouched legacy, and
the guide says not to block a focused PR on that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread packages/docs/v4/best-practices/caching.mdx Outdated
Comment thread packages/docs/v4/migrations/v3.mdx Outdated

@shrey150 shrey150 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Pre-approving, would address nits first

…ample

The create() example passed `cache: true` in all three tabs, which now reads
as though caching needs enabling. Removed it so the example shows the default
doing the work, with a comment saying so. Also dropped the Go tab's
`CacheEnabled` helper and `Cache` field, which would otherwise be an unused
variable.

Retitled the section: with nothing being set, "Setting on create()" no longer
described it. No page links to that anchor. The two cases the old example
conflated stay covered, `false` under "Disabling per call" and the object form
under "Cache threshold".

Reworded "As in v3, caching is on by default" per review.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

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

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/docs/v4/best-practices/caching.mdx
Flipping the default left prompting-best-practices telling readers that
"Caching is off by default, so the values stay inside the run unless you opt
in." That is now false in the dangerous direction: variables reach the cache
service by default, so anyone following that advice could ship credentials
while believing they stayed local. It now states the transmission plainly and
says to set `cache: false` on calls carrying a credential.

The same conditional framing appeared in the act and Browser Use credential
notes ("with caching enabled", "with caching on"), which understated the risk
now that neither requires an opt-in.

Three more spots told readers to "Enable caching on the constructor", implying
it is required. They now say caching is on by default and the option changes
it.

Left the `cache: true` in the act, observe, and extract examples. Unlike the
one removed from caching.mdx, those blocks are demonstrating the instance-level
knob against the per-call overrides below them, so the explicit value is doing
work. Happy to drop them for consistency if preferred.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both CI failures had one cause. packages/sdk-go/internal/extensionassets/
stagehand-extension.zip is committed and go:embed'd, so changing
cacheService.ts left the embedded copy stale. "Extension drift" checks that
directly, and "Python wheel smoke" fails on the same extensionpack --check at
line 64 of the build recipe.

Rebuilt the extension and reran the packer. Confirmed `just generate`'s other
steps produce no further drift: protocol build, go generate ./..., and the
Python codegen --check are all clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

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

2 issues found across 5 files (changes from recent commits).

Confidence score: 3/5

  • In packages/docs/v4/migrations/browser-use.mdx, the JavaScript credential example omits cache=False while caching is enabled by default, so copied code can send username/password values to the cache—add the equivalent cache-disable option and correct the language example.
  • In packages/docs/v4/basics/observe.mdx, the examples still imply cache: true is required despite caching being enabled by default, which can confuse users about configuration—remove the redundant options and comments or update the examples to match the documented default.
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/docs/v4/basics/observe.mdx">

<violation number="1" location="packages/docs/v4/basics/observe.mdx:502">
P2: This now says caching is enabled by default, but the examples immediately below still present `cache: true` as necessary to enable it. Remove the redundant constructor options and comments, or change the examples to show the instance opt-out (`cache: false`) and explain the explicit override.</violation>
</file>

<file name="packages/docs/v4/migrations/browser-use.mdx">

<violation number="1" location="packages/docs/v4/migrations/browser-use.mdx:341">
P1: This Python-only guide gives JavaScript syntax and the credential example above omits the equivalent `cache=False`. With Browserbase caching enabled by default, copying that example sends the username/password values to the server cache; use Python's `cache=False` on each variable-bearing call.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

```

Stagehand exposes only the variable names to the model and substitutes the real values locally. One exception: with [server-side caching](/v4/best-practices/caching) on, variable values travel to the cache service, so turn `cache` off for calls that carry credentials. See [act](/v4/basics/act#secure-your-automations). For Browser Use's TOTP support (`sensitive_data` keys ending in `bu_2fa_code`), generate the code in your own script and pass it as a variable; v4 has no built-in 2FA step.
Stagehand exposes only the variable names to the model and substitutes the real values locally. One exception: [server-side caching](/v4/best-practices/caching) is on by default and sends variable values to the cache service. Set `cache: false` on calls that carry credentials. See [act](/v4/basics/act#secure-your-automations). For Browser Use's TOTP support (`sensitive_data` keys ending in `bu_2fa_code`), generate the code in your own script and pass it as a variable; v4 has no built-in 2FA step.

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.

P1: This Python-only guide gives JavaScript syntax and the credential example above omits the equivalent cache=False. With Browserbase caching enabled by default, copying that example sends the username/password values to the server cache; use Python's cache=False on each variable-bearing call.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/docs/v4/migrations/browser-use.mdx, line 341:

<comment>This Python-only guide gives JavaScript syntax and the credential example above omits the equivalent `cache=False`. With Browserbase caching enabled by default, copying that example sends the username/password values to the server cache; use Python's `cache=False` on each variable-bearing call.</comment>

<file context>
@@ -338,7 +338,7 @@ Both frameworks keep secrets out of the model's context. Browser Use uses a `sen

-Stagehand exposes only the variable names to the model and substitutes the real values locally. One exception: with server-side caching on, variable values travel to the cache service, so turn cache off for calls that carry credentials. See act. For Browser Use's TOTP support (sensitive_data keys ending in bu_2fa_code), generate the code in your own script and pass it as a variable; v4 has no built-in 2FA step.
+Stagehand exposes only the variable names to the model and substitutes the real values locally. One exception: server-side caching is on by default and sends variable values to the cache service. Set cache: false on calls that carry credentials. See act. For Browser Use's TOTP support (sensitive_data keys ending in bu_2fa_code), generate the code in your own script and pass it as a variable; v4 has no built-in 2FA step.

Custom tools

</file context>


</details>

</Note>

When running on Browserbase, Stagehand can cache `observe()` results server-side. Repeated calls with the same inputs return instantly without consuming LLM tokens. Enable caching on the constructor and override it per call:
When running on Browserbase, Stagehand can cache `observe()` results server-side. Repeated calls with the same inputs return instantly without consuming LLM tokens. Caching is on by default. Set `cache` on the constructor to change it for the instance, or override it per call:

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: This now says caching is enabled by default, but the examples immediately below still present cache: true as necessary to enable it. Remove the redundant constructor options and comments, or change the examples to show the instance opt-out (cache: false) and explain the explicit override.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/docs/v4/basics/observe.mdx, line 502:

<comment>This now says caching is enabled by default, but the examples immediately below still present `cache: true` as necessary to enable it. Remove the redundant constructor options and comments, or change the examples to show the instance opt-out (`cache: false`) and explain the explicit override.</comment>

<file context>
@@ -499,7 +499,7 @@ if emailField != nil && passwordField != nil {
 </Note>
 
-When running on Browserbase, Stagehand can cache `observe()` results server-side. Repeated calls with the same inputs return instantly without consuming LLM tokens. Enable caching on the constructor and override it per call:
+When running on Browserbase, Stagehand can cache `observe()` results server-side. Repeated calls with the same inputs return instantly without consuming LLM tokens. Caching is on by default. Set `cache` on the constructor to change it for the instance, or override it per call:
 
 Locator-scoped observations, including calls with `locator` or `ignoreLocators`, bypass the server-side cache and report `metadata.cache.status` as `DISABLED`.
</file context>

main regenerated packages/sdk-go/internal/extensionassets/stagehand-extension.zip
in #2916 (WebMCP event hooks), which conflicts with this branch's copy. It is a
generated binary, so neither side is correct on its own. Rebuilt the extension
from the merged source and reran the packer instead of taking either version.

Extension suite passes 362 tests on the merged tree, and the Python codegen
check is clean.
@sameelarif
sameelarif merged commit 5a297cd into main Sep 22, 2026
57 checks passed

This branch was successfully deployed

1 active deployment
staging - packages/docs a4ee44f2 Deployed Sep 22, 2026 by mintlify[bot]
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