Skip to content

[fix]: add support for local caching of agent when using api (2)#1521

Merged
seanmcguire12 merged 9 commits intomainfrom
add-local-agent-cache-when-using-api
Jan 21, 2026
Merged

[fix]: add support for local caching of agent when using api (2)#1521
seanmcguire12 merged 9 commits intomainfrom
add-local-agent-cache-when-using-api

Conversation

@seanmcguire12
Copy link
Copy Markdown
Member

@seanmcguire12 seanmcguire12 commented Jan 10, 2026

why

what changed

test plan


Summary by cubic

Adds local agent caching when executing via the API. When a cacheContext is provided, agent cache entries returned by the server are saved to local cache for replay and faster repeats.

  • New Features
    • Extended AgentExecute (types + OpenAPI) with shouldCache and optional cacheEntry in the response.
    • API client captures cacheEntry from SSE and exposes consumeLatestAgentCacheEntry().
    • V3 persists remote cache entries to local CacheStorage when using API mode.
    • Added in-memory CacheStorage and a server agent cache handle to buffer and transfer cache entries.

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

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jan 10, 2026

🦋 Changeset detected

Latest commit: cda468f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@browserbasehq/stagehand Patch
@browserbasehq/stagehand-evals Patch
@browserbasehq/stagehand-server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 10, 2026

✱ Stainless preview builds

This PR will update the stagehand SDKs with the following commit message.

feat: [feat]: add support for local caching of agent when using api (2)
⚠️ stagehand-csharp studio · code

There was a regression in your SDK.
generate ⚠️lint ❗test ✅

stagehand-python studio · conflict

There was a conflict between your custom code and your generated changes.
You don't need to resolve this conflict right now, but you will need to resolve it for your changes to be released to your users. Read more about why this happened here.

stagehand-openapi studio · code

Your SDK built successfully.
generate ✅lint ⏳test ⏳

⚠️ stagehand-typescript studio · code

There was a regression in your SDK.
generate ✅build ✅lint ✅test ❗

npm install https://pkg.stainless.com/s/stagehand-typescript/a8bcece3d1a6e6c1aefda99a01a767225722bd3b/dist.tar.gz
stagehand-go studio · code

Your SDK built successfully.
generate ✅lint ✅test ✅

go get github.com/stainless-sdks/stagehand-go@636a1ec3c7793f6a634fba0dcc9f16274b33c525
stagehand-kotlin studio

Code was not generated because there was a fatal error.

stagehand-java studio · conflict

There was a conflict between your custom code and your generated changes.
You don't need to resolve this conflict right now, but you will need to resolve it for your changes to be released to your users. Read more about why this happened here.

stagehand-php studio · code

Your SDK built successfully.
generate ✅lint ✅test ✅

stagehand-ruby studio · conflict

There was a conflict between your custom code and your generated changes.
You don't need to resolve this conflict right now, but you will need to resolve it for your changes to be released to your users. Read more about why this happened here.

⚠️ stagehand-cli studio · code

There was a regression in your SDK.
generate ✅lint ❗test ❗

⏳ These are partial results; builds are still running.


This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
Last updated: 2026-01-21 01:10:47 UTC

Comment thread packages/server/openapi.v3.yaml Outdated
id: $response.body#/data/sessionId
description: End the session and release resources
schemas:
AgentCacheContext:
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.

This should just be a boolean - something like useCaching. AgentCacheContext and AgentCacheContextOutput makes us stray even further from the REST/resource model. The main issue is that it leaks internals & makes future changes harder because clients now depend on cache mechanics.

Long-term we want an interface that’s basically just GET/POST resources — like GET /products/{productId} returns a product, not “a product + instructions for how the warehouse finds it”.

Rule of thumb: clients should describe what they want, not how we optimize it internally.

@seanmcguire12 seanmcguire12 force-pushed the add-local-agent-cache-when-using-api branch from 646ca3d to 89f3a74 Compare January 13, 2026 22:48
Comment thread packages/server/openapi.v3.yaml
Comment thread packages/server/openapi.v3.yaml
@seanmcguire12 seanmcguire12 changed the title [feat]: add support for local caching of agent when using api (2) [fix]: add support for local caching of agent when using api (2) Jan 14, 2026
@seanmcguire12 seanmcguire12 marked this pull request as ready for review January 14, 2026 23:52
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jan 14, 2026

