Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/docs/v4/basics/act.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
</Note>

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

Expand Down Expand Up @@ -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.

<Note>
Load sensitive data from environment variables. Never hardcode API keys, passwords, or other secrets directly in your code.
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/v4/basics/extract.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
</Note>

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

Expand Down
2 changes: 1 addition & 1 deletion packages/docs/v4/basics/observe.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
</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>


Locator-scoped observations, including calls with `locator` or `ignoreLocators`, bypass the server-side cache and report `metadata.cache.status` as `DISABLED`.

Expand Down
14 changes: 7 additions & 7 deletions packages/docs/v4/best-practices/caching.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ 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.

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

### 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.
Comment thread
sameelarif marked this conversation as resolved.

<Tabs>
<Tab title="TypeScript">
Expand All @@ -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();
Expand All @@ -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()
Expand All @@ -67,7 +67,6 @@ await stagehand.act("click the login button")
<Tab title="Go">
```go
apiKey := os.Getenv("BROWSERBASE_API_KEY")
cache := stagehand.CacheEnabled(true)

browser, err := stagehand.LaunchBrowserbase(ctx, stagehand.BrowserbaseLaunchOptions{
APIKey: apiKey,
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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

<Tabs>
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/v4/migrations/browser-use.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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>


### Custom tools

Expand Down
15 changes: 9 additions & 6 deletions packages/docs/v4/migrations/v3.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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()` |
Expand Down
11 changes: 8 additions & 3 deletions packages/extension/services/cacheService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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,
};
}

Expand Down Expand Up @@ -220,7 +225,7 @@ export async function withCache<Result extends { metadata: { cache: CacheMetadat
onHit: (value: unknown) => Promise<Result> | Result;
execute: () => Promise<CacheExecuteOutcome<Result>>;
}): Promise<Result> {
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;
Expand Down
39 changes: 39 additions & 0 deletions packages/extension/tests/cache-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Binary file modified packages/sdk-go/internal/extensionassets/stagehand-extension.zip
Binary file not shown.
Loading