Lazy-load sandbox function invocation GCS blobs. - #32767
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
This change is part of the following stack:
Change managed by git-spice. |
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>
|
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. |
|
r? cc |
There was a problem hiding this comment.
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.
I removed a few that weren't used. |
@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. |
@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. |
| ? await UserResource.fetchByModelIds([invocation.userId]) | ||
| : []; | ||
|
|
||
| await invocation.ensureData(); |
There was a problem hiding this comment.
Feels like something that should be in toPokeJSON(), so we don't put the onus on all the callers?
There was a problem hiding this comment.
Yep, that would make toPokeJSON async but it's okay (i think we have prior art)
davidebbo
left a comment
There was a problem hiding this comment.
LGTM with minor comment.
| }: { | ||
| sandboxFunction: SandboxFunctionResource; | ||
| data?: SandboxFunctionInvocationData; | ||
| /** Set when `data` is the real blob (makeNew / after GCS load), not a placeholder. */ |
| version: SANDBOX_FUNCTION_INVOCATION_DATA_VERSION, | ||
| input: undefined, | ||
| }; | ||
| this.dataLoaded = dataLoaded; |
There was a problem hiding this comment.
It's surprising that dataLoaded can be set directly here?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
I don't understand this one. Since we fall ensureData above dataLoaded must be true already?
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Something like getData() + _data & _pendingDataLoad & _dataloaded then, no ?
| "Invalid sandbox function invocation data" | ||
| ); | ||
| this.data = { version: SANDBOX_FUNCTION_INVOCATION_DATA_VERSION }; | ||
| this.dataLoaded = true; |
There was a problem hiding this comment.
It's not really loaded is it?
| } | ||
|
|
||
| /** | ||
| * DB-only existence check for execution-side pairing. Does not download the |
There was a problem hiding this comment.
This comment is not bringing much value is it?
| user: UserResource | null, | ||
| mcpActions: PokeSandboxFunctionMCPAction[] | ||
| ): PokeSandboxFunctionInvocationDetails { | ||
| assert( |
There was a problem hiding this comment.
Is that the only place where we need it such an assert?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
What does existence only mean here?
|
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({ |
There was a problem hiding this comment.
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.
Description
SandboxFunctionInvocationResourcenow fetches invocation rows without downloading their GCS payloads.ensureDatacoalesces on-demand loads, async getters expose blob-backed fields, andexistsForFunctionhandles 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.tsandrun_sandbox_function_invocation.test.tsfor 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.