Skip to content

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

Open
sameelarif wants to merge 2 commits into
mainfrom
sameelarif/ap-2925-ensure-caching-is-enabled-by-default-in-v4
Open

sameelarif wants to merge 2 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

Enables server-side caching by default in v4 so upgrading from v3 no longer silently loses caching (v3 cached unless a call opted out; v4 shipped opt-in).

  • Flips the client-side default to true so caching runs unless the instance or call opts out.
  • Adds tests covering the default, explicit opt-out, and a lookup with no cache set.
  • Corrects the v3 migration guide: serverCache maps to cache (still on by default), and enableCaching/cacheDir no longer exist.
  • Updates caching docs to describe on-by-default behavior, notes that locator-scoped calls bypass the cache and report DISABLED, and fixes the opt-out example to use stagehand.act.

Written for commit b948484. 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 17, 2026, 6:49 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: b948484

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.

1 issue found across 2 files (changes from recent commits).

Confidence score: 5/5

  • packages/docs/v4/best-practices/caching.mdx uses passive voice in the added limitation, which may make the caching behavior less clear to readers; name the actor explicitly, such as stating that Stagehand does not cache the relevant content.
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/best-practices/caching.mdx">

<violation number="1" location="packages/docs/v4/best-practices/caching.mdx:338">
P2: Custom agent: **Stagehand docs prose guide**

This added limitation uses passive voice in “are not cached” and “is keyed” without naming the actor. Rewrite it with explicit actors, such as “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.”</violation>
</file>

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

Re-trigger cubic

- 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.
- Calls that set `locator` or `ignoreLocators` are not cached. The cache contract is keyed on unscoped requests, so Stagehand skips both reads and writes and reports a `DISABLED` cache status, even when caching is enabled.

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: Custom agent: Stagehand docs prose guide

This added limitation uses passive voice in “are not cached” and “is keyed” without naming the actor. Rewrite it with explicit actors, such as “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.”

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/docs/v4/best-practices/caching.mdx, line 338:

<comment>This added limitation uses passive voice in “are not cached” and “is keyed” without naming the actor. Rewrite it with explicit actors, such as “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.”</comment>

<file context>
@@ -335,6 +335,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.
+- Calls that set `locator` or `ignoreLocators` are not cached. The cache contract is keyed on unscoped requests, so Stagehand skips both reads and writes and reports a `DISABLED` cache status, even when caching is enabled.
 
 ### Best practices
</file context>
Suggested change
- Calls that set `locator` or `ignoreLocators` are not cached. The cache contract is keyed on unscoped requests, so Stagehand skips both reads and writes and reports a `DISABLED` cache status, even when caching is enabled.
- 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.

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.

1 participant