Greptile Summary

This PR implements local agent caching when executing through the API. When a cacheContext is provided, the server captures agent cache entries using an in-memory buffer and returns them to the client via SSE, which then persists them to local storage for future replay.

Key Changes:

  • Extended AgentExecute API with shouldCache request flag and optional cacheEntry in response
  • Added StagehandAPIClient methods to capture and consume cache entries from SSE events
  • Implemented in-memory CacheStorage using Map for temporary server-side buffering
  • Created ServerAgentCacheHandle to temporarily swap V3's agent cache with a memory-backed version
  • Modified V3 agent execution to conditionally record locally or retrieve remote cache based on API mode
  • Updated OpenAPI spec to reflect new cache-related fields

Architecture:
The implementation uses a temporary in-memory cache on the server side that buffers agent execution steps. After completion, the cache entry is serialized and sent to the client through the SSE finished event. The client then writes this entry to its local cache directory for future replay.

Previous Review Comments:
The PR addresses style feedback about redundant null coalescing and discusses the temporary nature of private field access in serverAgentCache.ts (noted with TODO comment for future refactoring).

Confidence Score: 4/5

  • This PR is safe to merge with minor considerations around the temporary reflective access pattern
  • The implementation is well-structured with clear separation of concerns. The main risk is the use of reflective private field access in serverAgentCache.ts, which is acknowledged with a TODO comment. The caching logic is sound, with proper buffering and transfer mechanisms. Type safety is maintained throughout, and the changes are additive without breaking existing functionality.
  • packages/core/lib/v3/cache/serverAgentCache.ts requires attention due to reflective private field access pattern

Important Files Changed

Filename Overview
packages/core/lib/v3/api.ts Added cache entry capture and consumption methods to support agent caching in API mode
packages/core/lib/v3/cache/serverAgentCache.ts New file implementing in-memory agent cache handle for server-side cache capture using private field access
packages/core/lib/v3/cache/CacheStorage.ts Added in-memory storage mode with Map-based implementation for temporary cache buffering
packages/core/lib/v3/cache/AgentCache.ts Added buffer and transfer methods to support cache entry serialization and import from remote execution
packages/core/lib/v3/v3.ts Modified agent execute logic to conditionally record locally vs retrieve remote cache entries based on API mode

Sequence Diagram

sequenceDiagram
    participant Client as V3 Client
    participant API as StagehandAPIClient
    participant Server as API Server
    participant Handle as ServerAgentCacheHandle
    participant MemCache as In-Memory AgentCache
    participant LocalCache as Local CacheStorage

    Note over Client,LocalCache: Agent Execute with API Mode + Caching

    Client->>Client: Check if cacheContext exists
    alt Has cacheContext
        Client->>LocalCache: Try to read cache entry
        LocalCache-->>Client: Return cached result or null
    end

    alt Cache miss or no cache
        Client->>API: agentExecute(config, options, shouldCache=true)
        API->>Server: POST /sessions/{id}/agentExecute
        Server->>Handle: createInMemoryAgentCacheHandle()
        Handle->>MemCache: Create memory-backed AgentCache
        Handle->>Server: Replace V3.agentCache with MemCache
        Server->>Server: Execute agent task
        Server->>MemCache: Record agent steps (in memory)
        MemCache->>MemCache: Store to Map (bufferLatestEntry=true)
        Server->>Handle: handle.complete()
        Handle->>MemCache: consumeBufferedEntry()
        MemCache-->>Handle: Return AgentCacheTransferPayload
        Handle->>Server: Restore original cache
        Handle-->>Server: Return payload
        Server->>API: SSE: finished event with cacheEntry
        API->>API: Store finishedData.cacheEntry
        API-->>Client: Return AgentResult
        Client->>API: consumeLatestAgentCacheEntry()
        API-->>Client: Return AgentCacheTransferPayload
        Client->>LocalCache: storeTransferredEntry(payload)
        LocalCache->>LocalCache: Write to local cache file
    end

    Note over Client,LocalCache: Subsequent executions use local cache
Loading

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

