Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
160277 stories
·
33 followers

Taking Advantage of Gemini Managed Agents with Google Apps Script

1 Share

Breaking the Limits of GAS with Direct Cloud-to-Cloud Streaming in Persistent Linux Sandboxes

Abstract

While Google Apps Script (GAS) is a powerful tool for Google Workspace automation, platform and computational constraints often limit its ability to handle advanced workloads. Gemini Managed Agents provide remote Linux sandboxes equipped with bash execution. This article introduces an architecture integrating GAS with a Linux sandbox to execute tasks beyond the capabilities of Apps Script alone. By streaming generated artifacts directly from within the Linux sandbox to Google Drive, this approach bypasses API payload limits, eliminates token overhead, and achieves high-throughput cloud automation.

Introduction

Recently, Martin Hawksey published an inspiring article on AppsScriptPulse exploring the potential of Gemini Managed Agents and the Google Workspace CLI within Google Workspace automation. Ref Gemini Managed Agents (part of the Gemini v1beta Interactions and Environments API) allow developers to provision and interact with remote Linux sandbox environments capable of autonomous code execution, shell commands, and package management. Ref

While Google Apps Script (GAS) is widely used for automating Google Workspace workflows, it operates as a lightweight, restricted serverless runtime without OS-level access, inherently preventing developers from executing various advanced computational workloads. Common platform bottlenecks include restricted low-level network and protocol controls, the absence of headless browser environments for dynamic web rendering, the inability to run native binaries for media transcoding or signal processing, the lack of modern compilers and build toolchains, and strict platform quotas on execution duration and payload sizes. The objective of this article is to introduce a generalized architecture that bridges GAS with a full-featured Linux sandbox provisioned by Gemini Managed Agents, demonstrating how developers can seamlessly offload otherwise impossible workloads to a dedicated cloud compute environment with high throughput and complete autonomy.

By integrating Google Apps Script with Gemini Managed Agents, GAS gains access to a dedicated Linux container (4 vCPU, 16 GB RAM) featuring Python 3.12, Node.js 22, and standard Linux package managers (apt, npm, pip). In this article, I present an end-to-end architecture and client library that enables GAS to orchestrate complex tasks inside a persistent Linux sandbox, eliminating local processing overhead by streaming generated artifacts directly to Google Drive via the ggsrun CLI tool.

Architectural Paradigm: Why Direct Cloud-to-Cloud Streaming?

When generating large files (such as high-resolution screenshots, audio waveforms, or bundled JavaScript) inside a Managed Agent sandbox and transferring them to Google Drive, returning raw binary data as Base64 strings through the Gemini API response to GAS introduces severe platform bottlenecks:

  • GAS URL Fetch Response Limit: Google Apps Script enforces a strict 50 MB response payload limit on UrlFetchApp. Ref
  • Code Execution Output Buffer Truncation: The Gemini Interactions API code execution environment imposes standard output (stdout) buffer limits, truncating multi-megabyte Base64 payloads mid-stream. Ref
  • Rate Limits and Conversational Token Inflation: Gemini Managed Agents enforce a 200,000 Tokens Per Minute (TPM) quota. Ref Base64 encoding inflates binary size by ~33%. In multi-turn sessions, accumulating previous Base64 output strings in conversation history rapidly exhausts input token quotas, triggering immediate 429 Quota Exceeded errors.
  • CPU and Memory Overhead on GAS: Decoding multi-megabyte Base64 strings and creating Drive blobs inside Apps Script consumes valuable execution time and script memory.

Figure 1: Architectural comparison between Base64 API transfer and direct cloud-to-cloud streaming via ggsrun

To eliminate these bottlenecks, the optimal approach is to execute the Go CLI tool ggsrun directly inside the Linux sandbox using a dynamically injected OAuth access token (ScriptApp.getOAuthToken()). This allows the sandbox to stream binary artifacts directly to Google Drive over Google Cloud's internal backbone network at speeds exceeding 2 MB/s, completely bypassing Apps Script memory, API response size limits, and token quota exhaustion.

Drastic Input Token Savings via Bi-directional Streaming

The advantages of direct cloud-to-cloud streaming extend far beyond outbound artifact uploads. When bringing large external datasets (high-resolution images, audio, video files, multi-gigabyte CSV/JSON datasets, or machine learning models) into the sandbox for processing, direct inbound downloads provide an equally critical advantage.

Embedding large binary or structured datasets directly into API prompts as Base64 strings or serialized text rapidly consumes input token quotas, instantly hitting the 200,000 Tokens Per Minute (TPM) limit and triggering immediate 429 Quota Exceeded errors. In contrast, by streaming files directly from Google Drive into the sandbox via ggsrun, the prompt requires only a concise instruction (e.g., "Download target dataset from Drive and analyze it"). This architecture reduces input token consumption to virtually zero, completely preventing rate-limit exhaustion.

Process Cost Reduction via Shared Persistent Sandboxes

Furthermore, sharing a single persistent Linux sandbox (environmentId) across multiple clients—including Google Apps Script, local Node.js workstations, Python scripts, and CI/CD pipelines—dramatically lowers operational process costs.

By staging common master datasets, corpora, libraries, or pre-trained models inside the persistent sandbox filesystem (/workspace/), any client can immediately leverage those shared assets to generate content and execute complex processing. This eliminates the redundant overhead of uploading or re-initializing datasets on every execution turn, significantly reducing execution latency, network bandwidth, and cumulative API overhead.

Furthermore, provisioning a single persistent Linux sandbox and sharing its unique environmentId across multiple script executions, Google Apps Script projects, and local developer workstations eliminates redundant initialization overhead and allows multiple tasks to reuse shared working files and pre-installed packages seamlessly.

Workflow

The following diagram illustrates the complete end-to-end architecture where Google Apps Script and local Node.js workstations orchestrate a single persistent Linux sandbox using a shared environmentId, leveraging bi-directional streaming (Inbound download / Outbound upload) and shared master datasets for instant content generation.

