[AP-2925] Enable server-side caching by default in v4 - #2964
sameelarif merged 7 commits into
Conversation
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>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
|
There was a problem hiding this comment.
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
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- 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>
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
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>
shrey150
left a comment
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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
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>
There was a problem hiding this comment.
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 omitscache=Falsewhile 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 implycache: trueis 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. |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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.
v4 shipped with server-side caching opt-in, where v3 had it on by default. Anyone who upgraded without passing
cachelost 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.mdxdescribed opt-in, which this makes wrong.The v3 migration guide also mapped
enableCaching→cache.enableCachingwas a v2-era local option that v3 had already replaced withcacheDir; the server-side option v3 users actually had wasserverCache, 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.
serverCachetocacheand remove outdatedenableCaching/cacheDirmappings; updates caching docs for locator-scoped bypasses,DISABLEDstatus, thestagehand.actopt-out example, andcreate()examples that show the default doing the work.cache: falseon calls carrying credentials.Written for commit a4ee44f. Summary will update on new commits.