Home / Articles

Offloading Apps Script Workloads to a Gemini Managed Agent Linux Sandbox

Google Apps Script driving a Gemini managed agent Linux sandbox: stream files to Drive with ggsrun, dodge the 50 MB limit, run Playwright, FFmpeg and esbuild.

Publisher: REACTAPP.TOP13 min read2934 words

This article is published in English.

Sources: Taking Advantage of Gemini Managed Agents with Google Apps Script

Why pair Apps Script with a Linux sandbox

Google Apps Script is a handy way to automate Google Workspace, yet it's a lightweight, restricted serverless runtime with no OS-level access. That rules out a long list of heavier jobs. Low-level network and protocol control is limited. There's no headless browser for rendering dynamic pages, no way to run native binaries for media transcoding or signal processing, no modern compilers or build toolchains, and strict quotas on run time and payload size.

Gemini Managed Agents offer a way around this. Part of the Gemini v1beta Interactions and Environments API, they let developers provision remote Linux sandboxes that can execute code on their own, run shell commands and manage packages. Pairing them with the Google Workspace CLI has already drawn community interest.

The architecture described here connects the two. Apps Script orchestrates a persistent Linux container, with 4 vCPUs and 16 GB of RAM plus Python 3.12, Node.js 22 and the usual package managers (apt, npm, pip), and the container streams generated files straight to Google Drive using the ggsrun command-line tool. Because files never travel back through the API response, the design avoids payload limits and token overhead and sustains high-throughput cloud automation.

The problem with returning binaries as Base64

Suppose the sandbox produces something large, like high-resolution screenshots, audio waveforms or a bundled JavaScript file, and it has to land in Drive. The obvious route is returning the raw bytes as a Base64 string in the Gemini API response and decoding them in Apps Script. That route hits several walls at once.

  • UrlFetchApp in Apps Script enforces a 50 MB cap on response payloads.
  • The code execution environment in the Interactions API limits the stdout buffer, so a multi-megabyte Base64 string can be cut off mid-stream.
  • Managed Agents have a quota of 200,000 tokens per minute. Base64 inflates binary size by about a third, and in multi-turn sessions the earlier Base64 strings pile up in conversation history until input quotas run dry and 429 Quota Exceeded errors appear right away.
  • Decoding big Base64 strings and building Drive blobs in Apps Script eats execution time and script memory.

The next diagram contrasts that route with direct streaming.

The better approach runs the Go-based ggsrun tool inside the sandbox itself, with an OAuth access token injected at run time from ScriptApp.getOAuthToken(). The sandbox then pushes binary artifacts to Drive over Google Cloud's internal backbone at speeds above 2 MB/s, bypassing Apps Script memory, response-size limits and token-quota exhaustion in one move.

Streaming works in both directions

Direct cloud-to-cloud transfer helps for downloads as well. When large outside datasets, such as high-resolution images, audio, video, multi-gigabyte CSV or JSON files or machine-learning models, must reach the sandbox, embedding them in a prompt as Base64 or serialized text burns through the input token quota almost at once and ends in a 429. With ggsrun, the sandbox pulls the file from Drive itself, and the prompt needs only a short instruction along the lines of "download the target dataset from Drive and analyze it." Input token use drops to practically nothing, which prevents rate-limit exhaustion.

Sharing one persistent sandbox cuts costs further. A single provisioning step produces an environmentId that Apps Script projects, local Node.js workstations, Python scripts and CI/CD pipelines can all use. Master datasets, corpora, libraries or pre-trained models staged under /workspace/ are then there for every client, so nobody re-uploads data or repeats setup on each turn. That trims latency, network bandwidth and cumulative API overhead, and tasks can reuse shared working files and pre-installed packages.

The following diagram shows the complete architecture, with Apps Script and local Node.js workstations orchestrating the same persistent sandbox through a shared environmentId, using two-way streaming and shared datasets for fast content generation.

It breaks down into four parts:

  • Several clients drive one container. Cloud-based Apps Script uses a synchronous trigger and a dynamic OAuth token, while local Node.js workstations use real-time SSE streaming and gcloud authentication.
  • The sandbox keeps shared datasets and build tools such as Playwright, FFmpeg and esbuild, so content can be generated without re-uploading data.
  • ggsrun download streams large datasets from Drive into the sandbox, which spares prompt data embedding and keeps the 200k TPM quota safe.
  • ggsrun upload pushes finished binaries to Drive at 2 MB/s or better, ahead of the 50 MB payload limit and stdout truncation.