Figure 2: End-to-end bi-directional workflow and shared persistent sandbox architecture

Figure 2 Narrative: The diagram outlines the data integration and execution pipelines across cloud and local environments:

  • Multi-Client Orchestration: Cloud-based Google Apps Script (synchronous trigger, dynamic OAuth token) and local Node.js workstations (real-time SSE streaming, gcloud CLI auth) orchestrate the exact same remote container via a shared environmentId.
  • Shared Data Repository & Pre-installed Toolchains: The persistent sandbox (4 vCPU / 16 GB RAM) retains shared master datasets and build tools (Playwright, FFmpeg, esbuild), enabling instant content generation without redundant data re-upload overhead.
  • Inbound Direct Download (ggsrun download): Streams large external datasets directly from Google Drive into the sandbox, eliminating prompt data embedding and preserving input token quotas (200k TPM safe).
  • Outbound Direct Upload (ggsrun upload): Streams generated binary deliverables directly to Google Drive at 2+ MB/s, completely bypassing GAS 50 MB payload limits and stdout buffer truncation.

Repository

All source code, GAS classes, Node.js stream clients, test suites, and raw execution logs are available in the GitHub repository:

Usage

1. Obtain Gemini API Key

Generate an API key from Google AI Studio. Ref This API key authenticates requests to the Gemini v1beta Interactions and Environments APIs.

2. Create Google Apps Script Project

Create a Google Apps Script project using either of the following methods: Ref

  • Standalone Project: Visit script.google.com and click New project.
  • Container-bound Project: Open a Google Sheet, Doc, or Form, click Extensions, and select Apps Script.

3. Deploy Client Scripts & Set Script Properties

Copy the following files from the repository into your Apps Script editor:

  • ManagedAgentSandboxClient.js: Core client class managing sandbox lifecycle, dynamic environment variables, session persistence in PropertiesService, and intelligent 429 rate-limit backoff.
  • tests.js: Master test suite covering sandbox provisioning, tooling verification, media processing, web scraping, and performance benchmarks.

Navigate to Project Settings > Script Properties and add your API key: Ref

  • Property: GEMINI_API_KEY
  • Value: Your Gemini API Key

4. Required Authorization Scopes

Ensure your project manifest (appsscript.json) includes the necessary OAuth scopes:

  • https://www.googleapis.com/auth/script.external_request: Required for UrlFetchApp API communication.
  • https://www.googleapis.com/auth/drive: Required for creating destination folders and uploading artifacts. (If using existing folders without DriveApp.createFolder(), https://www.googleapis.com/auth/drive.file can be used).

Testing on Cloud (Google Apps Script)

Execution logs for all tests can be verified in gas-src/execution-logs.md.

1. Provisioning a Unified Linux Sandbox

Executing provisionSharedSandbox() initializes a new remote Linux container, installs all required CLI utilities and dependencies, configures destination Google Drive paths, and saves the resulting environmentId in PropertiesService.

Figure 3: Technical infographic of provisioning a unified persistent Linux sandbox via Google Apps Script

Figure 3 Narrative: The infographic details the 4-step provisioning pipeline. In Step 1, Google Drive creates destination directory ManagedAgent_Artifacts_YYYYMMDD. In Step 2, a 4 vCPU / 16 GB RAM Linux container bootstraps ggsrun, ffmpeg, sox, jq, typescript, esbuild, and Playwright (Chromium). In Step 3, the sandbox validates installed binaries and emits a READY status. In Step 4, the unique environmentId is persisted under SHARED_SANDBOX_SESSION in PropertiesService for multi-test and cross-client reuse.

  • Step 1: Destination folder ManagedAgent_Artifacts_YYYYMMDD is created in Google Drive.
  • Step 2: An initialization prompt dispatches commands to download ggsrun, install ffmpeg, sox, jq, typescript, esbuild, and configure headless Chromium via Playwright.
  • Step 3: The sandbox validates tool installations and returns a READY status.
  • Step 4: The persistent environmentId is stored under SHARED_SANDBOX_SESSION in PropertiesService for subsequent test reuse.

Running testListSandboxes() queries the Environments API to confirm active sandbox status and metadata.

2. Test 1: User-Agent Customization & POSIX Socket Verification (runTest1_UserAgentComparison)

This test demonstrates that while GAS UrlFetchApp automatically overwrites custom HTTP User-Agent headers with Google's proxy identity string, the Managed Agent sandbox preserves arbitrary header configurations via raw POSIX sockets and native curl.

Figure 4: Technical infographic of HTTP User-Agent header behavior comparison between Google Apps Script and Linux Sandbox

Figure 4 Narrative: The diagram illustrates the request and response paths when sending a custom User-Agent: sample user agent header to httpbin.org/anything. In Google Apps Script (left), platform proxy policies enforce header substitution (❌). In contrast, the Linux sandbox using curl (right) retains the exact custom header string via raw POSIX socket transmission (✅). An autonomous inline Python script compares the reflected JSON payloads and outputs the verification matrix.

  • Step 1: GAS sends an HTTP GET request to https://httpbin.org/anything specifying User-Agent: sample user agent.
  • Step 2: The sandbox executes an identical curl request to the same endpoint and compares the reflected JSON payloads using an inline Python script.
  • Summary of Execution: The comparison confirms that GAS replaced the header with Mozilla/5.0 (compatible; Google-Apps-Script; beanserver; ...), whereas the Linux sandbox preserved the exact sample user agent header string.

3. Test 2: ggsrun Deployment & Drive Direct Access Verification (runTest2_GgsrunDirectDeployment)

This test validates Google Drive authentication and direct access via ggsrun inside the sandbox by dynamically injecting a fresh OAuth access token (ScriptApp.getOAuthToken()) into the execution turn.

Figure 5: Technical infographic of dynamic OAuth token injection and ggsrun direct Google Drive deployment

