diff --git a/packages/docs/v4/basics/act.mdx b/packages/docs/v4/basics/act.mdx
index b6d40ad0b..e6a3dc61e 100644
--- a/packages/docs/v4/basics/act.mdx
+++ b/packages/docs/v4/basics/act.mdx
@@ -350,7 +350,7 @@ Target locators only affect instruction-based `act()` calls. When you pass an `A
`cache` requires a Browserbase browser and a Browserbase API key. It has no effect on local browsers.
-When running on Browserbase, Stagehand can cache `act()` 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 `act()` 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:
Instruction-based `act()` calls with a target locator or ignored locators bypass the server-side cache and report `metadata.cache.status` as `DISABLED`.
@@ -671,7 +671,7 @@ The cache lives on Browserbase, keyed on the instruction, page content, and call
### Secure your automations
-Variables are **not shared with LLM providers**. Use them for passwords, API keys, and other sensitive data. Stagehand exposes only the variable names to the model and substitutes the real values locally, so results record the placeholder rather than the secret. One exception: with [server-side caching](/v4/best-practices/caching) enabled, variable values travel to the cache service as part of the request, so turn the `cache` option off for calls that carry credentials.
+Variables are **not shared with LLM providers**. Use them for passwords, API keys, and other sensitive data. Stagehand exposes only the variable names to the model and substitutes the real values locally, so results record the placeholder rather than the secret. One exception: [server-side caching](/v4/best-practices/caching) is on by default, and it sends variable values to the cache service as part of the request. Set `cache: false` on calls that carry credentials.
Load sensitive data from environment variables. Never hardcode API keys, passwords, or other secrets directly in your code.
diff --git a/packages/docs/v4/basics/extract.mdx b/packages/docs/v4/basics/extract.mdx
index cdaa4b3ac..54b3b3932 100644
--- a/packages/docs/v4/basics/extract.mdx
+++ b/packages/docs/v4/basics/extract.mdx
@@ -442,7 +442,7 @@ fmt.Println(result.Data.Name)
`cache` requires a Browserbase browser and a Browserbase API key. It has no effect on local browsers.
-When running on Browserbase, Stagehand can cache `extract()` results server-side. Repeated calls with the same inputs return instantly without consuming LLM tokens. Enable caching on `Stagehand.create()` and override it per call:
+When running on Browserbase, Stagehand can cache `extract()` results server-side. Repeated calls with the same inputs return instantly without consuming LLM tokens. Caching is on by default. Set `cache` on `Stagehand.create()` to change it for the instance, or override it per call:
Locator-scoped extractions, including calls with `locator` or `ignoreLocators`, bypass the server-side cache and report `metadata.cache.status` as `DISABLED`.
diff --git a/packages/docs/v4/basics/observe.mdx b/packages/docs/v4/basics/observe.mdx
index 5e24606fc..7d257f65a 100644
--- a/packages/docs/v4/basics/observe.mdx
+++ b/packages/docs/v4/basics/observe.mdx
@@ -499,7 +499,7 @@ if emailField != nil && passwordField != nil {
`cache` requires a Browserbase browser and a Browserbase API key. It has no effect on local browsers.
-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`.
diff --git a/packages/docs/v4/best-practices/caching.mdx b/packages/docs/v4/best-practices/caching.mdx
index 459e64bf6..e1ce3786c 100644
--- a/packages/docs/v4/best-practices/caching.mdx
+++ b/packages/docs/v4/best-practices/caching.mdx
@@ -9,7 +9,7 @@ Stagehand caches `act()`, `observe()`, and `extract()` results server-side to re
## Browserbase cache
-Browserbase Cache is a managed, server-side caching layer inside the Stagehand API. Turn it on with the `cache` option and Browserbase caches every `act()`, `observe()`, and `extract()` call on its servers. Repeated calls with the same inputs return instantly without consuming any LLM tokens.
+Browserbase Cache is a managed, server-side caching layer inside the Stagehand API. It is on by default: Browserbase caches eligible `act()`, `observe()`, and `extract()` calls on its servers, and repeated calls with the same inputs return instantly without consuming any LLM tokens. Set `cache: false` to turn it off. Some calls are not eligible, see [limitations](#limitations).
Browserbase builds the cache key from the instruction, page content, and the options you pass. It deliberately leaves out model configuration, so switching models does not invalidate your cache. On a cache hit, the server returns the response directly with no LLM inference and no token cost. Check out the [Browserbase blog](https://www.browserbase.com/blog/stagehand-caching) for more details on how it works under the hood.
@@ -17,9 +17,9 @@ Browserbase builds the cache key from the instruction, page content, and the opt
Caching requires a Browserbase browser and the Browserbase API key you passed to `browserbase.launch()`. With a local browser there is no Browserbase session to key against, so the `cache` option has no effect and every call runs inference.
-### Enabling on create()
+### Default behavior
-Pass `cache: true` to enable caching for all requests made by that instance:
+Stagehand enables caching by default, so a Browserbase browser caches every eligible call with no extra configuration. Pass `cache` on `Stagehand.create()` to change that for the instance: `false` opts out, and an object tunes the threshold.
@@ -28,9 +28,9 @@ import { browserbase, Stagehand } from "@browserbasehq/stagehand";
const browser = await browserbase.launch({ apiKey: process.env.BROWSERBASE_API_KEY });
+// Caching is on by default, so no cache option is needed here
const stagehand = await Stagehand.create({
browser,
- cache: true,
});
const page = await browser.context.activePage();
@@ -50,9 +50,9 @@ from stagehand import Stagehand, browserbase
browser = await browserbase.launch(api_key=os.environ["BROWSERBASE_API_KEY"])
+# Caching is on by default, so no cache option is needed here
stagehand = await Stagehand.create(
browser=browser,
- cache=True,
)
page = await browser.context.active_page()
@@ -67,7 +67,6 @@ await stagehand.act("click the login button")
```go
apiKey := os.Getenv("BROWSERBASE_API_KEY")
-cache := stagehand.CacheEnabled(true)
browser, err := stagehand.LaunchBrowserbase(ctx, stagehand.BrowserbaseLaunchOptions{
APIKey: apiKey,
@@ -76,9 +75,9 @@ if err != nil {
return err
}
+// Caching is on by default, so no cache option is needed here
client, err := stagehand.Create(ctx, stagehand.CreateOptions{
Browser: browser,
- Cache: &cache,
})
if err != nil {
return err
@@ -335,6 +334,7 @@ Cache behavior is also visible in the [Browserbase session replay dashboard](htt
- If the page content or structure changes, the action won't get a cache `HIT` and Stagehand calls the LLM. Subsequent actions will attempt to hit the resulting cache entry.
- Caching is best-effort. If the cache is unreachable, Stagehand falls back to normal inference rather than failing your run.
- Stagehand replays a cached `act()` result deterministically with self-healing turned off. If the recorded selector no longer resolves, Stagehand falls back to full inference.
+- Stagehand does not cache calls that set `locator` or `ignoreLocators`. The cache contract uses unscoped requests as keys, so Stagehand skips both reads and writes and reports a `DISABLED` cache status, even when caching is enabled.
### Best practices
diff --git a/packages/docs/v4/best-practices/prompting-best-practices.mdx b/packages/docs/v4/best-practices/prompting-best-practices.mdx
index afeda55a4..9b078f066 100644
--- a/packages/docs/v4/best-practices/prompting-best-practices.mdx
+++ b/packages/docs/v4/best-practices/prompting-best-practices.mdx
@@ -212,7 +212,7 @@ if _, err := client.Act(ctx, stagehand.ActInstruction("choose option 1"), nil);
Variables keep sensitive information out of prompts and logs. Stagehand shows the model only the variable name and its optional description, then substitutes the real value into the resolved action right before it runs, so the secret never reaches the model and never appears in the logged action.
-Variables are part of the payload that builds the [cache key](/v4/best-practices/caching). Caching is off by default, so the values stay inside the run unless you opt in. Once you enable the `cache` option on create or on a call, the variables you pass travel to the cache service with the rest of the request, so turn it off on the calls that carry credentials.
+Variables are part of the payload that builds the [cache key](/v4/best-practices/caching). Stagehand caches by default, so the variables you pass travel to the cache service with the rest of the request. Set `cache: false` on any call that carries a credential.
diff --git a/packages/docs/v4/migrations/browser-use.mdx b/packages/docs/v4/migrations/browser-use.mdx
index 38fb4efe3..833bab134 100644
--- a/packages/docs/v4/migrations/browser-use.mdx
+++ b/packages/docs/v4/migrations/browser-use.mdx
@@ -338,7 +338,7 @@ Both frameworks keep secrets out of the model's context. Browser Use uses a `sen
+ await stagehand.act("click the login button")
```
-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.
### Custom tools
diff --git a/packages/docs/v4/migrations/v3.mdx b/packages/docs/v4/migrations/v3.mdx
index 846921e3e..fc7d29d43 100644
--- a/packages/docs/v4/migrations/v3.mdx
+++ b/packages/docs/v4/migrations/v3.mdx
@@ -380,7 +380,7 @@ Then work through the sections below for anything it missed.
2. Replace page and context access, since it moved.
3. Unwrap results: every primitive now returns `{ data, metadata }`.
4. Replace `agent()` calls with [code mode](#code-mode) or [tool calling](#tool-calling).
-5. Turn on server-side caching once the flow is stable.
+5. Confirm server-side caching is doing what you expect via `metadata.cache.status`.
## Coming from Python or Go
@@ -506,14 +506,16 @@ Model names always carry a provider prefix. Omit `model` entirely on a Browserba
### Caching
-`enableCaching` is gone. v4 caches `act()`, `observe()`, and `extract()` results on Browserbase's servers instead, keyed on the instruction, page content, and options:
+v3's server-side cache option, `serverCache`, is now `cache`, and the local `enableCaching`/`cacheDir` options are gone. v4 caches `act()`, `observe()`, and `extract()` results on Browserbase's servers, keyed on the instruction, page content, and options:
```diff
-- const stagehand = new Stagehand({ enableCaching: true });
-+ const stagehand = await Stagehand.create({ browser, cache: true });
+- const stagehand = new Stagehand({ env: "BROWSERBASE" });
+- await stagehand.act("...", { serverCache: false });
++ const stagehand = await Stagehand.create({ browser });
++ await stagehand.act("...", { cache: false });
```
-Caching needs a Browserbase browser and the API key you passed to `browserbase.launch()`. See [caching](/v4/best-practices/caching).
+Caching stays on by default, and each call can opt out. Caching needs a Browserbase browser and the API key you passed to `browserbase.launch()`. See [caching](/v4/best-practices/caching).
### Logging
@@ -595,7 +597,8 @@ Metrics became a method:
| Agent structured output | `extract()` with a schema |
| Agent streaming, callbacks, abort signal, message continuation | No equivalent. Your loop sits between steps, so log, cancel, or persist there |
| `modelName`, `modelClientOptions` | `model: { modelName, apiKey }` |
-| `enableCaching` | `cache` |
+| `serverCache` | `cache` (still on by default) |
+| `enableCaching`, `cacheDir` | No equivalent; caching is server-side |
| `verbose`, `logger` | `logging: { level, format, onLog }` |
| `await stagehand.metrics` | `await stagehand.metrics()` |
| `stagehand.browserbaseSessionID` | Hold the ID from `sessions.create()` |
diff --git a/packages/extension/services/cacheService.ts b/packages/extension/services/cacheService.ts
index aa8b8b592..84adfe231 100644
--- a/packages/extension/services/cacheService.ts
+++ b/packages/extension/services/cacheService.ts
@@ -43,7 +43,12 @@ interface CachePage {
export interface CacheContext {
sessionId: string;
client: CacheClient;
- /** Instance-level default; each request can override via options.cache. */
+ /**
+ * Instance-level default; each request can override via options.cache.
+ * Defaults to enabled, matching v3, where server-side caching ran unless a
+ * call opted out. The server still gates every lookup on the per-project
+ * LaunchDarkly flag, so this only decides whether we ask.
+ */
defaultCaching: Caching;
}
@@ -62,7 +67,7 @@ export function buildCacheContext(initParams: StagehandInitParams): CacheContext
apiUrlForRegion(initParams.browser?.region, initParams.apiUrl),
initParams.apiKey,
),
- defaultCaching: initParams.cache ?? false,
+ defaultCaching: initParams.cache ?? true,
};
}
@@ -220,7 +225,7 @@ export async function withCache Promise | Result;
execute: () => Promise>;
}): Promise {
- const resolvedCaching = caching ?? context?.defaultCaching ?? false;
+ const resolvedCaching = caching ?? context?.defaultCaching ?? true;
const cachePage = resolvedCaching !== false ? asCachePage(page) : null;
if (bypass || !context || !cachePage) {
return (await execute()).result;
diff --git a/packages/extension/tests/cache-service.test.ts b/packages/extension/tests/cache-service.test.ts
index 789bea562..48307c96a 100644
--- a/packages/extension/tests/cache-service.test.ts
+++ b/packages/extension/tests/cache-service.test.ts
@@ -137,6 +137,45 @@ describe("cache service", () => {
expect(result.metadata).toStrictEqual({ cache: { status: "DISABLED" } });
});
+ // v3 cached every eligible call unless the caller opted out; v4 shipped with
+ // the inverted default, silently disabling caching for anyone who upgraded
+ // without passing `cache`. These pin the default so it can't drift back.
+ it("enables caching by default when init params omit `cache`", () => {
+ const context = cacheService.buildCacheContext({
+ apiKey: "bb-key",
+ browser: { sessionId: "session-id" },
+ } as StagehandInitParams);
+
+ expect(context?.defaultCaching).toBe(true);
+ });
+
+ it("honours an explicit opt-out in init params", () => {
+ const context = cacheService.buildCacheContext({
+ apiKey: "bb-key",
+ browser: { sessionId: "session-id" },
+ cache: false,
+ } as StagehandInitParams);
+
+ expect(context?.defaultCaching).toBe(false);
+ });
+
+ it("looks the cache up when neither the request nor the instance sets `cache`", async () => {
+ const get = vi.fn().mockResolvedValue({ hit: true, value: { answer: 42 }, cacheKey: "key" });
+ const execute = executesTo({ answer: 0 });
+ const { caching: _omitted, ...argsWithoutCaching } = baseArgs();
+
+ const result = await cacheService.withCache({
+ ...argsWithoutCaching,
+ context: { ...cacheContext(get, vi.fn()), defaultCaching: true },
+ onHit: (value): TestResult => ({ data: value, metadata: { cache: { status: "HIT" } } }),
+ execute,
+ });
+
+ expect(get).toHaveBeenCalled();
+ expect(execute).not.toHaveBeenCalled();
+ expect(result.metadata).toStrictEqual({ cache: { status: "HIT" } });
+ });
+
it("omits locator descriptors from act, observe, and extract cache data", () => {
expect(
cacheService.buildActCacheData({
diff --git a/packages/sdk-go/internal/extensionassets/stagehand-extension.zip b/packages/sdk-go/internal/extensionassets/stagehand-extension.zip
index e7a00562d..564e6426b 100644
Binary files a/packages/sdk-go/internal/extensionassets/stagehand-extension.zip and b/packages/sdk-go/internal/extensionassets/stagehand-extension.zip differ