Skip to content

Lazy-load sandbox function invocation GCS blobs. - #32767

Merged
Fraggle merged 4 commits into
mainfrom
sflory/lazy-load-invocation-blob
Sep 21, 2026
Merged

Fraggle merged 4 commits into
mainfrom
sflory/lazy-load-invocation-blob

Conversation

@Fraggle

@Fraggle Fraggle commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Description

SandboxFunctionInvocationResource now fetches invocation rows without downloading their GCS payloads. ensureData coalesces on-demand loads, async getters expose blob-backed fields, and existsForFunction handles execution-side existence checks without a storage read. Updated Google Calendar, Pod Manager, Poke, and LLM serialization callers to await data explicitly.

Tests

Updated sandbox_function_invocation_resource.test.ts and run_sandbox_function_invocation.test.ts for async getters, lazy reload, persistence, migrations, execution, and error paths. No manual testing recorded.

Risk

Low-to-medium risk: callers now cross an async boundary when they access invocation data, and a lazy GCS read can fail at call time. Updated call sites and coverage mitigate missed awaits. Rollback is safe by reverting the front changes.

Deploy Plan

Deploy front. No SQL migrations or infrastructure changes are required.

Fetch the DB row without downloading the blob; load on demand via ensureData/getters, and use existsForFunction for existence checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Sep 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated
playground Ignored Ignored Preview Sep 21, 2026 8:40am UTC
storybook Ignored Ignored Preview Sep 21, 2026 8:40am UTC

Request Review

Fraggle and others added 2 commits September 18, 2026 21:24
Nothing outside tests called it; getters already expose the same blob fields.

Co-authored-by: Cursor <cursoragent@cursor.com>
getInput, getResult, getError, and getBundleSha256 had no callers outside tests. getContext stays; tests read the blob through toPokeJSON.

Co-authored-by: Cursor <cursoragent@cursor.com>
@spolu

spolu commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Small comments as I re-read this file. @davidebbo a fair amount of noisy code attributes to you. listRows, SandboxFunctionInvocationRow, SandboxFunctionInvocationForLLM, toJSONForLLM (unused what is that?)

Please avoid throwing new types around especially if they are not critical. Poke stuff should be clearly distinguished from mission critical stuff. listRows is only used in poke but there is no clear way to understand that without investigating all call sites.

@spolu

spolu commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

r? cc

@github-actions github-actions 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.

cc-verify: violations found!

  • no-catching-own-errors: Blob failures thrown

Findings and notifications outside the PR diff:

Source: front/lib/resources/sandbox_function_invocation_resource.ts:964

Pre-existing violation of no-catching-own-errors in CONTRACTS: loadDataFromGcs() throws a failed withRetry result instead of returning Err. The new lazy getContext() path exposes this exception inside Calendar's create_event after calendar.events.insert() succeeds, reporting a successful creation as a tool failure and risking duplicate events on retry; propagate a Result and handle the context load before the mutation.

@Fraggle

Fraggle commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

@davidebbo a fair amount of noisy code attributes to you. listRows, SandboxFunctionInvocationRow, SandboxFunctionInvocationForLLM, toJSONForLLM (unused what is that?)

I removed a few that weren't used.

@davidebbo

Copy link
Copy Markdown
Contributor

Small comments as I re-read this file. @davidebbo a fair amount of noisy code attributes to you. listRows, SandboxFunctionInvocationRow, SandboxFunctionInvocationForLLM, toJSONForLLM (unused what is that?)

@spolu SandboxFunctionInvocationForLLM and toJSONForLLM date back to the early days (see @flvndvd's #29088). At the time, we had a tool that let the model inspect invocations. We don't have it anymore, but that code ended up staying around and should be cleaned up.

Massive changes happened to go from old model to new, and we moved fast. We cleaned out a ton of obsolete code, but some obviously was missed.

@davidebbo

davidebbo commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Please avoid throwing new types around especially if they are not critical. Poke stuff should be clearly distinguished from mission critical stuff. listRows is only used in poke but there is no clear way to understand that without investigating all call sites.

@spolu Not sure that we have this properly encoded today (to name Poke-only types with Poke in their name). Agreed it's probably a good thing, though likely widely violated today. Could be a global clean up exercise.

Comment thread front/lib/api/poke/sandbox_functions.ts Outdated
? await UserResource.fetchByModelIds([invocation.userId])
: [];

await invocation.ensureData();

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.

Feels like something that should be in toPokeJSON(), so we don't put the onus on all the callers?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, that would make toPokeJSON async but it's okay (i think we have prior art)

@davidebbo davidebbo 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.

LGTM with minor comment.

}: {
sandboxFunction: SandboxFunctionResource;
data?: SandboxFunctionInvocationData;
/** Set when `data` is the real blob (makeNew / after GCS load), not a placeholder. */

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.

Code contract please

version: SANDBOX_FUNCTION_INVOCATION_DATA_VERSION,
input: undefined,
};
this.dataLoaded = dataLoaded;

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.

It's surprising that dataLoaded can be set directly here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll double check but it makes sense at creation, you are supposed to have the data.

// record of a failure the stream classified precisely.
error: callError,
};
this.dataLoaded = true;

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.

I don't understand this one. Since we fall ensureData above dataLoaded must be true already?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct, good catch.

* Load the GCS blob once and cache it on this instance. No-op when already loaded
* (including instances constructed from `makeNew` with in-memory data).
*/
async ensureData(): Promise<void> {

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.

Shouldn't we return data here fully loaded. This will make it more explicit in methods of this resolve to use that return value vs this.data when using the data post loading?

@Fraggle Fraggle Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Something like getData() + _data & _pendingDataLoad & _dataloaded then, no ?

"Invalid sandbox function invocation data"
);
this.data = { version: SANDBOX_FUNCTION_INVOCATION_DATA_VERSION };
this.dataLoaded = true;

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.

It's not really loaded is it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I agree.

}

/**
* DB-only existence check for execution-side pairing. Does not download the

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 comment is not bringing much value is it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok.

user: UserResource | null,
mcpActions: PokeSandboxFunctionMCPAction[]
): PokeSandboxFunctionInvocationDetails {
assert(

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.

Is that the only place where we need it such an assert?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will refactor (see above).

* their (function, invocation) ids come from server-minted inputs — workflow args or verified
* sandbox JWT claims — never from user input.
*
* Existence only: does not download the invocation GCS blob.

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.

What does existence only mean here?

@spolu

spolu commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

cc @dust-tt/data-model-owners

Return the resolved blob from getData, load inside toPokeJSON, replace
dataLoaded with dataResolved (including degraded empty parses), and add
code contracts for construction/resolution and write-behind invariants.

Co-authored-by: Cursor <cursoragent@cursor.com>
async succeed(result: unknown): Promise<boolean> {
const current = await this.getData();

const claimed = await this.casStatus({

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.

React Doctor · react-doctor/server-sequential-independent-await (warning)

This await doesn't use the previous result, so your users wait twice as long for nothing.

Fix → These two awaits don't depend on each other. Wrap them in Promise.all([...]) so they run at the same time.

Docs

@Fraggle
Fraggle merged commit ff8cbcb into main Sep 21, 2026
53 checks passed
@Fraggle
Fraggle deleted the sflory/lazy-load-invocation-blob branch September 21, 2026 12:53
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.

3 participants