Figure 5 Narrative: The infographic outlines the three execution steps of dynamic authentication and CLI offloading. In Step 1, GAS extracts ScriptApp.getOAuthToken() and dynamically injects it into the execution turn's GGSRUN_AT environment variable (eliminating 1-hour token expiration risks). In Step 2, the sandbox generates a verification file and uploads it via ggsrun upload. In Step 3, ggsrun searchfiles executes a folder query, confirming all 9 artifacts in 12.1 seconds.

  • Step 1: A verification file 00_ggsrun_verification.txt is created inside /workspace/test2/.
  • Step 2: ggsrun upload uploads the file directly to the designated Google Drive folder using non-blocking overwrite mode (--nc --cm OverwriteIfNewer -j).
  • Step 3: ggsrun searchfiles queries the destination folder to confirm file existence and returns structured metadata.
  • Summary of Execution: The file was created, uploaded, and verified in Google Drive in 12.1 seconds, confirming full workspace interoperability without persisting sensitive access tokens across sessions.

4. Test 3: Playwright Headless Scraping to Direct Drive Upload (runTest3_PlaywrightDirectUpload)

This test executes an automated headless Chromium browser session to scrape dynamic JavaScript content and capture multi-viewport screenshots.

Figure 6: Technical infographic of headless browser scraping with Playwright and bulk direct upload to Google Drive

Figure 6 Narrative: The diagram depicts headless Chromium (Playwright) rendering dynamic JavaScript pages within the sandbox to capture multi-viewport screenshots (Desktop 1280x800: 92.5 KB, Mobile 375x812: 51.6 KB, Paginated Page 2: 171.9 KB) alongside structured quote JSON (4.1 KB), totaling ~320 KB across 4 artifacts. Bypassing Base64 API conversion, all files are streamed directly to Google Drive via ggsrun upload in a single command, completing in 20.4 seconds.

  • Step 1: A Node.js Playwright script navigates to a JavaScript-rendered quote website (quotes.toscrape.com/js/).
  • Step 2: Playwright captures full-page Desktop (1280x800) and Mobile iPhone emulation (375x812) screenshots of Page 1.
  • Step 3: Playwright clicks pagination controls, captures a Desktop screenshot of Page 2, and extracts structured quote data into 02_Page2_Quotes.json.
  • Step 4: ggsrun upload transfers all 3 PNG images and the JSON dataset directly to Google Drive in a single command.
  • Summary of Execution: All 4 artifacts (totaling ~320 KB) were generated and uploaded in 20.4 seconds, successfully rendering client-side JavaScript that GAS cannot parse natively.

5. Test 4: FFmpeg Audio Synthesis & Transcoding to Direct Drive Upload (runTest4_FFmpegAudioDirectUpload)

This test executes native digital signal processing inside the sandbox using FFmpeg and SoX to synthesize multi-tone audio chords.

Figure 7: Technical infographic of multi-tone audio synthesis with FFmpeg and direct Google Drive upload

Figure 7 Narrative: The infographic illustrates the digital signal processing (DSP) pipeline inside the Linux sandbox. Three sine wave generators (440 Hz / A4, 554.37 Hz / C#5, 659.25 Hz / E5) are combined through the ffmpeg amix filter complex into a 3-second harmonic major chord MP3 (73.4 KB), while ffprobe extracts stream metadata into JSON (1.8 KB). Both binary audio and JSON analysis are streamed directly to Google Drive via ggsrun in 9.1 seconds.

  • Step 1: ffmpeg synthesizes a 3-second harmonic major chord MP3 by combining three sine waves (440 Hz, 554.37 Hz, and 659.25 Hz) through an amix audio filter complex.
  • Step 2: ffprobe analyzes the output stream and extracts waveform metadata into 03_Audio_Analysis.json.
  • Step 3: ggsrun upload uploads 03_Chord_Major.mp3 (73.4 KB) and 03_Audio_Analysis.json (1.8 KB) directly to Google Drive.
  • Summary of Execution: High-fidelity audio synthesis, metadata extraction, and Drive upload completed in 9.1 seconds.

6. Test 5: TypeScript AST Extraction & esbuild Bundling to Direct Drive Upload (runTest5_TypeScriptASTDirectUpload)

This test demonstrates modern JavaScript/TypeScript build tooling inside the sandbox environment.

Figure 8: Technical infographic of TypeScript AST extraction and high-speed esbuild compilation with direct Drive upload

Figure 8 Narrative: The diagram outlines the dual build toolchains operating on TypeScript source code (matrix.ts). The first branch employs the official TypeScript Compiler API to parse the Abstract Syntax Tree (AST) and export interface schemas (04_TypeScript_AST.json: 152 B). The second branch leverages esbuild to compile a standalone IIFE bundle (04_Matrix_Bundle.iife.js: 1.2 KB) in just 13 milliseconds. Both deliverables are offloaded to Google Drive via ggsrun in 10.0 seconds.

  • Step 1: A TypeScript module (matrix.ts) defining generic classes and interfaces is written to /workspace/test5/.
  • Step 2: A Node.js script utilizes the official TypeScript Compiler API to parse the AST and export interface and method schemas into 04_TypeScript_AST.json.
  • Step 3: esbuild bundles and minifies matrix.ts into a standalone IIFE JavaScript bundle (04_Matrix_Bundle.iife.js).
  • Step 4: ggsrun upload transfers both the AST schema and the bundled JavaScript to Google Drive.
  • Summary of Execution: AST parsing, bundle compilation (13 ms build time), and Drive upload completed in 10.0 seconds.

7. Test 6: Performance Benchmark: Direct ggsrun Upload vs. Base64 via GAS (runTest6_DriveUploadPerformanceComparison)

This benchmark evaluates transferring a binary payload (10,000 bytes) from the sandbox to Google Drive across two distinct methods:

Figure 9: Performance benchmark comparison infographic: Direct ggsrun streaming vs. Base64 transfer via Gemini API

Figure 9 Narrative: The benchmark infographic compares Approach A (direct ggsrun streaming) against Approach B (Base64 transfer via API -> GAS decode). Approach A finished in 16.20 seconds (0.60 KB/s, zero GAS CPU usage), proving to be 1.98x faster than Approach B (32.13 seconds, 0.30 KB/s, 1.23 s GAS CPU). Approach A completely eliminates Base64 payload inflation (~33%) and prevents multi-turn conversational token exhaustion.

  • Approach A (Direct ggsrun Upload): The sandbox generates a 10 KB binary file from /dev/urandom and streams it directly to Google Drive via ggsrun in a single interaction turn (freshInteraction: true).
  • Approach B (Base64 Transfer via API -> GAS Blob Save): The sandbox encodes the 10 KB binary into Base64, returns it through the Gemini API response text, and GAS decodes the string and saves the file to Drive.
================================================================================
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)  |
================================================================================