The full source (the Apps Script classes, Node.js stream clients, test suites and raw execution logs) lives in the project's public GitHub repository.

Setting it up

First, generate an API key in Google AI Studio. It authenticates requests to the Gemini v1beta Interactions and Environments APIs. Then create an Apps Script project in one of two ways: a standalone project from script.google.com via New project, or a container-bound one by opening a Sheet, Doc or Form and choosing Extensions, then Apps Script.

Copy two files from the repository into the editor:

  • ManagedAgentSandboxClient.js is the core client class. It manages the sandbox lifecycle and dynamic environment variables, keeps session data in PropertiesService and applies sensible backoff when a 429 arrives.
  • tests.js is the master test suite. It covers sandbox provisioning, tool checks, media processing, web scraping and performance benchmarks.

Next, open Project Settings, then Script Properties, and add a property named GEMINI_API_KEY holding the key. Finally, make sure the manifest (appsscript.json) carries the right OAuth scopes. The scope https://www.googleapis.com/auth/script.external_request is needed for UrlFetchApp calls, and https://www.googleapis.com/auth/drive covers creating destination folders and uploading artifacts. Code that uses existing folders without DriveApp.createFolder() can get by with https://www.googleapis.com/auth/drive.file. Execution logs for every test are in the repository's gas-src/execution-logs.md.

Provisioning the shared sandbox

Running provisionSharedSandbox() starts a new remote Linux container, installs the CLI tools and dependencies, configures the destination Drive paths and saves the resulting environmentId in PropertiesService.

The infographic shows four steps. Drive first gets a destination folder named ManagedAgent_Artifacts_YYYYMMDD. Then the 4 vCPU, 16 GB container bootstraps ggsrun, ffmpeg, sox, jq, typescript, esbuild and Playwright with Chromium, via an initialization prompt. Next the sandbox verifies the installed binaries and reports READY. Last, the unique environmentId is stored under SHARED_SANDBOX_SESSION, so later tests and other clients can reuse it. Calling testListSandboxes() afterward queries the Environments API to confirm the sandbox is active and returns its metadata.

Test 1: custom headers that Apps Script overwrites

The first test shows something UrlFetchApp can't do. Apps Script automatically replaces a custom User-Agent header with Google's proxy identity string, while the sandbox keeps any header you set, since it sends requests through raw POSIX sockets and native curl.

The diagram follows a request carrying User-Agent: sample user agent to httpbin.org/anything. On the Apps Script side, platform proxy policy swaps the header. On the sandbox side, curl keeps the exact string. An inline Python script then compares the reflected JSON payloads and prints a verification matrix. Concretely, Apps Script sends a GET with the custom header, the sandbox sends the same curl request, and the comparison confirms that Apps Script substituted Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; ...) while the sandbox preserved sample user agent exactly.

Test 2: authenticating to Drive from inside the sandbox

This test checks Drive authentication and direct access through ggsrun by injecting a fresh OAuth token from ScriptApp.getOAuthToken() into that turn's execution.

In step one, Apps Script extracts the token and injects it as the GGSRUN_AT environment variable, which removes the risk of a one-hour token expiring. In step two, the sandbox generates a verification file, 00_ggsrun_verification.txt, under /workspace/test2/ and uploads it with ggsrun upload in non-blocking overwrite mode (--nc --cm OverwriteIfNewer -j). In step three, ggsrun searchfiles queries the destination folder and returns structured metadata that confirms all nine artifacts. The whole cycle of creating, uploading and verifying took 12.1 seconds, and no sensitive token persisted across sessions.

Test 3: headless scraping with Playwright

Next, an automated headless Chromium session in the sandbox scrapes dynamic, JavaScript-rendered content and captures screenshots at several viewport sizes.

A Node.js Playwright script opens a JavaScript-rendered quotes site (quotes.toscrape.com/js/), captures full-page screenshots at desktop size (1280x800) and in iPhone emulation (375x812), then clicks the pagination control, grabs a desktop screenshot of page two and extracts the quotes into 02_Page2_Quotes.json. The sizes were about 92.5 KB for the desktop shot, 51.6 KB for mobile and 171.9 KB for page two, plus 4.1 KB of JSON, roughly 320 KB across four artifacts. A single ggsrun upload command sent all three PNGs and the JSON dataset to Drive without any Base64 conversion, and the entire run took 20.4 seconds. That's client-side rendering Apps Script can't parse natively.