12 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +374 to +379
const finishedData =
this.consumeFinishedEventData<Api.AgentExecuteResult>() ?? null;
this.latestAgentCacheEntry =
finishedData?.cacheEntry !== undefined
? (finishedData.cacheEntry as AgentCacheTransferPayload)
: null;
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.

style: The null coalescing on line 379 (?? null) is redundant since entry is already null if latestAgentCacheEntry is null

Suggested change
const finishedData =
this.consumeFinishedEventData<Api.AgentExecuteResult>() ?? null;
this.latestAgentCacheEntry =
finishedData?.cacheEntry !== undefined
? (finishedData.cacheEntry as AgentCacheTransferPayload)
: null;
const finishedData =
this.consumeFinishedEventData<Api.AgentExecuteResult>() ?? null;
this.latestAgentCacheEntry =
finishedData?.cacheEntry !== undefined
? (finishedData.cacheEntry as AgentCacheTransferPayload)
: null;
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/lib/v3/api.ts
Line: 374:379

Comment:
**style:** The null coalescing on line 379 (`?? null`) is redundant since `entry` is already `null` if `latestAgentCacheEntry` is `null`

```suggestion
    const finishedData =
      this.consumeFinishedEventData<Api.AgentExecuteResult>() ?? null;
    this.latestAgentCacheEntry =
      finishedData?.cacheEntry !== undefined
        ? (finishedData.cacheEntry as AgentCacheTransferPayload)
        : null;
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread packages/core/lib/v3/api.ts
Comment thread packages/core/lib/v3/cache/serverAgentCache.ts
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 12 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/server/openapi.v3.yaml">

<violation number="1" location="packages/server/openapi.v3.yaml:87">
P3: The `entry` property is missing the `type` field. Other flexible-type fields in this spec use `type: unknown` (e.g., `ExtractResult.result`, `NavigateResult.result`). For consistency with codebase conventions, consider adding `type: unknown` here.</violation>
</file>

<file name="packages/core/lib/v3/cache/CacheStorage.ts">

<violation number="1" location="packages/core/lib/v3/cache/CacheStorage.ts:84">
P2: Memory store holds direct references, unlike file storage which deep-copies via JSON serialization. Mutations to the original data or read results will unexpectedly modify cached values. Consider cloning data to match file storage semantics:
```typescript
this.memoryStore.set(fileName, JSON.parse(JSON.stringify(data)));
```</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread packages/core/lib/v3/cache/CacheStorage.ts Outdated
Comment thread packages/server/openapi.v3.yaml
@seanmcguire12
Copy link
Copy Markdown
Member Author

@greptileai

@seanmcguire12 seanmcguire12 merged commit 84c05ca into main Jan 21, 2026
31 of 34 checks passed
seanmcguire12 pushed a commit that referenced this pull request Jan 22, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @browserbasehq/stagehand@3.0.8

### Patch Changes

- [#1514](#1514)
[`40ce5cc`](40ce5cc)
Thanks [@tkattkat](https://github.com/tkattkat)! - Rename the close tool
in agent to "done"

- [#1574](#1574)
[`5506f41`](5506f41)
Thanks [@tkattkat](https://github.com/tkattkat)! - fix(server): pass
cdpUrl to localBrowserLaunchOptions when launchOptions absent

- [#1521](#1521)
[`84c05ca`](84c05ca)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix: get
agent cache working in API mode

- [#1486](#1486)
[`692ffa0`](692ffa0)
Thanks [@tkattkat](https://github.com/tkattkat)! - improve logging in
agent

- [#1551](#1551)
[`1ef8901`](1ef8901)
Thanks [@miguelg719](https://github.com/miguelg719)! - move extract
handler response log to after URL injection

- [#1495](#1495)
[`72ac775`](72ac775)
Thanks [@tkattkat](https://github.com/tkattkat)! - export tool function
& type to simplify defining custom tools

- [#1481](#1481)
[`3d5af07`](3d5af07)
Thanks [@tkattkat](https://github.com/tkattkat)! - add waitForTimeout to
page

- [#1423](#1423)
[`40e1d80`](40e1d80)
Thanks [@miguelg719](https://github.com/miguelg719)! - Improve benchmark
handling and add metadata

- [#1588](#1588)
[`56c0d24`](56c0d24)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add
SnapshotOptions to page.snapshot()

- [#1483](#1483)
[`16d72fb`](16d72fb)
Thanks [@tkattkat](https://github.com/tkattkat)! - Optimize screenshot
handling in agent hybrid mode

- [#1498](#1498)
[`088c4cc`](088c4cc)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix:
replaying cached actions (for agent & act) now uses the originally
defined model, (instead of default model) when action fails and
rerunning inference is needed

- [#1575](#1575)
[`4276f4a`](4276f4a)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - expose port
param in localBrowserLaunchOptions

- [#1544](#1544)
[`6005786`](6005786)
Thanks [@tkattkat](https://github.com/tkattkat)! - Recommend hybrid mode
over DOM mode in agent, which is now considered legacy

- [#1505](#1505)
[`6fbf5fc`](6fbf5fc)
Thanks [@tkattkat](https://github.com/tkattkat)! - Add structured output
to agent result + ensure close tool is always called

- [#1511](#1511)
[`704cf18`](704cf18)
Thanks [@shrey150](https://github.com/shrey150)! - Fix ControlOrMeta
keypress event

- [#1480](#1480)
[`091296e`](091296e)
Thanks [@tkattkat](https://github.com/tkattkat)! - Update agent to only
calculate xpath when caching is enabled

- [#1509](#1509)
[`e56c6eb`](e56c6eb)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add support
for page.waitForSelector()

- [#1478](#1478)
[`2cb78d0`](2cb78d0)
Thanks [@tkattkat](https://github.com/tkattkat)! - update agent message
handling

- [#1518](#1518)
[`5dad639`](5dad639)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add
page.snapshot() for capturing a stringified DOM snapshot of the page,
including an xpath map & url map

- [#1576](#1576)
[`b7c2571`](b7c2571)
Thanks [@tkattkat](https://github.com/tkattkat)! - utilize
waitForSelector when running agent cache

- [#1560](#1560)
[`4c69117`](4c69117)
Thanks [@tkattkat](https://github.com/tkattkat)! - Update coordinate
handling in cua and hybrid

## @browserbasehq/stagehand-server@3.5.0

### Minor Changes

- [#1578](#1578)
[`a5074bd`](a5074bd)
Thanks [@monadoid](https://github.com/monadoid)! - /end endpoint no
longer takes an empty object - instead, no request body is required.

### Patch Changes

- Updated dependencies
\[[`40ce5cc`](40ce5cc),
[`5506f41`](5506f41),
[`84c05ca`](84c05ca),
[`692ffa0`](692ffa0),
[`1ef8901`](1ef8901),
[`72ac775`](72ac775),
[`3d5af07`](3d5af07),
[`40e1d80`](40e1d80),
[`56c0d24`](56c0d24),
[`16d72fb`](16d72fb),
[`088c4cc`](088c4cc),
[`4276f4a`](4276f4a),
[`6005786`](6005786),
[`6fbf5fc`](6fbf5fc),
[`704cf18`](704cf18),
[`091296e`](091296e),
[`e56c6eb`](e56c6eb),
[`2cb78d0`](2cb78d0),
[`5dad639`](5dad639),
[`b7c2571`](b7c2571),
[`4c69117`](4c69117)]:
    -   @browserbasehq/stagehand@3.0.8

## @browserbasehq/stagehand-evals@1.1.7

### Patch Changes

- Updated dependencies
\[[`40ce5cc`](40ce5cc),
[`5506f41`](5506f41),
[`84c05ca`](84c05ca),
[`692ffa0`](692ffa0),
[`1ef8901`](1ef8901),
[`72ac775`](72ac775),
[`3d5af07`](3d5af07),
[`40e1d80`](40e1d80),
[`56c0d24`](56c0d24),
[`16d72fb`](16d72fb),
[`088c4cc`](088c4cc),
[`4276f4a`](4276f4a),
[`6005786`](6005786),
[`6fbf5fc`](6fbf5fc),
[`704cf18`](704cf18),
[`091296e`](091296e),
[`e56c6eb`](e56c6eb),
[`2cb78d0`](2cb78d0),
[`5dad639`](5dad639),
[`b7c2571`](b7c2571),
[`4c69117`](4c69117)]:
    -   @browserbasehq/stagehand@3.0.8

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

4 participants