Summary of Benchmark Findings: Direct streaming via ggsrun was 1.98x faster, eliminated 100% of Apps Script CPU/memory decoding overhead, and prevented conversational token quota consumption. For multi-megabyte payloads, this direct streaming architecture is essential to prevent 429 Quota Exceeded errors.

Testing on Local Workstations (Node.js Stream Runner)

To demonstrate cross-platform interoperability enabling developers to control the exact same persistent Linux sandbox from both Google Apps Script and local workstations, a high-performance Node.js client powered by Server-Sent Events (SSE) streaming was implemented. Ref

1. Purpose and Advantages of the Local Stream Runner

While Google Apps Script operates under a synchronous blocking execution model where agent events are aggregated at the end of the HTTP request, the local Node.js runner (built with the @google/genai SDK) provides significant developer benefits:

  • Real-Time Lifecycle Visibility (SSE Streaming): Streams internal reasoning steps (thought), executed shell commands (code_execution_call), sandbox standard output/error (code_execution_result), and model text (model_output) live to the terminal with ANSI color coding.
  • Interactive Hybrid Development Workflow: Enables developers to prototype, debug, and calibrate agent prompts and toolchains locally with real-time feedback before deploying them into automated, hands-off Google Apps Script triggers.
  • Zero-Friction Sandbox Sharing (GAS ↔ Local): By simply setting ENVIRONMENT_ID in a local .env file to the identifier generated during Apps Script provisioning, the local client immediately attaches to the existing container, sharing all pre-installed packages, compiled binaries, and workspace files without re-installation overhead.
  • Automated OAuth Token Integration: Dynamically extracts fresh Google OAuth access tokens via the Google Cloud SDK (gcloud auth print-access-token) and injects them into GGSRUN_AT, executing direct-to-Drive file uploads identically to Apps Script without manual credential copying.

2. Local Setup and Test Execution

Local test suites can be executed through the following straightforward steps:

  • Step 1: Clone the repository and install dependencies by running npm install inside the local-node.js-src directory.
  • Step 2: Copy .env.example to .env and specify GEMINI_API_KEY, the persistent ENVIRONMENT_ID, and the destination TARGET_FOLDER_ID.
  • Step 3: Run npm test (or individual tests npm run test:1 through test:6) to monitor agent execution in real-time.
  • Step 4: When testing is complete, run npm run test:teardown to safely purge the remote sandbox environment and release cloud resources.

Full raw execution transcripts with live streaming outputs can be reviewed in local-node.js-src/execution-logs.md, confirming 100% functional parity with Google Apps Script executions.

Appendix: Gemini Managed Agents API Usage Patterns

The following patterns summarize common interaction models when working with the Gemini v1beta Interactions and Environments API:

Base Endpoint

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

Scenario 1: Sharing a Single Persistent Sandbox Across Multiple Clients

Provision a remote environment once by setting environment.type to "remote". Save the returned environment_id and pass it as a string in subsequent requests across any client (GAS, Node.js, Python, or CI/CD).

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

Scenario 2: Using Isolated Sandboxes per Execution

Set environment.type to "remote" on every call when tasks require a completely fresh, isolated Linux environment.

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

Scenario 3: Preserving Multi-turn Conversational Context

Include previous_interaction_id when the agent must retain knowledge of prior reasoning, variables, or command outputs.