Test 4: audio synthesis with FFmpeg and SoX

The fourth test runs native digital signal processing in the sandbox, building a multi-tone chord with FFmpeg and SoX.

Three sine-wave generators at 440 Hz (A4), 554.37 Hz (C#5) and 659.25 Hz (E5) are mixed through the ffmpeg amix filter into a three-second major-chord MP3 of 73.4 KB. ffprobe then extracts stream metadata into 03_Audio_Analysis.json, which came to 1.8 KB. ggsrun upload sent 03_Chord_Major.mp3 and the JSON file straight to Drive. Synthesis, metadata extraction and upload took 9.1 seconds.

Test 5: TypeScript parsing and esbuild bundling

This test shows modern JavaScript and TypeScript build tooling working inside the sandbox.

Two toolchains work on a TypeScript module, matrix.ts, that defines generic classes and interfaces and sits in /workspace/test5/. In the first branch, a Node.js script uses the official TypeScript Compiler API to parse the abstract syntax tree and export interface and method schemas to 04_TypeScript_AST.json, only 152 bytes. In the second, esbuild bundles and minifies the module into a standalone IIFE file, 04_Matrix_Bundle.iife.js at 1.2 KB, with a build time of just 13 milliseconds. ggsrun upload then sent both outputs to Drive, and the full run took 10.0 seconds.

Test 6: direct upload against Base64 through Apps Script

The benchmark moves a 10,000-byte binary from the sandbox to Drive by two different methods.

In Approach A, the sandbox creates a 10 KB binary from /dev/urandom and streams it to Drive with ggsrun in a single interaction turn (freshInteraction: true). In Approach B, the sandbox encodes the same bytes as Base64 and returns them in the API response text, and Apps Script decodes the string and saves the file to Drive. Approach A finished in 16.20 seconds at 0.60 KB/s and used no Apps Script CPU. Approach B needed 32.13 seconds at 0.30 KB/s plus 1.23 seconds of Apps Script CPU, so A was 1.98 times faster. It also avoids the roughly 33 percent Base64 inflation and the token exhaustion that multi-turn sessions suffer. The recorded report looks like this.

================================================================================
PERFORMANCE BENCHMARK REPORT: 10,000 BYTES FILE TRANSFER TO GOOGLE DRIVE
================================================================================
| Metric                       | Approach A: Direct ggsrun Upload | Approach B: Base64 via Gemini API -> GAS |
| :--------------------------- | :------------------------------- | :--------------------------------------- |
| Transfer Method              | Direct Sandbox-to-Drive (Go CLI) | Base64 Stream -> GAS -> Drive            |
| Drive File Name              | benchmark_10kb_ggsrun.bin        | benchmark_10kb_gas.bin                   |
| Verified File Size           | 10,000 bytes (9.77 KB)           | 10,000 bytes (9.77 KB)                   |
| API Turns Required           | 1 Turn (Direct Offload)          | 1 Turn (Base64 Retrieval)                |
| Local GAS Processing Time    | 0.00 s (Zero CPU overhead)       | 1.23 s (Base64 Decode & Blob Creation)   |
| Total End-to-End Duration    | 16.20 s                          | 32.13 s                                  |
| Effective Throughput         | 0.60 KB/s                        | 0.30 KB/s                                |
| Performance Multiplier       | 1.98x FASTER                     | Baseline (Higher Latency & Token Usage)  |
================================================================================

Streaming through ggsrun was 1.98 times faster, removed all of the Apps Script CPU and memory cost of decoding, and consumed none of the conversation token quota. For multi-megabyte payloads, this direct route is what keeps 429 Quota Exceeded errors away.

Driving the same sandbox from a local workstation

To show that the same persistent Linux sandbox can be controlled from Apps Script and from a laptop, a Node.js client with Server-Sent Events streaming was built on the @google/genai SDK. Apps Script runs synchronously and blocking, collecting agent events at the end of the HTTP request, so the local runner gives developers several things Apps Script can't.

  • Live visibility into the run. Reasoning steps (thought), shell commands (code_execution_call), sandbox stdout and stderr (code_execution_result) and model text (model_output) stream to the terminal as they happen, with ANSI colors.
  • A hybrid workflow for development. Prompts and toolchains can be prototyped, debugged and tuned locally with real-time feedback, then moved into automated Apps Script triggers that run unattended.
  • Easy sandbox sharing. Putting the identifier generated during Apps Script provisioning into ENVIRONMENT_ID in a local .env file attaches the client to the existing container, with all its installed packages, compiled binaries and workspace files, and no reinstalling.
  • Automatic OAuth handling. The runner obtains fresh Google access tokens with gcloud auth print-access-token and injects them into GGSRUN_AT, so direct-to-Drive uploads work as they do in Apps Script without copying credentials by hand.

Running the local tests takes four steps.

  1. Clone the repository and run npm install inside local-node.js-src.
  2. Duplicate .env.example as .env and fill in three values: your API key as GEMINI_API_KEY, the long-lived sandbox identifier as ENVIRONMENT_ID, and the Drive folder to write into as TARGET_FOLDER_ID.
  3. Run npm test, or a single test with npm run test:1 through test:6, and watch the agent work in real time.
  4. When finished, run npm run test:teardown to delete the remote sandbox and release cloud resources.

Complete raw transcripts with the live streamed output are saved in the repository's local-node.js-src/execution-logs.md, and they show the same functional results as the Apps Script runs.

API patterns for Managed Agents

The patterns below summarize the common ways to work with the Gemini v1beta Interactions and Environments API. Every one of them sends a POST to the same base endpoint.

POST https://generativelanguage.googleapis.com/v1beta/interactions?key=${API_KEY}
Content-Type: application/json

Scenario 1 shares a single persistent sandbox among many clients. Create the remote environment once, with environment.type set to "remote", keep the environment_id that comes back, and send it as a plain string in later requests from Apps Script, Node.js, Python or a CI/CD job.

{
  "agent": "antigravity-preview-05-2026",
  "input": "Run task in shared container...",
  "environment": "environments/env-12345"
}

Scenario 2 is an isolated sandbox for each run. When a task needs a completely fresh Linux environment, set environment.type to "remote" on every call.

{
  "agent": "antigravity-preview-05-2026",
  "input": "Execute client-specific isolated task...",
  "environment": {
    "type": "remote"
  }
}

Scenario 3 keeps conversational context across turns. Include previous_interaction_id whenever the agent must remember earlier reasoning, variables or command output.

{
  "agent": "antigravity-preview-05-2026",
  "input": "Based on the previous output, proceed to step 2...",
  "environment": "environments/env-12345",
  "previous_interaction_id": "interaction-prev-67890"
}

Scenario 4 reuses a sandbox with a fresh context, the freshInteraction option. Supply the existing environment_id and leave out previous_interaction_id. All files and installed tools on the container survive, while the conversation history resets to zero tokens, which avoids exhausting the TPM rate limit.

{
  "agent": "antigravity-preview-05-2026",
  "input": "Execute a completely new task in the existing sandbox...",
  "environment": "environments/env-12345"
}

Comparison at a glance

The summary matrix puts the approaches next to each other.

What this adds up to

Combining Apps Script with Gemini Managed Agents moves past the usual limits of a serverless runtime. A persistent remote sandbox, plus streaming straight between cloud storage and the sandbox in both directions, makes workloads possible that Apps Script alone can't handle, and it sidesteps API payload limits and conversational token quotas.

  • Apps Script limits are overcome. Jobs that need a native Linux environment, such as headless browser scraping with Playwright, audio synthesis with FFmpeg and TypeScript compilation with esbuild, can now be launched from Apps Script.
  • AI reasoning meets Linux execution. Instead of running fixed external commands, Gemini's reasoning is paired with a real shell, so commands are composed on the fly, code runs at run time and the agent can correct itself during complex workflows.
  • Streaming in both directions saves tokens and payload. Uploading artifacts and also pulling large input datasets from Drive into the sandbox avoids stuffing data into prompts and Base64 conversion, which keeps input and output tokens low and stays clear of the 50 MB limit and the 200,000 TPM quota.
  • Shared sandboxes lower costs. Staging common datasets and tooling on a shared container filesystem removes repeated uploads and setup for each task, cutting latency and bandwidth.
  • The benchmark backs this up. Direct cloud-to-cloud CLI streaming was 1.98 times faster than fetching Base64 through the API, with no CPU decoding cost and no local memory use in Apps Script.