{
  "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: Reusing Sandbox with Fresh Context (freshInteraction)

Specify the existing environment_id and omit previous_interaction_id. This preserves all files and installed tools on the Linux container while resetting conversation history to zero tokens, preventing TPM rate-limit exhaustion.

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

Summary Matrix

Figure 10: Summary matrix of Gemini Managed Agents API interaction scenarios and context management models

Summary

This article introduced an enterprise-grade architecture integrating Google Apps Script with Gemini Managed Agents (Linux sandboxes) to fundamentally transcend traditional serverless runtime constraints. By combining persistent remote sandboxes with bi-directional direct cloud-to-cloud streaming via ggsrun, developers can achieve advanced processing capabilities previously impossible in Apps Script while avoiding API payload limitations and conversational token rate quotas.

  • Overcame Apps Script Limits: Enabled advanced workloads requiring native Linux environments—such as headless browser scraping (Playwright), audio synthesis (FFmpeg), and TypeScript compilation (esbuild)—directly from Google Apps Script.
  • Autonomous Fusion of AI Reasoning & Linux Execution: Transcended static external command execution by uniting Gemini's cognitive reasoning with native Linux shell autonomy, enabling dynamic command synthesis, runtime code execution, and autonomous self-correction across complex workflows.
  • Bi-directional Streaming Token & Payload Optimization: In addition to direct artifact uploads, streaming large input datasets directly from Drive into the sandbox eliminates prompt data embedding and Base64 conversion, minimizing input/output token usage to bypass GAS 50 MB limits and the 200,000 TPM rate quota.
  • Process Cost Reduction via Shared Sandboxes: Staging and sharing the container filesystem and common master datasets across clients eliminates redundant data re-upload and tooling setup overhead per task, substantially reducing execution latency and bandwidth costs.
  • Empirically Proven 2x Performance Acceleration & Zero Memory Footprint: Validated through benchmarks that direct cloud-to-cloud CLI streaming is 1.98x faster than traditional API Base64 retrieval while imposing zero CPU decoding load or local memory consumption on Google Apps Script.
Read the whole story
alvinashcraft
7 hours ago
reply
Pennsylvania, USA
Share this story
Delete

AI’s third era: the rise of persistent AI coworkers | Tara Seshan (OpenAI’s product lead)

1 Share

Tara Seshan leads product for Codex and ChatGPT Work at OpenAI (alongside previous podcast guest Andrew Ambrosino, who’s her engineering manager). Before OpenAI, Tara spent over six years at Stripe, where she joined as one of the first five product managers. She went on to lead product for Watershed, which Time magazine named one of the best inventions of 2022, and she is also a founder and Thiel Fellow. Most personally meaningful to me: Tara is one of the three inaugural Lenny’s Newsletter Fellows, a program I ran a couple of years ago to spotlight the most exciting up-and-coming product leaders.

In our in-depth conversation, we discuss:

1. The shift from “rowing” to “steering,” and why human judgment and ambition will become differentiators as AI takes on execution

2. How OpenAI thinks about building for model capabilities two to three months out

3. OpenAI’s best internal memes, such as “Is this maximally accelerated?” and “Are you mainlining it yet?”

4. Why ambition is the new bottleneck for companies, and why elevating others’ ambitions is now the key part of the PM job

5. Writing as thinking vs. writing as reporting

Brought to you by:

WorkOS—Make your app enterprise-ready, with SSO, SCIM, RBAC, and more

Mercury—Radically different banking, now with Command

Where to find Tara Seshan:

• X: https://x.com/tarstarr

• LinkedIn: https://www.linkedin.com/in/tarstarr

• Newsletter: https://substack.com/@taraseshan

Where to find Lenny:

• Newsletter: https://www.lennysnewsletter.com

• X: https://twitter.com/lennysan

• LinkedIn: https://www.linkedin.com/in/lennyrachitsky/

In this episode, we cover:

(00:00) Introduction

(02:18) What makes OpenAI’s culture so different

(06:42) Why AI product strategy is all about fast experimentation

(09:02) How the PM role is changing

(10:50) The shift from rowing to steering

(15:35) What changes when agents become coworkers

(20:05) Why ambition matters more than ever

(26:39) Building products for models that do not exist yet

(29:21) How ChatGPT’s Chat and Work modes differ

(34:01) How OpenAI ships so quickly at scale

(39:14) The vibe shift happening inside Codex

(42:20) Why traditional roles are beginning to blur

(45:59) Where humans will continue to provide unique value

(48:20) How Tara uses AI in her own work

(51:38) The magic of the /visualize command

(52:39) Writing to think versus writing to report

(57:10) How to use AI without losing your ability to think

(01:00:15) Tara’s biggest lesson from Sutter Hill

(01:04:16) ChatGPT’s site output

(01:05:01) Why knowledge work is becoming more like coding

(01:07:55) Lightning round and final thoughts

Referenced:

• Codex: https://chatgpt.com/codex

• ChatGPT Work: https://openai.com/chatgpt-work

• Stripe: https://stripe.com

• Watershed: https://watershed.com

• Thiel Fellowship: https://thielfellowship.org

• Meet your Lenny’s Newsletter Fellows: https://www.lennysnewsletter.com/p/meet-your-lennys-newsletter-fellows

• The rituals of great teams | Shishir Mehrotra of Coda, YouTube, Microsoft: https://www.lennysnewsletter.com/p/the-rituals-of-great-teams-shishir

• The nature of product | Marty Cagan, Silicon Valley Product Group: https://www.lennysnewsletter.com/p/the-nature-of-product-marty-cagan

• Product management theater | Marty Cagan (Silicon Valley Product Group): https://www.lennysnewsletter.com/p/product-management-theater-marty

• Patrick Collison’s examples of fast projects: https://patrickcollison.com/fast

• Inside ChatGPT: The fastest-growing product in history | Nick Turley (Head of ChatGPT at OpenAI): https://www.lennysnewsletter.com/p/inside-chatgpt-nick-turley

• Andrew Ambrosino on X: https://x.com/ajambrosino

• Tyler Cowen’s website: https://tylercowen.com

• OpenAI’s CPO on how AI changes must-have skills, moats, coding, startup playbooks, more | Kevin Weil (CPO at OpenAI, ex-Instagram, Twitter): https://www.lennysnewsletter.com/p/kevin-weil-open-ai

• “Chop wood, carry water” quote: https://buddhism.stackexchange.com/questions/15921/what-is-the-meaning-of-the-zen-quote-before-enlightenment-chop-wood-carry-wat

• 4 questions Shreyas Doshi wishes he’d asked himself sooner | Former PM leader at Stripe, Twitter, Google: https://www.lennysnewsletter.com/p/shreyas-doshi-live

• Alan Kay: https://en.wikipedia.org/wiki/Alan_Kay

• Brie Wolfson on X: https://x.com/zebriez

• The playbook for building high-talent-density teams | Adam Ward, Head of Talent at Cursor: https://www.lennysnewsletter.com/p/the-playbook-for-building-high-talent

• Building product at Stripe: craft, metrics, and customer obsession | Jeff Weinstein (Product lead): https://www.lennysnewsletter.com/p/building-product-at-stripe-jeff-weinstein

• Sutter Hill Ventures: https://shv.com

• Snowflake: https://www.snowflake.com

• Mike Speiser on LinkedIn: https://www.linkedin.com/in/mikespeiser

• Footnotes and Tangents: https://footnotesandtangents.substack.com

• The Power Broker Book Club: https://www.robertcaro.org/copy-of-six-books-six-ny-times-book

The Odyssey: https://www.imdb.com/title/tt33764258

Rashomon: https://www.imdb.com/title/tt0042876

• Akira Kurosawa: https://en.wikipedia.org/wiki/Akira_Kurosawa

• Kevin Kwok on LinkedIn: https://www.linkedin.com/in/kevinakwok

• The Work You Do, the Person You Are: https://www.newyorker.com/magazine/2017/06/05/toni-morrison-the-work-you-do-the-person-you-are

• Ari Weinstein on X: https://x.com/AriX

• Sky: https://sky.app

• Dylan Field live at Config: Intuition, simplicity, and the future of design: https://www.lennysnewsletter.com/p/dylan-field-live-at-config

Recommended books:

Barbarian Days: A Surfing Life: https://www.amazon.com/dp/0143109391

Anna Karenina: https://www.amazon.com/Anna-Karenina-LEO-TOLSTOY/dp/8175993421

The Power Broker: https://www.amazon.com/dp/0394720245

War and Peace: https://www.amazon.com/War-Peace-Leo-Tolstoy/dp/8175992832

Wolf Hall: https://www.amazon.com/dp/0312429983

Production and marketing by https://penname.co/. For inquiries about sponsoring the podcast, email podcast@lennyrachitsky.com.

Lenny may be an investor in the companies discussed.



To hear more, visit www.lennysnewsletter.com



Download audio: https://pscrb.fm/rss/p/api.substack.com/feed/podcast/211586245/57f58f8fc53fc0fa2f864c0923e1517e.mp3
Read the whole story
alvinashcraft
7 hours ago
reply
Pennsylvania, USA
Share this story
Delete

#577 - 30th August 2026

1 Share

Highlights this week include:

Read the whole story
alvinashcraft
7 hours ago
reply
Pennsylvania, USA
Share this story
Delete

On The Imaginary Trade-Off Between Agility & Rigour

1 Share

In 2009, I co-authored a paper titled Formal vs. Agile: Survival of the Fittest, in which I argued that rigour and agility in software engineering were not antithetical.

I first had to explain to my co-authors that Agile Software Development (evolutionary prototyping) was not the same thing as Rapid Application Development (rapid prototyping) – still a common misunderstanding, and a pointer to where the perception of an apparent conflict might come from, at least in academia.

That initial misunderstanding may explain much of the myth that agility lacks rigour. But in my career, I’ve also come up against another misunderstanding about rigour itself.

To me, when you talk about “rigour” in software engineering, it conjures up images of precise specifications, guided inspections, static analysis, model checking, proof assistants, contracts, property-based testing, exhaustive testing of constrained state spaces, etc, being performed on small batches of changes in tight feedback loops.

I know from first-hand experience that teams can apply this level of rigour in a highly iterative, feedback-driven development process. Indeed, I know from experience that it’s the most effective way to apply that level of rigour. To attempt it in bigger batches is… well, I’ve been there and got the t-shirt, and the t-shirt says “Nope”.

I suspect what many people mean by “rigour” has more to do with ceremonial bureaucracy than actual verification and validation. It’s The Big Design Specification that needs The Big Signature from The Big Boss before anybody can type so much as a character of source code. Quite often, when they say “rigour” they mean strictly-enforced top-down control.

All too often, teams mistake authority and accountability for rigour, and subscribe to the belief that self-organising teams cannot be rigorous in their approach. I mean, how can it be rigorous if the Big Chief Architect up in the Big Tower – wearing the shiny cape and the big pointy hat – didn’t sign off on it?

I know – and you should know by now – that design without meaningful feedback is just guesswork. It’s Play Your Cards Right, except they don’t turn the cards over until the end. But a perception of rigour that creates high feedback latency – asking many more questions than it meaningfully answers – still dominates.

And, of course, the “end” is never the end. Waiting for that avalanche of feedback until the last minute is when teams have to choose between suddenly discovering rapid iterations (there’s no such thing as successful “waterfall” – just iterative development where the first iteration is reeeeally long), or canning the project because we’d spent all our budget on unvalidated guesswork. And the reliability and maintainability of the resulting software in most cases is – how should I put this? – not as good as it could be.

And almost always, when people say rigour can’t be achieved in an agile way, they have zero experience of creating software to that high integrity. Turns out ticking process boxes and signing off documents isn’t actually testing the software.

I appreciate in many safety-critical applications, like aviation and medical devices, there’s a lot of legally-mandated box-ticking, form-filling and signing-off. But I’ve argued for decades that it’s just an illusion of rigour, and something the industry really needs to address. The bureaucracy by itself has little-to-no impact on the reliability of the resulting software. The evidence it produces of reliability is of low quality – we did the things we were mandated to do. It’s less about product quality and more about lack of trust in the people creating the product, and about covering our backsides if things go wrong.

In a TDD workshop last week, a student asked if the tests used to drive the design of their code were sufficient to ensure its integrity. It depends on how complex the logic is and how reliable the implementation needs to be, of course. But I asked them if they’d ever written this many tests for that amount of solution code before. “No.”

When it’s called for, I can go a lot further than TDD – I have many testing techniques in my toolbox. And I don’t need a complete, signed-off system specification or a vote from a committee to do it.





Read the whole story
alvinashcraft
7 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Boxed In: Working With Azure's Region Constraints Instead of Getting Surprised By Them

1 Share
If you've tried to spin up a VM in East US and gotten slapped with an AllocationFailed error, or watched a GPU quota request sit in "In Review" for three weeks, you already know where this post is going. Azure capacity constraints aren't a rumor anymore...they're a planning input. And most of the guidance out there stops at "just use multiple regions," which is true and also almost useless without the mechanics behind recommendation. This is the post I wish I could hand every client who asks...

Read the whole story
alvinashcraft
11 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Grand Central Station: Why BLoC, Riverpod, and BlocSignal Are Now True Peers

1 Share

By Randal L. Schwartz, and a few million TPU cycles

Motto: "With the rigor of Bloc and the flex and speed of Signal"

The State Management Balkanization Is Officially Over

If you have spent any time in the Flutter community over the past eight years, you have witnessed the great "State Management Wars."

On one track sat Classic BLoC: strict, battle-tested, enterprise-grade, but heavily reliant on asynchronous Dart Stream microtasks. On an adjacent track sat Riverpod: offering compile-time safety and declarative dependency graph plumbing, but steering increasingly toward mandatory code generation and build_runner iteration tax. On the newest high-speed track arrived Signals: offering raw sub-microsecond synchronous reactivity and fine-grained UI rebuilding.

For years, choosing a state management library felt like choosing an isolated railroad network. If an engineering team built their core application with flutter_bloc or flutter_riverpod and wanted to take advantage of synchronous Signals for a new high-frequency feature, conventional wisdom dictated a painful choice: either undertake a risky, multi-month rewrite or suffer through clunky, second-class adapter boilerplate.

Traditional "interop" packages in our ecosystem have almost always been an afterthought—awkward, leaky wrappers designed to tolerate legacy code until someone finds the budget to delete it.

Today, with the release of bloc_signals_bloc and a major update to bloc_signals_riverpod, we are fundamentally changing that paradigm.

BLoC, Riverpod, and BlocSignal are no longer competing silos. They are first-class, bidirectional peers.

🏛️ The Metaphor: Grand Central State Terminal

Imagine walking into a majestic railway terminal—vaulted glass arches overhead, golden sunbeams cutting through the air, and railway block signal gantries glowing bright green.

Pulling up to the platforms side by side on three parallel steel tracks are three distinct locomotives:

  🚂 Track 1: Classic BLoC (The Steam Locomotive) ─────┐
                                                        │
  🚚 Track 2: Riverpod (The Heavy Freight Hauler) ──────┼──► [ Grand Central State Terminal ] ◄──► Synchronous Signals
                                                        │
  🚄 Track 3: BlocSignal (The High-Speed Maglev) ───────┘
  1. The Steam Locomotive (Classic BLoC): The venerable, heavy-duty iron horse. Explicit event-to-state contracts, distinct mechanical pistons, and a proven safety record powering thousands of enterprise apps.
  2. The Heavy Freight Locomotive (Riverpod): The industrial logistics powerhouse. Unmatched at hauling complex dependency graph freight, managing scoped provider routes, and coordinating global-to-local supply chains.
  3. The High-Speed Maglev (BlocSignal): The aerodynamic bullet train. Zero microtask drag, instant sub-microsecond acceleration, streamless execution, and fine-grained reactivity.

In Grand Central Terminal, the tracks do not collide, and no train is treated as second-class rolling stock. Platforms sit adjacent to each other. Passengers (state, events, actions) walk across the concourse between trains with zero baggage check fees, zero customs delays, and zero microtask penalties.

⚡ What Makes Them "True Peers"?

In most architectures, adapting one state container to another requires wrapping everything in custom StreamController instances, registering manual listener callbacks, and remembering to clean up disposers to prevent memory leaks.

Under BlocSignal, peer integration is completely bidirectional, lifecycle-managed, and type-safe:

From Target ➔ To Target How It Works Developer Ergonomics
Classic BLoC ➔ BlocSignal classicBloc.toBlocSignal() Exposes synchronous .state signal + forwards .add(event)
Classic Cubit ➔ CubitSignal classicCubit.toBlocSignal() Exposes synchronous .state signal + typed .cubit methods
Riverpod Provider ➔ BlocSignal provider.toBlocSignal(ref) Exposes synchronous .state signal + typed .notifier methods + auto-disposal
BlocSignal ➔ Classic BLoC blocSignal.toClassicBloc() Direct drop-in for legacy flutter_bloc BlocBuilder / BlocListener
CubitSignal ➔ Classic Cubit cubitSignal.toClassicCubit() Direct drop-in for legacy flutter_bloc widgets
BlocSignal / CubitSignal ➔ Riverpod blocSignal.toProvider() Direct drop-in for Riverpod ref.watch and ref.read
Riverpod AsyncValue ↔ Signals AsyncState .toAsyncState() / .toAsyncValue() Seamless mapping across sealed loading/error/data states

Let's put this into practice with a concrete example.

☕ The "Grand Central Triple Counter" (Least Boilerplate Possible)

What does it look like when all three state engines work together in a single Flutter screen?

Here is a complete, runnable Flutter app where a Classic BLoC, a Riverpod Notifier, and a Modern CubitSignal live side by side. Each manages its own domain state, yet they compose synchronously into a unified Grand Total using a single computed signal in under 65 lines of code:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:bloc/bloc.dart' as bloc_lib;
import 'package:bloc_signals/bloc_signals.dart';
import 'package:bloc_signals_bloc/bloc_signals_bloc.dart';
import 'package:bloc_signals_riverpod/bloc_signals_riverpod.dart';
import 'package:signals_flutter/signals_flutter.dart';

// 🚂 1. CLASSIC BLOC: The Steam Engine (Explicit Event -> State)
class ClassicCounterBloc extends bloc_lib.Bloc<int, int> {
  ClassicCounterBloc() : super(0) {
    on<int>((event, emit) => emit(state + event));
  }
}

// 🚚 2. RIVERPOD: The Freight Hauler (Declarative Notifier)
class RiverpodCounter extends Notifier<int> {
  @override
  int build() => 0;
  void increment() => state++;
}
final riverpodCountProvider =
    NotifierProvider<RiverpodCounter, int>(RiverpodCounter.new);

// 🚄 3. BLOCSIGNAL: The High-Speed Maglev (Synchronous Signals)
class ModernSignalCubit extends CubitSignal<int> {
  ModernSignalCubit() : super(initialState: 0);
  void increment() => emit(stateValue + 1);
}

// 🏛️ GRAND CENTRAL TERMINAL: The Peer Counter Screen
class GrandCentralCounterScreen extends ConsumerWidget {
  const GrandCentralCounterScreen({
    super.key,
    required this.classicBloc,
    required this.signalCubit,
  });

  final ClassicCounterBloc classicBloc;
  final ModernSignalCubit signalCubit;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 🔀 Adapt BLoC and Riverpod into first-class signal peers:
    final blocPeer = classicBloc.toBlocSignal();
    final riverpodPeer = riverpodCountProvider.toBlocSignal(ref);

    // ⚡ Synchronously compute the Grand Total across all three rail lines:
    final grandTotal = computed(
      () => blocPeer.state() + riverpodPeer.state() + signalCubit.state(),
    );

    return Scaffold(
      appBar: AppBar(title: const Text('Grand Central State Terminal')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('🚂 Classic BLoC Count: ${blocPeer.stateValue}'),
            Text('🚚 Riverpod Count: ${riverpodPeer.stateValue}'),
            Text('🚄 BlocSignal Count: ${signalCubit.stateValue}'),
            const Divider(height: 32, indent: 64, endIndent: 64),
            // Reactively updates the instant ANY train leaves its station!
            Watch((context) => Text(
              '🏁 Grand Total: ${grandTotal()}',
              style: Theme.of(context).textTheme.headlineMedium,
            )),
          ],
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton.extended(
            heroTag: 'bloc',
            label: const Text('+1 BLoC'),
            onPressed: () => blocPeer.add(1),
          ),
          const SizedBox(width: 8),
          FloatingActionButton.extended(
            heroTag: 'riverpod',
            label: const Text('+1 Riverpod'),
            onPressed: () => riverpodPeer.notifier.increment(),
          ),
          const SizedBox(width: 8),
          FloatingActionButton.extended(
            heroTag: 'signal',
            label: const Text('+1 Signal'),
            onPressed: () => signalCubit.increment(),
          ),
        ],
      ),
    );
  }
}

void main() {
  // Initialize classic BLoC and modern CubitSignal instances:
  final classicBloc = ClassicCounterBloc();
  final signalCubit = ModernSignalCubit();

  runApp(
    // Wrap with Riverpod's ProviderScope at the application root:
    ProviderScope(
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        home: GrandCentralCounterScreen(
          classicBloc: classicBloc,
          signalCubit: signalCubit,
        ),
      ),
    ),
  );
}

Why This Integration Is Revolutionary:

  1. Bidirectional Control via Typed Getters:
    • blocPeer.add(1) dispatches directly into the underlying classic Bloc event queue.
    • riverpodPeer.notifier.increment() calls the underlying RiverpodCounter methods with full type safety.
    • signalCubit.increment() triggers immediate synchronous signal emission.
  2. Lifecycle Auto-Wiring:
    • counterProvider.toBlocSignal(ref) automatically registers ref.onDispose to close the underlying bridge when the widget or provider scope unmounts. No memory leaks.
  3. Synchronous Cross-Framework Composition:
    • Look at grandTotal: computed(() => blocPeer.state() + riverpodPeer.state() + signalCubit.state()).
    • A single computed signal observes state originating in package:bloc, package:riverpod, and package:bloc_signals simultaneously. When any of the three states update, grandTotal recalculates in the exact same frame with zero microtask queue hops.

🔄 The Return Trip: Exporting BlocSignal to Legacy Trees

Peer status is not a one-way street. What if you build a cutting-edge feature using modern BlocSignal containers, but you need to embed it inside an existing application that relies entirely on flutter_bloc's BlocBuilder or Riverpod's ConsumerWidget?

You don't need to rewrite your containers!

1. Modern BlocSignal ➔ Classic flutter_bloc Trees

final modernCubit = ModernSignalCubit();

// Adapt to a classic flutter_bloc Cubit:
final classicCubit = modernCubit.toClassicCubit();

// Consume directly inside existing flutter_bloc widgets with standard types:
BlocBuilder<bloc_lib.Cubit<int>, int>(
  bloc: classicCubit,
  builder: (context, state) => Text('Legacy BLoC UI: $state'),
);

2. Modern BlocSignal ➔ Riverpod ProviderScope Trees

final modernCubit = ModernSignalCubit();

// Expose modern CubitSignal as a standard Riverpod NotifierProvider:
final myRiverpodProvider = modernCubit.toProvider();

// In any Riverpod ConsumerWidget:
Widget build(BuildContext context, WidgetRef ref) {
  final count = ref.watch(myRiverpodProvider);
  return ElevatedButton(
    onPressed: () => ref.read(myRiverpodProvider.notifier).cubit.increment(),
    child: Text('Riverpod UI: $count'),
  );
}

🎯 What This Means for Engineering Teams

This architectural milestone eliminates the single largest point of friction in Flutter development:

  • No More All-or-Nothing Rewrites: You can introduce BlocSignal into a massive legacy Riverpod or BLoC production codebase one screen, one dialog, or one widget at a time.
  • Respect for Established Code: Your battle-tested classic BLoC authentication flows or Riverpod dependency injection graphs do not need to be touched. They plug directly into synchronous signal pipelines as first-class citizens.
  • Freedom of Choice for Greenfield Features: For new, high-performance features (for example complex forms, real-time dashboards, charts, animations, or web components), your team can leverage zero-codegen, streamless BlocSignal containers with sub-microsecond rendering speed.
  • Clean AI Coding Skills for Interop and Migration: We provide official, pre-packaged AI coding skill bundles that guide AI assistants (such as Antigravity, Claude Code, Gemini, and Cursor) with exact bidirectional rules, decision trees, and step-by-step migration recipes without hallucinations.

🏁 All Aboard at Grand Central

State management in Flutter does not have to be an ideological battleground.

Whether your architecture runs on the Classic BLoC Iron Horse, the Riverpod Freight Hauler, or the BlocSignal Bullet Train, Grand Central Terminal ensures green lights across all lines.

To get started today, add the peer packages to your pubspec.yaml:

dependencies:
  bloc_signals: ^1.0.0
  bloc_signals_bloc: ^1.0.0      # For Classic BLoC peer bridges
  bloc_signals_riverpod: ^1.2.0  # For Riverpod peer bridges
  bloc_signals_flutter: ^1.0.0   # For Flutter widget bindings

Check out the complete documentation, interactive API catalogs, and live showcase apps at blocsignal.dev.

See you on the tracks!

Read the whole story
alvinashcraft
11 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories